diff --git a/components/components.ts b/components/components.ts index eb8b75c61..bf1ccd2d9 100644 --- a/components/components.ts +++ b/components/components.ts @@ -74,7 +74,7 @@ export type { EmptyProps } from './empty'; export { default as Empty } from './empty'; export type { FormProps, FormItemProps } from './form'; -export { default as Form, FormItem } from './form'; +export { default as Form, FormItem, FormItemRest } from './form'; export { default as Grid } from './grid'; diff --git a/components/form/FormItem.tsx b/components/form/FormItem.tsx index e8060fa2e..e309cb59c 100644 --- a/components/form/FormItem.tsx +++ b/components/form/FormItem.tsx @@ -272,20 +272,25 @@ export default defineComponent({ resetField, }); - useProvideFormItemContext({ - id: fieldId, - onFieldBlur: () => { - if (props.autoLink) { - onFieldBlur(); - } - }, - onFieldChange: () => { - if (props.autoLink) { - onFieldChange(); - } + useProvideFormItemContext( + { + id: fieldId, + onFieldBlur: () => { + if (props.autoLink) { + onFieldBlur(); + } + }, + onFieldChange: () => { + if (props.autoLink) { + onFieldChange(); + } + }, + clearValidate, }, - clearValidate, - }); + computed(() => { + return !!(props.autoLink && formContext.model.value && fieldName.value); + }), + ); let registered = false; watch( fieldName, diff --git a/components/form/FormItemContext.ts b/components/form/FormItemContext.ts index 2b364b2dd..b2010b730 100644 --- a/components/form/FormItemContext.ts +++ b/components/form/FormItemContext.ts @@ -1,5 +1,15 @@ -import type { ComputedRef, InjectionKey } from 'vue'; -import { computed, inject, provide } from 'vue'; +import type { ComputedRef, InjectionKey, ConcreteComponent } from 'vue'; +import { + watch, + computed, + inject, + provide, + ref, + onBeforeUnmount, + getCurrentInstance, + defineComponent, +} from 'vue'; +import devWarning from '../vc-util/devWarning'; export type FormItemContext = { id: ComputedRef; @@ -8,24 +18,88 @@ export type FormItemContext = { clearValidate: () => void; }; -type ContextProps = FormItemContext; +type InternalFormItemContext = { + addFormItemField: (key: Symbol, type: ConcreteComponent) => void; + removeFormItemField: (key: Symbol) => void; +}; + +const ContextKey: InjectionKey = Symbol('ContextProps'); -const ContextKey: InjectionKey = Symbol('ContextProps'); +const InternalContextKey: InjectionKey = Symbol('InternalContextProps'); -export const useProvideFormItemContext = (props: ContextProps) => { +export const useProvideFormItemContext = ( + props: FormItemContext, + useValidation: ComputedRef = computed(() => true), +) => { + const formItemFields = ref(new Map()); + const addFormItemField = (key: Symbol, type: ConcreteComponent) => { + formItemFields.value.set(key, type); + formItemFields.value = new Map(formItemFields.value); + }; + const removeFormItemField = (key: Symbol) => { + formItemFields.value.delete(key); + formItemFields.value = new Map(formItemFields.value); + }; + const instance = getCurrentInstance(); + watch([useValidation, formItemFields], () => { + if (process.env.NODE_ENV !== 'production') { + if (useValidation.value && formItemFields.value.size > 1) { + devWarning( + false, + 'Form.Item', + `FormItem can only collect one field item, you haved set ${[ + ...formItemFields.value.values(), + ] + .map(v => `\`${v.name}\``) + .join(', ')} ${formItemFields.value.size} field items. + You can set not need to be collected fields into \`a-form-item-rest\``, + ); + let cur = instance; + while (cur.parent) { + console.warn('at', cur.type); + cur = cur.parent; + } + } + } + }); provide(ContextKey, props); + provide(InternalContextKey, { + addFormItemField, + removeFormItemField, + }); }; +const defaultContext: FormItemContext = { + id: computed(() => undefined), + onFieldBlur: () => {}, + onFieldChange: () => {}, + clearValidate: () => {}, +}; +const defaultInternalContext: InternalFormItemContext = { + addFormItemField: () => {}, + removeFormItemField: () => {}, +}; export const useInjectFormItemContext = () => { - const defaultContext: ContextProps = { - id: computed(() => undefined), - onFieldBlur: () => {}, - onFieldChange: () => {}, - clearValidate: () => {}, - }; - + const internalContext = inject(InternalContextKey, defaultInternalContext); + const formItemFieldKey = Symbol('FormItemFieldKey'); + const instance = getCurrentInstance(); + internalContext.addFormItemField(formItemFieldKey, instance.type); + onBeforeUnmount(() => { + internalContext.removeFormItemField(formItemFieldKey); + }); // We should prevent the passing of context for children - + provide(InternalContextKey, defaultInternalContext); provide(ContextKey, defaultContext); return inject(ContextKey, defaultContext); }; + +export default defineComponent({ + name: 'AFormItemRest', + setup(_, { slots }) { + provide(InternalContextKey, defaultInternalContext); + provide(ContextKey, defaultContext); + return () => { + return slots.default?.(); + }; + }, +}); diff --git a/components/form/index.en-US.md b/components/form/index.en-US.md index d4ed8ef55..511b6e998 100644 --- a/components/form/index.en-US.md +++ b/components/form/index.en-US.md @@ -116,12 +116,14 @@ The first is to use multiple `a-form-item`: ``` -The second way is to wrap it with a custom component and call `useFormItemContext` in the custom component +The second way is to wrap it with a custom component and call `useFormItemContext` in the custom component, It is equivalent to merging multiple form items into one. ```html