Skip to content

Commit 6e1f306

Browse files
committed
refactor: form
1 parent c4a61f2 commit 6e1f306

File tree

13 files changed

+281
-388
lines changed

13 files changed

+281
-388
lines changed

components/_util/createContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { inject, provide } from 'vue';
22

3-
function createContext<T>() {
3+
function createContext<T>(defaultValue?: T) {
44
const contextKey = Symbol('contextKey');
55
const useProvide = (props: T) => {
66
provide(contextKey, props);
77
};
88
const useInject = () => {
9-
return inject(contextKey, undefined as T) || ({} as T);
9+
return inject(contextKey, defaultValue as T) || ({} as T);
1010
};
1111
return {
1212
useProvide,

components/calendar/Header.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import Select from '../select';
22
import { Group, Button } from '../radio';
33
import type { CalendarMode } from './generateCalendar';
44
import type { Ref } from 'vue';
5-
import { defineComponent, ref } from 'vue';
5+
import { reactive, watchEffect, defineComponent, ref } from 'vue';
66
import type { Locale } from '../vc-picker/interface';
77
import type { GenerateConfig } from '../vc-picker/generate';
8+
import { FormItemInputContext } from '../form/FormItemContext';
89

910
const YearSelectOffset = 10;
1011
const YearSelectTotal = 20;
@@ -168,6 +169,15 @@ export default defineComponent<CalendarHeaderProps<any>>({
168169
] as any,
169170
setup(_props, { attrs }) {
170171
const divRef = ref<HTMLDivElement>(null);
172+
const formItemInputContext = FormItemInputContext.useInject();
173+
const newFormItemInputContext = reactive({});
174+
FormItemInputContext.useProvide(newFormItemInputContext);
175+
watchEffect(() => {
176+
Object.assign(newFormItemInputContext, formItemInputContext, {
177+
isFormItemInput: false,
178+
});
179+
});
180+
171181
return () => {
172182
const props = { ..._props, ...attrs };
173183
const { prefixCls, fullscreen, mode, onChange, onModeChange } = props;

components/calendar/style/index.less

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
line-height: 18px;
7171
}
7272
}
73+
.@{calendar-picker-prefix-cls}-cell::before {
74+
pointer-events: none;
75+
}
7376
}
7477

7578
// ========================== Full ==========================

components/calendar/style/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import '../../style/index.less';
22
import './index.less';
33

44
// style dependencies
5-
// deps-lint-skip: date-picker
5+
// deps-lint-skip: date-picker, form
66
import '../../select/style';
77
import '../../radio/style';
88
import '../../date-picker/style';

components/form/FormItem.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
HTMLAttributes,
88
} from 'vue';
99
import {
10+
reactive,
1011
watch,
1112
defineComponent,
1213
computed,
@@ -16,6 +17,10 @@ import {
1617
onBeforeUnmount,
1718
toRaw,
1819
} from 'vue';
20+
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
21+
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
22+
import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled';
23+
import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled';
1924
import cloneDeep from 'lodash-es/cloneDeep';
2025
import PropTypes from '../_util/vue-types';
2126
import Row from '../grid/Row';
@@ -33,8 +38,10 @@ import { useInjectForm } from './context';
3338
import FormItemLabel from './FormItemLabel';
3439
import FormItemInput from './FormItemInput';
3540
import type { ValidationRule } from './Form';
36-
import { useProvideFormItemContext } from './FormItemContext';
41+
import type { FormItemStatusContextProps } from './FormItemContext';
42+
import { FormItemInputContext, useProvideFormItemContext } from './FormItemContext';
3743
import useDebounce from './utils/useDebounce';
44+
import classNames from '../_util/classNames';
3845

3946
const ValidateStatuses = tuple('success', 'warning', 'error', 'validating', '');
4047
export type ValidateStatus = typeof ValidateStatuses[number];
@@ -50,6 +57,13 @@ export interface FieldExpose {
5057
validateRules: (options: ValidateOptions) => Promise<void> | Promise<RuleError[]>;
5158
}
5259

60+
const iconMap: { [key: string]: any } = {
61+
success: CheckCircleFilled,
62+
warning: ExclamationCircleFilled,
63+
error: CloseCircleFilled,
64+
validating: LoadingOutlined,
65+
};
66+
5367
function getPropByPath(obj: any, namePathList: any, strict?: boolean) {
5468
let tempObj = obj;
5569

@@ -391,6 +405,30 @@ export default defineComponent({
391405
[`${prefixCls.value}-item-is-validating`]: mergedValidateStatus.value === 'validating',
392406
[`${prefixCls.value}-item-hidden`]: props.hidden,
393407
}));
408+
const formItemInputContext = reactive<FormItemStatusContextProps>({});
409+
FormItemInputContext.useProvide(formItemInputContext);
410+
watchEffect(() => {
411+
let feedbackIcon: any;
412+
if (props.hasFeedback) {
413+
const IconNode = mergedValidateStatus.value && iconMap[mergedValidateStatus.value];
414+
feedbackIcon = IconNode ? (
415+
<span
416+
class={classNames(
417+
`${prefixCls.value}-item-feedback-icon`,
418+
`${prefixCls.value}-item-feedback-icon-${mergedValidateStatus.value}`,
419+
)}
420+
>
421+
<IconNode />
422+
</span>
423+
) : null;
424+
}
425+
Object.assign(formItemInputContext, {
426+
status: mergedValidateStatus.value,
427+
hasFeedback: props.hasFeedback,
428+
feedbackIcon,
429+
isFormItemInput: true,
430+
});
431+
});
394432
return () => {
395433
if (props.noStyle) return slots.default?.();
396434
const help = props.help ?? (slots.help ? filterEmpty(slots.help()) : null);

components/form/FormItemContext.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ComputedRef, InjectionKey, ConcreteComponent } from 'vue';
1+
import type { ComputedRef, InjectionKey, ConcreteComponent, FunctionalComponent } from 'vue';
22
import {
33
watch,
44
computed,
@@ -10,6 +10,8 @@ import {
1010
defineComponent,
1111
} from 'vue';
1212
import devWarning from '../vc-util/devWarning';
13+
import createContext from '../_util/createContext';
14+
import type { ValidateStatus } from './FormItem';
1315

1416
export type FormItemContext = {
1517
id: ComputedRef<string>;
@@ -103,3 +105,17 @@ export default defineComponent({
103105
};
104106
},
105107
});
108+
109+
export interface FormItemStatusContextProps {
110+
isFormItemInput?: boolean;
111+
status?: ValidateStatus;
112+
hasFeedback?: boolean;
113+
feedbackIcon?: any;
114+
}
115+
116+
export const FormItemInputContext = createContext<FormItemStatusContextProps>({});
117+
118+
export const NoFormStatus: FunctionalComponent = (_, { slots }) => {
119+
FormItemInputContext.useProvide({});
120+
return slots.default?.();
121+
};

components/form/FormItemInput.tsx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
2-
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
3-
import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled';
4-
import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled';
5-
61
import type { ColProps } from '../grid/Col';
72
import Col from '../grid/Col';
83
import { useProvideForm, useInjectForm, useProvideFormItemPrefix } from './context';
@@ -27,12 +22,6 @@ export interface FormItemInputProps {
2722
status?: ValidateStatus;
2823
}
2924

30-
const iconMap: { [key: string]: any } = {
31-
success: CheckCircleFilled,
32-
warning: ExclamationCircleFilled,
33-
error: CloseCircleFilled,
34-
validating: LoadingOutlined,
35-
};
3625
const FormItemInput = defineComponent({
3726
slots: ['help', 'extra', 'errors'],
3827
inheritAttrs: false,
@@ -66,8 +55,8 @@ const FormItemInput = defineComponent({
6655
wrapperCol,
6756
help = slots.help?.(),
6857
errors = slots.errors?.(),
69-
hasFeedback,
70-
status,
58+
// hasFeedback,
59+
// status,
7160
extra = slots.extra?.(),
7261
} = props;
7362
const baseClassName = `${prefixCls}-item`;
@@ -78,7 +67,7 @@ const FormItemInput = defineComponent({
7867
const className = classNames(`${baseClassName}-control`, mergedWrapperCol.class);
7968

8069
// Should provides additional icon if `hasFeedback`
81-
const IconNode = status && iconMap[status];
70+
// const IconNode = status && iconMap[status];
8271

8372
return (
8473
<Col
@@ -89,11 +78,11 @@ const FormItemInput = defineComponent({
8978
<>
9079
<div class={`${baseClassName}-control-input`}>
9180
<div class={`${baseClassName}-control-input-content`}>{slots.default?.()}</div>
92-
{hasFeedback && IconNode ? (
81+
{/* {hasFeedback && IconNode ? (
9382
<span class={`${baseClassName}-children-icon`}>
9483
<IconNode />
9584
</span>
96-
) : null}
85+
) : null} */}
9786
</div>
9887
<ErrorList
9988
errors={errors}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<docs>
2+
---
3+
order: 20
4+
title:
5+
zh-CN: 自定义校验
6+
en-US: Customized Validation
7+
---
8+
9+
## zh-CN
10+
11+
我们提供了 `validateStatus` `help` `hasFeedback` 等属性,你可以不通过 Form 自己定义校验的时机和内容。
12+
13+
1. `validateStatus`: 校验状态,可选 'success', 'warning', 'error', 'validating'。
14+
2. `hasFeedback`:用于给输入框添加反馈图标。
15+
3. `help`:设置校验文案。
16+
17+
## en-US
18+
19+
We provide properties like `validateStatus` `help` `hasFeedback` to customize your own validate status and message, without using Form.
20+
21+
1. `validateStatus`: validate status of form components which could be 'success', 'warning', 'error', 'validating'.
22+
2. `hasFeedback`: display feed icon of input control
23+
3. `help`: display validate message.
24+
</docs>
25+
<template>
26+
<a-form v-bind="formItemLayout">
27+
<a-form-item
28+
label="Fail"
29+
validate-status="error"
30+
help="Should be combination of numbers & alphabets"
31+
>
32+
<a-input id="error" placeholder="unavailable choice" />
33+
</a-form-item>
34+
35+
<a-form-item label="Warning" validate-status="warning">
36+
<a-input id="warning" placeholder="Warning">
37+
<template #prefix><smile-outlined /></template>
38+
</a-input>
39+
</a-form-item>
40+
41+
<a-form-item
42+
label="Validating"
43+
has-feedback
44+
validate-status="validating"
45+
help="The information is being validated..."
46+
>
47+
<a-input id="validating" placeholder="I'm the content is being validated" />
48+
</a-form-item>
49+
50+
<a-form-item label="Success" has-feedback validate-status="success">
51+
<a-input id="success" placeholder="I'm the content" />
52+
</a-form-item>
53+
54+
<a-form-item label="Warning" has-feedback validate-status="warning">
55+
<a-input id="warning2" placeholder="Warning" />
56+
</a-form-item>
57+
58+
<a-form-item
59+
label="Fail"
60+
has-feedback
61+
validate-status="error"
62+
help="Should be combination of numbers & alphabets"
63+
>
64+
<a-input id="error2" placeholder="unavailable choice" />
65+
</a-form-item>
66+
67+
<a-form-item label="Success" has-feedback validate-status="success">
68+
<a-date-picker style="width: 100%" />
69+
</a-form-item>
70+
71+
<a-form-item label="Warning" has-feedback validate-status="warning">
72+
<a-time-picker style="width: 100%" />
73+
</a-form-item>
74+
75+
<a-form-item label="Error" has-feedback validate-status="error">
76+
<a-range-picker style="width: 100%" />
77+
</a-form-item>
78+
79+
<a-form-item label="Error" has-feedback validate-status="error">
80+
<a-select placeholder="I'm Select" allow-clear>
81+
<a-select-option value="1">Option 1</a-select-option>
82+
<a-select-option value="2">Option 2</a-select-option>
83+
<a-select-option value="3">Option 3</a-select-option>
84+
</a-select>
85+
</a-form-item>
86+
87+
<a-form-item
88+
label="Validating"
89+
has-feedback
90+
validate-status="error"
91+
help="Something breaks the rule."
92+
>
93+
<a-cascader
94+
placeholder="I'm Cascader"
95+
:options="[{ value: 'xx', label: 'xx' }]"
96+
allow-clear
97+
/>
98+
</a-form-item>
99+
100+
<a-form-item label="Warning" has-feedback validate-status="warning" help="Need to be checked">
101+
<a-tree-select
102+
placeholder="I'm TreeSelect"
103+
:tree-data="[{ value: 'xx', label: 'xx' }]"
104+
allow-clear
105+
/>
106+
</a-form-item>
107+
108+
<a-form-item label="inline" style="margin-bottom: 0px">
109+
<a-form-item
110+
validate-status="error"
111+
help="Please select right date"
112+
style="display: inline-block; width: calc(50% - 12px)"
113+
>
114+
<a-date-picker />
115+
</a-form-item>
116+
<span style="display: inline-block, width: 24px; line-height:32px; text-align: center">
117+
-
118+
</span>
119+
<a-form-item style="display: inline-block; width: calc(50% - 12px)">
120+
<a-date-picker />
121+
</a-form-item>
122+
</a-form-item>
123+
124+
<a-form-item label="Success" has-feedback validate-status="success">
125+
<a-inputNumber style="width: 100%" />
126+
</a-form-item>
127+
128+
<a-form-item label="Success" has-feedback validate-status="success">
129+
<a-input allow-clear placeholder="with allowClear" />
130+
</a-form-item>
131+
132+
<a-form-item label="Warning" has-feedback validate-status="warning">
133+
<a-input-password placeholder="with input password" />
134+
</a-form-item>
135+
136+
<a-form-item label="Error" has-feedback validate-status="error">
137+
<a-input-password allow-clear placeholder="with input password and allowClear" />
138+
</a-form-item>
139+
140+
<a-form-item label="Fail" validate-status="error" has-feedback>
141+
<a-mentions />
142+
</a-form-item>
143+
144+
<a-form-item label="Fail" validate-status="error" has-feedback help="Should have something">
145+
<a-textarea allow-clear show-count />
146+
</a-form-item>
147+
</a-form>
148+
</template>
149+
<script lang="ts">
150+
import { defineComponent } from 'vue';
151+
import { SmileOutlined } from '@ant-design/icons-vue';
152+
export default defineComponent({
153+
components: {
154+
SmileOutlined,
155+
},
156+
setup() {
157+
const formItemLayout = {
158+
labelCol: {
159+
xs: { span: 24 },
160+
sm: { span: 6 },
161+
},
162+
wrapperCol: {
163+
xs: { span: 24 },
164+
sm: { span: 14 },
165+
},
166+
};
167+
return {
168+
formItemLayout,
169+
};
170+
},
171+
});
172+
</script>

0 commit comments

Comments
 (0)