Skip to content

Commit 8e111e6

Browse files
committed
feat: add form item rest
1 parent 648ab4c commit 8e111e6

File tree

7 files changed

+136
-32
lines changed

7 files changed

+136
-32
lines changed

components/components.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export type { EmptyProps } from './empty';
7474
export { default as Empty } from './empty';
7575

7676
export type { FormProps, FormItemProps } from './form';
77-
export { default as Form, FormItem } from './form';
77+
export { default as Form, FormItem, FormItemRest } from './form';
7878

7979
export { default as Grid } from './grid';
8080

components/form/FormItem.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -272,20 +272,25 @@ export default defineComponent({
272272
resetField,
273273
});
274274

275-
useProvideFormItemContext({
276-
id: fieldId,
277-
onFieldBlur: () => {
278-
if (props.autoLink) {
279-
onFieldBlur();
280-
}
281-
},
282-
onFieldChange: () => {
283-
if (props.autoLink) {
284-
onFieldChange();
285-
}
275+
useProvideFormItemContext(
276+
{
277+
id: fieldId,
278+
onFieldBlur: () => {
279+
if (props.autoLink) {
280+
onFieldBlur();
281+
}
282+
},
283+
onFieldChange: () => {
284+
if (props.autoLink) {
285+
onFieldChange();
286+
}
287+
},
288+
clearValidate,
286289
},
287-
clearValidate,
288-
});
290+
computed(() => {
291+
return !!(props.autoLink && formContext.model.value && fieldName.value);
292+
}),
293+
);
289294
let registered = false;
290295
watch(
291296
fieldName,

components/form/FormItemContext.ts

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
import type { ComputedRef, InjectionKey } from 'vue';
2-
import { computed, inject, provide } from 'vue';
1+
import type { ComputedRef, InjectionKey, ConcreteComponent } from 'vue';
2+
import {
3+
watch,
4+
computed,
5+
inject,
6+
provide,
7+
ref,
8+
onBeforeUnmount,
9+
getCurrentInstance,
10+
defineComponent,
11+
} from 'vue';
12+
import devWarning from '../vc-util/devWarning';
313

414
export type FormItemContext = {
515
id: ComputedRef<string>;
@@ -8,24 +18,88 @@ export type FormItemContext = {
818
clearValidate: () => void;
919
};
1020

11-
type ContextProps = FormItemContext;
21+
type InternalFormItemContext = {
22+
addFormItemField: (key: Symbol, type: ConcreteComponent) => void;
23+
removeFormItemField: (key: Symbol) => void;
24+
};
25+
26+
const ContextKey: InjectionKey<FormItemContext> = Symbol('ContextProps');
1227

13-
const ContextKey: InjectionKey<ContextProps> = Symbol('ContextProps');
28+
const InternalContextKey: InjectionKey<InternalFormItemContext> = Symbol('InternalContextProps');
1429

15-
export const useProvideFormItemContext = (props: ContextProps) => {
30+
export const useProvideFormItemContext = (
31+
props: FormItemContext,
32+
useValidation: ComputedRef<boolean> = computed(() => true),
33+
) => {
34+
const formItemFields = ref(new Map<Symbol, ConcreteComponent>());
35+
const addFormItemField = (key: Symbol, type: ConcreteComponent) => {
36+
formItemFields.value.set(key, type);
37+
formItemFields.value = new Map(formItemFields.value);
38+
};
39+
const removeFormItemField = (key: Symbol) => {
40+
formItemFields.value.delete(key);
41+
formItemFields.value = new Map(formItemFields.value);
42+
};
43+
const instance = getCurrentInstance();
44+
watch([useValidation, formItemFields], () => {
45+
if (process.env.NODE_ENV !== 'production') {
46+
if (useValidation.value && formItemFields.value.size > 1) {
47+
devWarning(
48+
false,
49+
'Form.Item',
50+
`FormItem can only collect one field item, you haved set ${[
51+
...formItemFields.value.values(),
52+
]
53+
.map(v => `\`${v.name}\``)
54+
.join(', ')} ${formItemFields.value.size} field items.
55+
You can set not need to be collected fields into \`a-form-item-rest\``,
56+
);
57+
let cur = instance;
58+
while (cur.parent) {
59+
console.warn('at', cur.type);
60+
cur = cur.parent;
61+
}
62+
}
63+
}
64+
});
1665
provide(ContextKey, props);
66+
provide(InternalContextKey, {
67+
addFormItemField,
68+
removeFormItemField,
69+
});
1770
};
1871

72+
const defaultContext: FormItemContext = {
73+
id: computed(() => undefined),
74+
onFieldBlur: () => {},
75+
onFieldChange: () => {},
76+
clearValidate: () => {},
77+
};
78+
const defaultInternalContext: InternalFormItemContext = {
79+
addFormItemField: () => {},
80+
removeFormItemField: () => {},
81+
};
1982
export const useInjectFormItemContext = () => {
20-
const defaultContext: ContextProps = {
21-
id: computed(() => undefined),
22-
onFieldBlur: () => {},
23-
onFieldChange: () => {},
24-
clearValidate: () => {},
25-
};
26-
83+
const internalContext = inject(InternalContextKey, defaultInternalContext);
84+
const formItemFieldKey = Symbol('FormItemFieldKey');
85+
const instance = getCurrentInstance();
86+
internalContext.addFormItemField(formItemFieldKey, instance.type);
87+
onBeforeUnmount(() => {
88+
internalContext.removeFormItemField(formItemFieldKey);
89+
});
2790
// We should prevent the passing of context for children
28-
91+
provide(InternalContextKey, defaultInternalContext);
2992
provide(ContextKey, defaultContext);
3093
return inject(ContextKey, defaultContext);
3194
};
95+
96+
export default defineComponent({
97+
name: 'AFormItemRest',
98+
setup(_, { slots }) {
99+
provide(InternalContextKey, defaultInternalContext);
100+
provide(ContextKey, defaultContext);
101+
return () => {
102+
return slots.default?.();
103+
};
104+
},
105+
});

components/form/index.en-US.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,14 @@ The first is to use multiple `a-form-item`:
116116
</a-form-item>
117117
```
118118

119-
The second way is to wrap it with a custom component and call `useFormItemContext` in the custom component
119+
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.
120120

121121
```html
122122
<script>
123+
// custom component
123124
import { Form } from 'ant-desing-vue';
124125
export default {
126+
name: 'custom-name',
125127
setup() {
126128
const formItemContext = Form.useFormItemContext();
127129
},
@@ -138,6 +140,15 @@ The second way is to wrap it with a custom component and call `useFormItemContex
138140
</a-form-item>
139141
```
140142

143+
Third, the component library provides an `a-form-item-rest` component, which will prevent data collection. You can put form items that do not need to be collected and verified into this component. It is the same as the first This method is very similar, but it does not generate additional dom nodes.
144+
145+
```html
146+
<a-form-item>
147+
<a-input name="a"></a-input>
148+
<a-form-item-rest><a-input name="b"></a-input></a-form-item-rest>
149+
</a-form-item>
150+
```
151+
141152
#### 2.x
142153

143154
Form.Item hijacks the only child element and listens to the `blur` and `change` events to achieve the purpose of automatic verification, so please make sure that the form field is not wrapped by other elements. If there are multiple child elements, only the change of the first child element will be monitored.

components/form/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@ import type { App, Plugin } from 'vue';
22
import Form, { formProps } from './Form';
33
import FormItem, { formItemProps } from './FormItem';
44
import useForm from './useForm';
5-
import { useInjectFormItemContext } from './FormItemContext';
5+
import FormItemRest, { useInjectFormItemContext } from './FormItemContext';
66
export type { Rule, RuleObject } from './interface';
77

88
export type { FormProps } from './Form';
99
export type { FormItemProps } from './FormItem';
1010

1111
Form.useInjectFormItemContext = useInjectFormItemContext;
12+
Form.ItemRest = FormItemRest;
1213
/* istanbul ignore next */
1314
Form.install = function (app: App) {
1415
app.component(Form.name, Form);
1516
app.component(Form.Item.name, Form.Item);
17+
app.component(FormItemRest.name, FormItemRest);
1618
return app;
1719
};
1820

19-
export { FormItem, formItemProps, formProps, useForm, useInjectFormItemContext };
21+
export { FormItem, formItemProps, formProps, FormItemRest, useForm, useInjectFormItemContext };
2022

2123
export default Form as typeof Form &
2224
Plugin & {
2325
readonly Item: typeof Form.Item;
26+
readonly ItemRest: typeof FormItemRest;
2427
readonly useForm: typeof useForm;
2528
readonly useInjectFormItemContext: typeof useInjectFormItemContext;
2629
};

components/form/index.zh-CN.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/ORmcdeaoO/Form.svg
104104
</a-form-item>
105105
```
106106

107-
如上 Form.Item 并不知道需要收集 `name="a"` 还是 `name=`b``,你可以通过如下两种方式去解决此类问题
107+
如上 Form.Item 并不知道需要收集 `name="a"` 还是 `name=`b``,你可以通过如下三种方式去解决此类问题
108108

109109
第一种,使用多个 `a-form-item`:
110110

@@ -115,10 +115,11 @@ cover: https://gw.alipayobjects.com/zos/alicdn/ORmcdeaoO/Form.svg
115115
</a-form-item>
116116
```
117117

118-
第二种,使用自定义组件包裹,并在自定义组件中调用 `useFormItemContext`
118+
第二种,使用自定义组件包裹,并在自定义组件中调用 `useFormItemContext`,相当于把多个表单项合并成了一个
119119

120120
```html
121121
<script>
122+
// 自定义组件
122123
import { Form } from 'ant-desing-vue';
123124
export default {
124125
setup() {
@@ -137,6 +138,15 @@ cover: https://gw.alipayobjects.com/zos/alicdn/ORmcdeaoO/Form.svg
137138
</a-form-item>
138139
```
139140

141+
第三种,组件库提供了一个 `a-form-item-rest` 组件,它会阻止数据的收集,你可以将不需要收集校验的表单项放到这个组件中即可,它和第一种方式很类似,但它不会产生额外的 dom 节点。
142+
143+
```html
144+
<a-form-item>
145+
<a-input name="a"></a-input>
146+
<a-form-item-rest><a-input name="b"></a-input></a-form-item-rest>
147+
</a-form-item>
148+
```
149+
140150
#### 2.x
141151

142152
Form.Item 会对唯一子元素进行劫持,并监听 `blur``change` 事件,来达到自动校验的目的,所以请确保表单域没有其它元素包裹。如果有多个子元素,将只会监听第一个子元素的变化。

tests/__snapshots__/index.test.js.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Array [
4949
"Empty",
5050
"Form",
5151
"FormItem",
52+
"FormItemRest",
5253
"Grid",
5354
"Input",
5455
"InputGroup",

0 commit comments

Comments
 (0)