diff --git a/.storybook/main.ts b/.storybook/main.ts index c52e9a20..389036da 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -8,7 +8,6 @@ const config: StorybookConfig = { "addons": [ "@storybook/addon-links", "@storybook/addon-essentials", - "@chromatic-com/storybook", "@storybook/addon-interactions" ], "framework": { diff --git a/.storybook/preview.ts b/.storybook/preview.ts index fa2d8c1a..3cefc7fc 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -4,6 +4,9 @@ export const parameters = { return a.id.localeCompare(b.id, undefined, { numeric: true }) }, }, + actions: { + argTypesRegex: "^on[A-Z].*", + }, controls: { matchers: { color: /(background|color)$/i, diff --git a/package.json b/package.json index 2b157379..90e96e33 100755 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@douyinfe/semi-foundation": "2.55.1", "@douyinfe/semi-theme-default": "2.55.1", "@vue/repl": "^3.2.0", - "vue": "^3.4.19", + "vue": "^3.4.21", "lodash": "^4.17.21" }, "devDependencies": { @@ -52,15 +52,16 @@ "vue-inline-svg": "^2.1.3", - "@chromatic-com/storybook": "1.2.25", - "@storybook/addon-essentials": "^8.0.4", - "@storybook/addon-interactions": "^8.0.4", - "@storybook/addon-links": "^8.0.4", - "@storybook/blocks": "^8.0.4", - "@storybook/test": "^8.0.4", - "@storybook/vue3": "^8.0.4", - "@storybook/vue3-vite": "^8.0.4", - "storybook": "^8.0.4", + "@storybook/addon-essentials": "7.1.1", + "@storybook/addon-interactions": "7.1.1", + "@storybook/addon-links": "7.1.1", + "@storybook/blocks": "7.1.1", + "@storybook/testing-library": "^0.2.0", + "@storybook/vue3": "7.1.1", + "@storybook/vue3-vite": "7.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "storybook": "7.1.1", "@babel/core": "^7.18.2", diff --git a/packages/semi-animation-vue/package.json b/packages/semi-animation-vue/package.json index 7c783f88..45d6b2e5 100755 --- a/packages/semi-animation-vue/package.json +++ b/packages/semi-animation-vue/package.json @@ -44,7 +44,7 @@ "@douyinfe/semi-theme-default": "2.55.1", "classnames": "^2.3.2", "sass": "^1.57.1", - "vue": "^3.4.19" + "vue": "^3.4.21" }, "peerDependencies": { "vue": ">=3.4.3" diff --git a/packages/semi-icons-lab-vue/package.json b/packages/semi-icons-lab-vue/package.json index f8cf55d2..86408c45 100644 --- a/packages/semi-icons-lab-vue/package.json +++ b/packages/semi-icons-lab-vue/package.json @@ -31,7 +31,7 @@ "@douyinfe/semi-theme-default": "2.55.1", "classnames": "^2.3.2", "sass": "^1.57.1", - "vue": "^3.4.19" + "vue": "^3.4.21" }, "peerDependencies": { "vue": ">=3.4.3" diff --git a/packages/semi-icons-vue/package.json b/packages/semi-icons-vue/package.json index 6deb8695..b4503cb2 100755 --- a/packages/semi-icons-vue/package.json +++ b/packages/semi-icons-vue/package.json @@ -31,7 +31,7 @@ "@douyinfe/semi-theme-default": "2.55.1", "classnames": "^2.3.2", "sass": "^1.57.1", - "vue": "^3.4.19" + "vue": "^3.4.21" }, "peerDependencies": { "vue": ">=3.4.3" diff --git a/packages/semi-illustrations-vue/package.json b/packages/semi-illustrations-vue/package.json index 509ccf1d..46f1a771 100755 --- a/packages/semi-illustrations-vue/package.json +++ b/packages/semi-illustrations-vue/package.json @@ -28,7 +28,7 @@ "@douyinfe/semi-foundation": "2.55.1", "@douyinfe/semi-theme-default": "2.55.1", "classnames": "^2.3.2", - "vue": "^3.4.19" + "vue": "^3.4.21" }, "peerDependencies": { "vue": ">=3.4.3" diff --git a/packages/semi-ui-vue/package.json b/packages/semi-ui-vue/package.json index 8530468d..b49233f6 100755 --- a/packages/semi-ui-vue/package.json +++ b/packages/semi-ui-vue/package.json @@ -46,7 +46,7 @@ "sass": "^1.57.1", "scroll-into-view-if-needed": "^2.2.28", "utility-types": "^3.10.0", - "vue": "^3.4.19" + "vue": "^3.4.21" }, "peerDependencies": { "vue": ">=3.3.4" diff --git a/packages/semi-ui-vue/src/App.tsx b/packages/semi-ui-vue/src/App.tsx index 39a78cae..af4fc6ff 100755 --- a/packages/semi-ui-vue/src/App.tsx +++ b/packages/semi-ui-vue/src/App.tsx @@ -115,7 +115,7 @@ const App = defineComponent((props, {slots}) => { return () => (
- + {/**/} {/**/} {/**/} {/**/} @@ -124,7 +124,7 @@ const App = defineComponent((props, {slots}) => { {/**/} {/**/} {/**/} - {/**/} + {/**/} {/**/} {/**/} diff --git a/packages/semi-ui-vue/src/components/descriptions/index.tsx b/packages/semi-ui-vue/src/components/descriptions/index.tsx index c8278494..fd1a290a 100644 --- a/packages/semi-ui-vue/src/components/descriptions/index.tsx +++ b/packages/semi-ui-vue/src/components/descriptions/index.tsx @@ -69,7 +69,6 @@ const Descriptions = defineComponent( function adapter_(): DescriptionsAdapter { return { ...adapterInject(), - //@ts-ignore getColumns: () => { if (props.data?.length) { return props.data; diff --git a/packages/semi-ui-vue/src/components/form/hoc/withField.tsx b/packages/semi-ui-vue/src/components/form/hoc/withField.tsx index 2b0642f3..bce43d1b 100644 --- a/packages/semi-ui-vue/src/components/form/hoc/withField.tsx +++ b/packages/semi-ui-vue/src/components/form/hoc/withField.tsx @@ -1,36 +1,37 @@ import classNames from 'classnames'; import * as PropTypes from '../../PropTypes'; -import {cssClasses} from '@douyinfe/semi-foundation/form/constants'; +import { cssClasses } from '@douyinfe/semi-foundation/form/constants'; import { generateValidatesFromRules, mergeOptions, mergeProps, transformDefaultBooleanAPI, - transformTrigger + transformTrigger, } from '@douyinfe/semi-foundation/form/utils'; -import {isValid} from './utils' +import { isValid } from './utils'; import * as ObjectUtil from '@douyinfe/semi-foundation/utils/object'; import isPromise from '@douyinfe/semi-foundation/utils/isPromise'; import warning from '@douyinfe/semi-foundation/utils/warning'; -import {useArrayFieldState, useFormState, useStateWithGetter} from '../hooks/index'; +import { useArrayFieldState, useFormState, useStateWithGetter } from '../hooks/index'; import ErrorMessage from '../errorMessage'; -import {isElement} from '../../_base/reactUtils'; +import { isElement } from '../../_base/reactUtils'; import Label from '../label'; -import {Col} from '../../grid'; -import type {CallOpts, WithFieldOption} from '@douyinfe/semi-foundation/form/interface'; -import type {CommonexcludeType, CommonFieldProps} from '../interface'; -import type {Subtract} from 'utility-types'; +import { Col } from '../../grid'; +import type { CallOpts, WithFieldOption } from '@douyinfe/semi-foundation/form/interface'; +import type { CommonexcludeType, CommonFieldProps } from '../interface'; +import type { Subtract } from 'utility-types'; import { - ComponentObjectPropsOptions, + type ComponentObjectPropsOptions, defineComponent, - DefineComponent, + type DefineComponent, + type DefineSetupFnComponent, Fragment, - FunctionalComponent, + type FunctionalComponent, h, onBeforeMount, onMounted, - Ref, + type Ref, ref, shallowRef, unref, @@ -38,9 +39,9 @@ import { watch, withMemo, } from 'vue'; -import {VueHTMLAttributes} from '../../interface'; -import {useFormUpdaterContext} from '../context/FormUpdaterContext/Consumer'; -import {omit} from "lodash"; +import { VueHTMLAttributes } from '../../interface'; +import { useFormUpdaterContext } from '../context/FormUpdaterContext/Consumer'; +import { omit } from 'lodash'; const prefix = cssClasses.PREFIX; @@ -58,734 +59,743 @@ const useIsomorphicEffect = typeof window !== 'undefined' ? onBeforeMount : onMo function withField< C, T extends Subtract & CommonFieldProps & VueHTMLAttributes & C ->(Component: DefineComponent | FunctionalComponent, opts?: WithFieldOption, vuePropsType?: ComponentObjectPropsOptions): DefineComponent { - - // @ts-ignore +>( + Component: DefineSetupFnComponent | ((props: C) => any), + opts?: WithFieldOption, + vuePropsType?: ComponentObjectPropsOptions +): DefineSetupFnComponent { + //@ts-ignore const SemiField = defineComponent((truthProps, { attrs: props }) => { - const slots = useSlots(); - - // grab formUpdater (the api for field to read/modify FormState) from context - const { context: updater } = useFormUpdaterContext(); - // use arrayFieldState to fix issue 615 - let { context: arrayFieldState } = useArrayFieldState(); + const slots = useSlots(); + + // grab formUpdater (the api for field to read/modify FormState) from context + const { context: updater } = useFormUpdaterContext(); + // use arrayFieldState to fix issue 615 + let { context: arrayFieldState } = useArrayFieldState(); + + // To prevent user forgetting to pass the field, use undefined as the key, and updater.value.getValue will get the wrong value. + let initValueInFormOpts = + typeof mergeProps({ ...props, ...truthProps }).field !== 'undefined' + ? updater.value.getValue(mergeProps({ ...props, ...truthProps }).field) + : undefined; // Get the init value of form from formP rops.init Values Get the initial value set in the initValues of Form + let initVal = + typeof mergeProps({ ...props, ...truthProps }).initValue !== 'undefined' + ? mergeProps({ ...props, ...truthProps }).initValue + : initValueInFormOpts; + try { + if (arrayFieldState.value) { + initVal = + arrayFieldState.value.shouldUseInitValue && + typeof mergeProps({ ...props, ...truthProps }).initValue !== 'undefined' + ? mergeProps({ ...props, ...truthProps }).initValue + : initValueInFormOpts; + } + } catch (err) {} + // FIXME typeof initVal + const [value, setValue, getVal] = useStateWithGetter(typeof initVal !== undefined ? initVal : null); + + // watch([()=>props.field, ()=>props.initValue], ()=>{ + // let initValueInFormOpts = typeof mergeProps({...props, ...truthProps}).field !== 'undefined' ? updater.value.getValue(mergeProps({...props, ...truthProps}).field) : undefined; // Get the init value of form from formP rops.init Values Get the initial value set in the initValues of Form + // let initVal = typeof mergeProps({...props, ...truthProps}).initValue !== 'undefined' ? mergeProps({...props, ...truthProps}).initValue : initValueInFormOpts; + // setValue(typeof initVal !== undefined ? initVal : null) + // }) + + const rulesRef: Ref = ref(mergeProps({ ...props, ...truthProps }).rules); + const validateRef: Ref = ref(truthProps.validate); + const validatePromise = shallowRef | null>(null); + + // notNotify is true means that the onChange of the Form does not need to be triggered + // notUpdate is true means that this operation does not need to trigger the forceUpdate + const updateTouched = (isTouched: boolean, callOpts?: CallOpts) => { + let { field } = mergeProps({ ...props, ...truthProps }); + setTouched(isTouched); + updater.value.updateStateTouched(field, isTouched, callOpts); + }; - // To prevent user forgetting to pass the field, use undefined as the key, and updater.value.getValue will get the wrong value. - let initValueInFormOpts = - typeof mergeProps({...props, ...truthProps}).field !== 'undefined' ? updater.value.getValue(mergeProps({...props, ...truthProps}).field) : undefined; // Get the init value of form from formP rops.init Values Get the initial value set in the initValues of Form - let initVal = - typeof mergeProps({...props, ...truthProps}).initValue !== 'undefined' ? mergeProps({...props, ...truthProps}).initValue : initValueInFormOpts; + const updateError = (errors: any, callOpts?: CallOpts) => { + let { field } = mergeProps({ ...props, ...truthProps }); + if (errors === getError()) { + // When the inspection result is unchanged, no need to update, saving a forceUpdate overhead + // When errors is an array, deepEqual is not used, and it is always treated as a need to update + // 检验结果不变时,无需更新,节省一次forceUpdate开销 + // errors为数组时,不做deepEqual,始终当做需要更新处理 + return; + } + setError(errors); + updater.value.updateStateError(field, errors, callOpts); + if (!isValid(errors)) { + setStatus('error'); + } else { + setStatus('success'); + } + }; - try { - if (arrayFieldState.value) { - initVal = - arrayFieldState.value.shouldUseInitValue && typeof mergeProps({...props, ...truthProps}).initValue !== 'undefined' - ? mergeProps({...props, ...truthProps}).initValue - : initValueInFormOpts; - } - } catch (err) {} - - // FIXME typeof initVal - const [value, setValue, getVal] = useStateWithGetter(typeof initVal !== undefined ? initVal : null); - - // watch([()=>props.field, ()=>props.initValue], ()=>{ - // let initValueInFormOpts = typeof mergeProps({...props, ...truthProps}).field !== 'undefined' ? updater.value.getValue(mergeProps({...props, ...truthProps}).field) : undefined; // Get the init value of form from formP rops.init Values Get the initial value set in the initValues of Form - // let initVal = typeof mergeProps({...props, ...truthProps}).initValue !== 'undefined' ? mergeProps({...props, ...truthProps}).initValue : initValueInFormOpts; - // setValue(typeof initVal !== undefined ? initVal : null) - // }) - - const rulesRef: Ref = ref(mergeProps({...props, ...truthProps}).rules); - const validateRef: Ref = ref(truthProps.validate); - const validatePromise = shallowRef | null>(null); - - // notNotify is true means that the onChange of the Form does not need to be triggered - // notUpdate is true means that this operation does not need to trigger the forceUpdate - const updateTouched = (isTouched: boolean, callOpts?: CallOpts) => { - let { field } = mergeProps({...props, ...truthProps}); - setTouched(isTouched); - updater.value.updateStateTouched(field, isTouched, callOpts); - }; - - const updateError = (errors: any, callOpts?: CallOpts) => { - let { field } = mergeProps({...props, ...truthProps}); - if (errors === getError()) { - // When the inspection result is unchanged, no need to update, saving a forceUpdate overhead - // When errors is an array, deepEqual is not used, and it is always treated as a need to update - // 检验结果不变时,无需更新,节省一次forceUpdate开销 - // errors为数组时,不做deepEqual,始终当做需要更新处理 - return; - } - setError(errors); - updater.value.updateStateError(field, errors, callOpts); - if (!isValid(errors)) { - setStatus('error'); - } else { - setStatus('success'); + function getAllowEmpty(allowEmpty) { + return allowEmpty || updater.value.getFormProps().allowEmpty; } - }; - function getAllowEmpty(allowEmpty) { - return allowEmpty || updater.value.getFormProps().allowEmpty; - } - const updateValue = (val: any, callOpts?: CallOpts) => { - let { field, allowEmpty } = mergeProps({...props, ...truthProps}); - allowEmpty = getAllowEmpty(allowEmpty); - setValue(val); - let newOpts = { - ...callOpts, - allowEmpty, - }; - updater.value.updateStateValue(field, val, newOpts); - // truthProps['onUpdate:modelValue']?.(val) - }; - - const reset = () => { - let callOpts = { - notNotify: true, - notUpdate: true, + const updateValue = (val: any, callOpts?: CallOpts) => { + let { field, allowEmpty } = mergeProps({ ...props, ...truthProps }); + allowEmpty = getAllowEmpty(allowEmpty); + setValue(val); + let newOpts = { + ...callOpts, + allowEmpty, + }; + updater.value.updateStateValue(field, val, newOpts); + // truthProps['onUpdate:modelValue']?.(val) }; - // reset is called by the FormFoundaion uniformly. The field level does not need to trigger notify and update. - updateValue(initVal !== null ? initVal : undefined, callOpts); - updateError(undefined, callOpts); - updateTouched(undefined, callOpts); - setStatus('default'); - }; - - // Execute the validation rules specified by rules - const _validateInternal = (val: any, callOpts: CallOpts) => { - let latestRules = rulesRef.value || []; - const validator = generateValidatesFromRules(mergeProps({...props, ...truthProps}).field, latestRules); - const model = { - [mergeProps({...props, ...truthProps}).field]: val, + + const reset = () => { + let callOpts = { + notNotify: true, + notUpdate: true, + }; + // reset is called by the FormFoundaion uniformly. The field level does not need to trigger notify and update. + updateValue(initVal !== null ? initVal : undefined, callOpts); + updateError(undefined, callOpts); + updateTouched(undefined, callOpts); + setStatus('default'); }; + // Execute the validation rules specified by rules + const _validateInternal = (val: any, callOpts: CallOpts) => { + let latestRules = rulesRef.value || []; + const validator = generateValidatesFromRules(mergeProps({ ...props, ...truthProps }).field, latestRules); + const model = { + [mergeProps({ ...props, ...truthProps }).field]: val, + }; - let { - stopValidateWithError, - } = mergeProps({...props, ...truthProps}); - let formProps = updater.value.getFormProps([ - 'labelPosition', - 'labelWidth', - 'labelAlign', - 'labelCol', - 'wrapperCol', - 'disabled', - 'showValidateIcon', - 'extraTextPosition', - 'stopValidateWithError', - 'trigger' - ]); - let mergeStopValidateWithError = transformDefaultBooleanAPI(stopValidateWithError, formProps.stopValidateWithError, false); - - - const rootPromise = new Promise((resolve, reject) => { - validator - .validate( - model, - { - first: mergeStopValidateWithError, - }, - // eslint-disable-next-line @typescript-eslint/no-empty-function - (errors, fields) => {} - ) - .then((res) => { - if (validatePromise.value !== rootPromise) { - return; - } - // validation passed - setStatus('success'); - updateError(undefined, callOpts); - resolve({}); - }) - .catch((err) => { - if (validatePromise.value !== rootPromise) { - return; - } + let { stopValidateWithError } = mergeProps({ ...props, ...truthProps }); + let formProps = updater.value.getFormProps([ + 'labelPosition', + 'labelWidth', + 'labelAlign', + 'labelCol', + 'wrapperCol', + 'disabled', + 'showValidateIcon', + 'extraTextPosition', + 'stopValidateWithError', + 'trigger', + ]); + let mergeStopValidateWithError = transformDefaultBooleanAPI( + stopValidateWithError, + formProps.stopValidateWithError, + false + ); - let { errors, fields } = err; - if (errors && fields) { - let messages = errors.map((e: any) => e.message); - if (messages.length === 1) { - // eslint-disable-next-line prefer-destructuring - messages = messages[0]; + const rootPromise = new Promise((resolve, reject) => { + validator + .validate( + model, + { + first: mergeStopValidateWithError, + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + (errors, fields) => {} + ) + .then((res) => { + if (validatePromise.value !== rootPromise) { + return; + } + // validation passed + setStatus('success'); + updateError(undefined, callOpts); + resolve({}); + }) + .catch((err) => { + if (validatePromise.value !== rootPromise) { + return; } - updateError(messages, callOpts); - if (!isValid(messages)) { + + let { errors, fields } = err; + if (errors && fields) { + let messages = errors.map((e: any) => e.message); + if (messages.length === 1) { + // eslint-disable-next-line prefer-destructuring + messages = messages[0]; + } + updateError(messages, callOpts); + if (!isValid(messages)) { + setStatus('error'); + resolve(errors); + } + } else { + // Some grammatical errors in rules setStatus('error'); - resolve(errors); + updateError(err.message, callOpts); + resolve(err.message); + throw err; } - } else { - // Some grammatical errors in rules - setStatus('error'); - updateError(err.message, callOpts); - resolve(err.message); - throw err; - } - }); - }); - validatePromise.value = rootPromise; + }); + }); + validatePromise.value = rootPromise; - return rootPromise; - }; + return rootPromise; + }; - // execute custom validate function - const _validate = (val: any, values: any, callOpts: CallOpts) => { - const rootPromise = new Promise((resolve) => { - let maybePromisedErrors; - // let errorThrowSync; - try { - maybePromisedErrors = validateRef.value(val, values); - } catch (err) { - // error throw by syncValidate - maybePromisedErrors = err; - } - if (maybePromisedErrors === undefined) { - resolve({}); - updateError(undefined, callOpts); - } else if (isPromise(maybePromisedErrors)) { - maybePromisedErrors.then((result: any) => { - // If the async validate is outdated (a newer validate occurs), the result should be discarded - if (validatePromise.value !== rootPromise) { - return; - } + // execute custom validate function + const _validate = (val: any, values: any, callOpts: CallOpts) => { + const rootPromise = new Promise((resolve) => { + let maybePromisedErrors; + // let errorThrowSync; + try { + maybePromisedErrors = validateRef.value(val, values); + } catch (err) { + // error throw by syncValidate + maybePromisedErrors = err; + } + if (maybePromisedErrors === undefined) { + resolve({}); + updateError(undefined, callOpts); + } else if (isPromise(maybePromisedErrors)) { + maybePromisedErrors.then((result: any) => { + // If the async validate is outdated (a newer validate occurs), the result should be discarded + if (validatePromise.value !== rootPromise) { + return; + } - if (isValid(result)) { - // validate success,no need to do anything with result + if (isValid(result)) { + // validate success,no need to do anything with result + updateError(undefined, callOpts); + resolve(null); + } else { + // validate failed + updateError(result, callOpts); + resolve(result); + } + }); + } else { + if (isValid(maybePromisedErrors)) { updateError(undefined, callOpts); resolve(null); } else { - // validate failed - updateError(result, callOpts); - resolve(result); + updateError(maybePromisedErrors, callOpts); + resolve(maybePromisedErrors); } - }); - } else { - if (isValid(maybePromisedErrors)) { - updateError(undefined, callOpts); - resolve(null); - } else { - updateError(maybePromisedErrors, callOpts); - resolve(maybePromisedErrors); } - } - }); + }); - validatePromise.value = rootPromise; + validatePromise.value = rootPromise; - return rootPromise; - }; + return rootPromise; + }; - const fieldValidate = (val: any, callOpts?: CallOpts) => { - let finalVal = val; - let latestRules = rulesRef.value; - if (mergeProps({...props, ...truthProps}).transform) { - finalVal = mergeProps({...props, ...truthProps}).transform(val); - } - if (validateRef.value) { - return _validate(finalVal, updater.value.getValue(), callOpts); - } else if (latestRules) { - return _validateInternal(finalVal, callOpts); - } - return null; - }; - - /** - * parse / format - * validate when trigger - * - */ - const handleChange = (newValue: any, e: any, ...other: any[]) => { - // 不明来源事件触发过滤 - if (newValue && newValue[Symbol.toStringTag] && newValue[Symbol.toStringTag] === 'Event') { - console.trace('不明来源事件触发过滤', newValue); - return; - } + const fieldValidate = (val: any, callOpts?: CallOpts) => { + let finalVal = val; + let latestRules = rulesRef.value; + if (mergeProps({ ...props, ...truthProps }).transform) { + finalVal = mergeProps({ ...props, ...truthProps }).transform(val); + } + if (validateRef.value) { + return _validate(finalVal, updater.value.getValue(), callOpts); + } else if (latestRules) { + return _validateInternal(finalVal, callOpts); + } + return null; + }; - let { trigger, emptyValue } = mergeProps({...props, ...truthProps}); - let { allowEmptyString, allowEmpty } = mergeProps({...props, ...truthProps}); - allowEmpty = getAllowEmpty(allowEmpty); - let { options, shouldInject } = mergeOptions(opts, props); - let fnKey = options.onKeyChangeFnName; - if (fnKey in props && typeof props[options.onKeyChangeFnName] === 'function') { - // @ts-ignore - props[options.onKeyChangeFnName](newValue, e, ...other); - } + /** + * parse / format + * validate when trigger + * + */ + const handleChange = (newValue: any, e: any, ...other: any[]) => { + // 不明来源事件触发过滤 + if (newValue && newValue[Symbol.toStringTag] && newValue[Symbol.toStringTag] === 'Event') { + console.trace('不明来源事件触发过滤', newValue); + return; + } - // support various type component - let val; - if (!options.valuePath) { - val = newValue; - } else { - val = ObjectUtil.get(newValue, options.valuePath); - } + let { trigger, emptyValue } = mergeProps({ ...props, ...truthProps }); + let { allowEmptyString, allowEmpty } = mergeProps({ ...props, ...truthProps }); + allowEmpty = getAllowEmpty(allowEmpty); + let { options, shouldInject } = mergeOptions(opts, props); + let fnKey = options.onKeyChangeFnName; + if (fnKey in props && typeof props[options.onKeyChangeFnName] === 'function') { + // @ts-ignore + props[options.onKeyChangeFnName](newValue, e, ...other); + } - // User can use convert function to updateValue before Component UI render - if (typeof mergeProps({...props, ...truthProps}).convert === 'function') { - val = mergeProps({...props, ...truthProps}).convert(val); - } + // support various type component + let val; + if (!options.valuePath) { + val = newValue; + } else { + val = ObjectUtil.get(newValue, options.valuePath); + } - // TODO: allowEmptyString split into allowEmpty, emptyValue - // Added abandonment warning - // if (process.env.NODE_ENV !== 'production') { - // warning(allowEmptyString, `'allowEmptyString' will be de deprecated in next version, please replace with 'allowEmpty' & 'emptyValue' - // `) - // } - - // set value to undefined if it's an empty string - // allowEmptyString={true} is equivalent to allowEmpty = {true} emptyValue = " - if (allowEmptyString || allowEmpty) { - if (val === '') { - // do nothing + // User can use convert function to updateValue before Component UI render + if (typeof mergeProps({ ...props, ...truthProps }).convert === 'function') { + val = mergeProps({ ...props, ...truthProps }).convert(val); } - } else { - if (val === emptyValue) { - val = undefined; + + // TODO: allowEmptyString split into allowEmpty, emptyValue + // Added abandonment warning + // if (process.env.NODE_ENV !== 'production') { + // warning(allowEmptyString, `'allowEmptyString' will be de deprecated in next version, please replace with 'allowEmpty' & 'emptyValue' + // `) + // } + + // set value to undefined if it's an empty string + // allowEmptyString={true} is equivalent to allowEmpty = {true} emptyValue = " + if (allowEmptyString || allowEmpty) { + if (val === '') { + // do nothing + } + } else { + if (val === emptyValue) { + val = undefined; + } } - } - // maintain compoent cursor if needed - try { - if (e && e.target && e.target.selectionStart) { - setCursor(e.target.selectionStart); + // maintain compoent cursor if needed + try { + if (e && e.target && e.target.selectionStart) { + setCursor(e.target.selectionStart); + } + } catch (err) {} + + updateTouched(true, { notNotify: true, notUpdate: true }); + updateValue(val); + + let formProps = updater.value.getFormProps([ + 'labelPosition', + 'labelWidth', + 'labelAlign', + 'labelCol', + 'wrapperCol', + 'disabled', + 'showValidateIcon', + 'extraTextPosition', + 'stopValidateWithError', + 'trigger', + ]); + let mergeTrigger = transformTrigger(trigger, formProps.trigger); + // only validate when trigger includes change + if (mergeTrigger.includes('change')) { + fieldValidate(val); } - } catch (err) {} + }; - updateTouched(true, { notNotify: true, notUpdate: true }); - updateValue(val); - - - let formProps = updater.value.getFormProps([ - 'labelPosition', - 'labelWidth', - 'labelAlign', - 'labelCol', - 'wrapperCol', - 'disabled', - 'showValidateIcon', - 'extraTextPosition', - 'stopValidateWithError', - 'trigger' - ]); - let mergeTrigger = transformTrigger(trigger, formProps.trigger); - // only validate when trigger includes change - if (mergeTrigger.includes('change')) { - fieldValidate(val); - } - }; - - const handleBlur = (e: FocusEvent) => { - - let { - trigger, - } = mergeProps({...props, ...truthProps}); - let formProps = updater.value.getFormProps([ - 'labelPosition', - 'labelWidth', - 'labelAlign', - 'labelCol', - 'wrapperCol', - 'disabled', - 'showValidateIcon', - 'extraTextPosition', - 'stopValidateWithError', - 'trigger' - ]); - let mergeTrigger = transformTrigger(trigger, formProps.trigger); - - if (props.onBlur) { - // @ts-ignore - props.onBlur(e); - } - if (!touched) { - updateTouched(true); - } - if (mergeTrigger.includes('blur')) { - let val = getVal(); - fieldValidate(val); - } - }; + const handleBlur = (e: FocusEvent) => { + let { trigger } = mergeProps({ ...props, ...truthProps }); + let formProps = updater.value.getFormProps([ + 'labelPosition', + 'labelWidth', + 'labelAlign', + 'labelCol', + 'wrapperCol', + 'disabled', + 'showValidateIcon', + 'extraTextPosition', + 'stopValidateWithError', + 'trigger', + ]); + let mergeTrigger = transformTrigger(trigger, formProps.trigger); + + if (props.onBlur) { + // @ts-ignore + props.onBlur(e); + } + if (!touched) { + updateTouched(true); + } + if (mergeTrigger.includes('blur')) { + let val = getVal(); + fieldValidate(val); + } + }; - // grab formState from context - const formState = useFormState(); + // grab formState from context + const formState = useFormState(); - // Error information: Array, String, undefined - const [error, setError, getError] = useStateWithGetter(); - const touched = ref(); - function setTouched(val) { - touched.value = val; - } - const [cursor, setCursor, getCursor] = useStateWithGetter(0); - const status = ref(mergeProps({...props, ...truthProps}).validateStatus); // use props.validateStatus to init - function setStatus(val) { - status.value = val; - } + // Error information: Array, String, undefined + const [error, setError, getError] = useStateWithGetter(); + const touched = ref(); - // avoid hooks capture value, fixed issue 346 - watch( - [() => truthProps.rules, () => truthProps.validate], - () => { - rulesRef.value = mergeProps({...props, ...truthProps}).rules; - validateRef.value = truthProps.validate; - }, - { immediate: true } - ); - - // exec validate once when trigger inlcude 'mount' - useIsomorphicEffect(() => { - let { - trigger, - } = mergeProps({...props, ...truthProps}); - let formProps = updater.value.getFormProps([ - 'labelPosition', - 'labelWidth', - 'labelAlign', - 'labelCol', - 'wrapperCol', - 'disabled', - 'showValidateIcon', - 'extraTextPosition', - 'stopValidateWithError', - 'trigger' - ]); - let mergeTrigger = transformTrigger(trigger, formProps.trigger); - - const validateOnMount = mergeTrigger.includes('mount'); - if (validateOnMount) { - fieldValidate(value); + function setTouched(val) { + touched.value = val; } - // eslint-disable-next-line react-hooks/exhaustive-deps - }); - watch( - () => truthProps.field, - (value, oldValue, onCleanup) => { - let { - // condition, - field, - allowEmptyString, - allowEmpty, - keepState, - } = mergeProps({...props, ...truthProps}); - allowEmpty = getAllowEmpty(allowEmpty); - /** Field level maintains a separate layer of data, which is convenient for Form to control Field to update the UI */ - // The field level maintains a separate layer of data, which is convenient for the Form to control the Field for UI updates. - const fieldApi = { - setValue: updateValue, - setTouched: updateTouched, - setError: updateError, - reset, - validate: fieldValidate, - }; + const [cursor, setCursor, getCursor] = useStateWithGetter(0); + const status = ref(mergeProps({ ...props, ...truthProps }).validateStatus); // use props.validateStatus to init + function setStatus(val) { + status.value = val; + } + + // avoid hooks capture value, fixed issue 346 + watch( + [() => truthProps.rules, () => truthProps.validate], + () => { + rulesRef.value = mergeProps({ ...props, ...truthProps }).rules; + validateRef.value = truthProps.validate; + }, + { immediate: true } + ); - // register - if (typeof field === 'undefined') { - // eslint-disable-next-line @typescript-eslint/no-empty-function - return () => {}; + // exec validate once when trigger inlcude 'mount' + useIsomorphicEffect(() => { + let { trigger } = mergeProps({ ...props, ...truthProps }); + let formProps = updater.value.getFormProps([ + 'labelPosition', + 'labelWidth', + 'labelAlign', + 'labelCol', + 'wrapperCol', + 'disabled', + 'showValidateIcon', + 'extraTextPosition', + 'stopValidateWithError', + 'trigger', + ]); + let mergeTrigger = transformTrigger(trigger, formProps.trigger); + + const validateOnMount = mergeTrigger.includes('mount'); + if (validateOnMount) { + fieldValidate(value); } - // log('register: ' + field); + // eslint-disable-next-line react-hooks/exhaustive-deps + }); - // field value may change after field component mounted, we use ref value here to get changed value - const refValue = getVal(); - updater.value.register( - field, - { - value: refValue, - error: error.value, - touched, - status: status.value, - }, - { + watch( + () => truthProps.field, + (value, oldValue, onCleanup) => { + let { + // condition, field, - fieldApi, + allowEmptyString, + allowEmpty, keepState, - allowEmpty: allowEmpty || allowEmptyString, + } = mergeProps({ ...props, ...truthProps }); + allowEmpty = getAllowEmpty(allowEmpty); + /** Field level maintains a separate layer of data, which is convenient for Form to control Field to update the UI */ + // The field level maintains a separate layer of data, which is convenient for the Form to control the Field for UI updates. + const fieldApi = { + setValue: updateValue, + setTouched: updateTouched, + setError: updateError, + reset, + validate: fieldValidate, + }; + + // register + if (typeof field === 'undefined') { + // eslint-disable-next-line @typescript-eslint/no-empty-function + return () => {}; } - ); - // return unRegister cb - - // eslint-disable-next-line react-hooks/exhaustive-deps - onCleanup(() => { - updater.value.unRegister(mergeProps({...props, ...truthProps}).field); - }); - }, - { immediate: true } - ); - - return (_ctx, _cache) => { - const label = truthProps.label; - const id = truthProps.id - let { - // condition, - field, - labelPosition, - labelWidth, - labelAlign, - labelCol, - wrapperCol, - noLabel, - noErrorMessage, - isInInputGroup, - initValue, - validate, - validateStatus, - trigger, - allowEmptyString, - allowEmpty, - emptyValue, - rules, - required, - keepState, - transform, - name, - fieldClassName, - fieldStyle, - convert, - stopValidateWithError, - helpText, - extraText, - extraTextPosition, - pure, - rest: rest_, - } = mergeProps({...props, ...truthProps}); - - const rest = truthProps.prefix?{...rest_,prefix: truthProps.prefix}:rest_ - let { options, shouldInject } = mergeOptions(opts, props); - - warning( - typeof field === 'undefined' && options.shouldInject, - "[Semi Form]: 'field' is required, please check your props of Field Component" - ); + // log('register: ' + field); - // 无需注入的直接返回,eg:Group内的checkbox、radio - // Return without injection, eg: / inside CheckboxGroup/RadioGroup - if (!shouldInject) { - return {{ default: slots.default }}; - } - - if (!updater.value.getFormProps) { - warning(true, '[Semi Form]: Field Component must be use inside the Form, please check your dom declaration'); - return null; - } + // field value may change after field component mounted, we use ref value here to get changed value + const refValue = getVal(); + updater.value.register( + field, + { + value: refValue, + error: error.value, + touched, + status: status.value, + }, + { + field, + fieldApi, + keepState, + allowEmpty: allowEmpty || allowEmptyString, + } + ); + // return unRegister cb - const fieldState = { - value: value.value, - error: error.value, - touched: touched.value, - status: status.value, - }; + // eslint-disable-next-line react-hooks/exhaustive-deps + onCleanup(() => { + updater.value.unRegister(mergeProps({ ...props, ...truthProps }).field); + }); + }, + { immediate: true } + ); - let formProps = updater.value.getFormProps([ - 'labelPosition', - 'labelWidth', - 'labelAlign', - 'labelCol', - 'wrapperCol', - 'disabled', - 'showValidateIcon', - 'extraTextPosition', - ]); - let mergeLabelPos = labelPosition || formProps.labelPosition; - let mergeLabelWidth = labelWidth || formProps.labelWidth; - let mergeLabelAlign = labelAlign || formProps.labelAlign; - let mergeLabelCol = labelCol || formProps.labelCol; - let mergeWrapperCol = wrapperCol || formProps.wrapperCol; - let mergeExtraPos = extraTextPosition || formProps.extraTextPosition || 'bottom'; - - // id attribute to improve a11y - const a11yId = id ? id : field; - const labelId = `${a11yId}-label`; - const helpTextId = `${a11yId}-helpText`; - const extraTextId = `${a11yId}-extraText`; - const errorMessageId = `${a11yId}-errormessage`; - - // prefer to use validateStatus which pass by user throught props - let blockStatus = validateStatus ? validateStatus : status.value; - - const extraCls = classNames(`${prefix}-field-extra`, { - [`${prefix}-field-extra-string`]: typeof extraText === 'string', - [`${prefix}-field-extra-middle`]: mergeExtraPos === 'middle', - [`${prefix}-field-extra-botttom`]: mergeExtraPos === 'bottom', - }); + return (_ctx, _cache) => { + const label = truthProps.label; + const id = truthProps.id; + let { + // condition, + field, + labelPosition, + labelWidth, + labelAlign, + labelCol, + wrapperCol, + noLabel, + noErrorMessage, + isInInputGroup, + initValue, + validate, + validateStatus, + trigger, + allowEmptyString, + allowEmpty, + emptyValue, + rules, + required, + keepState, + transform, + name, + fieldClassName, + fieldStyle, + convert, + stopValidateWithError, + helpText, + extraText, + extraTextPosition, + pure, + rest: rest_, + } = mergeProps({ ...props, ...truthProps }); + + const rest = truthProps.prefix ? { ...rest_, prefix: truthProps.prefix } : rest_; + let { options, shouldInject } = mergeOptions(opts, props); + + warning( + typeof field === 'undefined' && options.shouldInject, + "[Semi Form]: 'field' is required, please check your props of Field Component" + ); - const extraContent = extraText ? ( -
- {extraText} -
- ) : null; - - let newProps: Record = { - id: a11yId, - disabled: formProps.disabled, - ...rest, - onBlur: handleBlur, - [options.onKeyChangeFnName]: handleChange, - // value 为Ref 对象 - [options.valueKey]: unref(value), - validateStatus: blockStatus, - 'aria-required': required, - 'aria-labelledby': labelId, - }; + // 无需注入的直接返回,eg:Group内的checkbox、radio + // Return without injection, eg: / inside CheckboxGroup/RadioGroup + if (!shouldInject) { + return {{ default: slots.default }}; + } - if (helpText) { - newProps['aria-describedby'] = extraText ? `${helpTextId} ${extraTextId}` : helpTextId; - } + if (!updater.value.getFormProps) { + warning(true, '[Semi Form]: Field Component must be use inside the Form, please check your dom declaration'); + return null; + } - if (extraText) { - newProps['aria-describedby'] = helpText ? `${helpTextId} ${extraTextId}` : extraTextId; - } + const fieldState = { + value: value.value, + error: error.value, + touched: touched.value, + status: status.value, + }; - if (status.value === 'error') { - newProps['aria-errormessage'] = errorMessageId; - newProps['aria-invalid'] = true; - } + let formProps = updater.value.getFormProps([ + 'labelPosition', + 'labelWidth', + 'labelAlign', + 'labelCol', + 'wrapperCol', + 'disabled', + 'showValidateIcon', + 'extraTextPosition', + ]); + let mergeLabelPos = labelPosition || formProps.labelPosition; + let mergeLabelWidth = labelWidth || formProps.labelWidth; + let mergeLabelAlign = labelAlign || formProps.labelAlign; + let mergeLabelCol = labelCol || formProps.labelCol; + let mergeWrapperCol = wrapperCol || formProps.wrapperCol; + let mergeExtraPos = extraTextPosition || formProps.extraTextPosition || 'bottom'; + + // id attribute to improve a11y + const a11yId = id ? id : field; + const labelId = `${a11yId}-label`; + const helpTextId = `${a11yId}-helpText`; + const extraTextId = `${a11yId}-extraText`; + const errorMessageId = `${a11yId}-errormessage`; + + // prefer to use validateStatus which pass by user throught props + let blockStatus = validateStatus ? validateStatus : status.value; + + const extraCls = classNames(`${prefix}-field-extra`, { + [`${prefix}-field-extra-string`]: typeof extraText === 'string', + [`${prefix}-field-extra-middle`]: mergeExtraPos === 'middle', + [`${prefix}-field-extra-botttom`]: mergeExtraPos === 'bottom', + }); - const fieldCls = classNames({ - [`${prefix}-field`]: true, - [`${prefix}-field-${name}`]: Boolean(name), - [fieldClassName]: Boolean(fieldClassName), - }); - const fieldMaincls = classNames({ - [`${prefix}-field-main`]: true, - }); + const extraContent = extraText ? ( +
+ {extraText} +
+ ) : null; + + let newProps: Record = { + id: a11yId, + disabled: formProps.disabled, + ...rest, + onBlur: handleBlur, + [options.onKeyChangeFnName]: handleChange, + // value 为Ref 对象 + [options.valueKey]: unref(value), + validateStatus: blockStatus, + 'aria-required': required, + 'aria-labelledby': labelId, + }; - if (mergeLabelPos === 'inset' && !noLabel) { - newProps.insetLabel = label || field; - newProps.insetLabelId = labelId; - if (typeof label === 'object' && !isElement(label)) { - // TODO - // @ts-ignore - newProps.insetLabel = label.text; - newProps.insetLabelId = labelId; + if (helpText) { + newProps['aria-describedby'] = extraText ? `${helpTextId} ${extraTextId}` : helpTextId; } - } - // @ts-ignore - const com = {{ default: slots.default }}; + if (extraText) { + newProps['aria-describedby'] = helpText ? `${helpTextId} ${extraTextId}` : extraTextId; + } - // when use in InputGroup, no need to insert