Skip to content

Commit 093fa55

Browse files
committed
feat: input add clearIcon & status
1 parent e9d41ef commit 093fa55

27 files changed

+1084
-591
lines changed

components/_util/createContext.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { inject, provide } from 'vue';
1+
import { inject, provide, reactive, watchEffect } from 'vue';
22

3-
function createContext<T>(defaultValue?: T) {
3+
function createContext<T extends Record<string, any>>(defaultValue?: T) {
44
const contextKey = Symbol('contextKey');
5-
const useProvide = (props: T) => {
6-
provide(contextKey, props);
5+
const useProvide = (props: T, newProps?: T) => {
6+
const mergedProps = reactive<T>({} as T);
7+
provide(contextKey, mergedProps);
8+
watchEffect(() => {
9+
Object.assign(mergedProps, props, newProps || {});
10+
});
11+
return mergedProps;
712
};
813
const useInject = () => {
914
return inject(contextKey, defaultValue as T) || ({} as T);

components/calendar/Header.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Select from '../select';
22
import { Group, Button } from '../radio';
33
import type { CalendarMode } from './generateCalendar';
44
import type { Ref } from 'vue';
5-
import { reactive, watchEffect, defineComponent, ref } from 'vue';
5+
import { defineComponent, ref } from 'vue';
66
import type { Locale } from '../vc-picker/interface';
77
import type { GenerateConfig } from '../vc-picker/generate';
88
import { FormItemInputContext } from '../form/FormItemContext';
@@ -170,13 +170,7 @@ export default defineComponent<CalendarHeaderProps<any>>({
170170
setup(_props, { attrs }) {
171171
const divRef = ref<HTMLDivElement>(null);
172172
const formItemInputContext = FormItemInputContext.useInject();
173-
const newFormItemInputContext = reactive({});
174-
FormItemInputContext.useProvide(newFormItemInputContext);
175-
watchEffect(() => {
176-
Object.assign(newFormItemInputContext, formItemInputContext, {
177-
isFormItemInput: false,
178-
});
179-
});
173+
FormItemInputContext.useProvide(formItemInputContext, { isFormItemInput: false });
180174

181175
return () => {
182176
const props = { ..._props, ...attrs };

components/form/FormItemContext.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ComputedRef, InjectionKey, ConcreteComponent, FunctionalComponent } from 'vue';
1+
import type { ComputedRef, InjectionKey, ConcreteComponent } from 'vue';
22
import {
33
watch,
44
computed,
@@ -115,7 +115,12 @@ export interface FormItemStatusContextProps {
115115

116116
export const FormItemInputContext = createContext<FormItemStatusContextProps>({});
117117

118-
export const NoFormStatus: FunctionalComponent = (_, { slots }) => {
119-
FormItemInputContext.useProvide({});
120-
return slots.default?.();
121-
};
118+
export const NoFormStatus = defineComponent({
119+
name: 'NoFormStatus',
120+
setup(_, { slots }) {
121+
FormItemInputContext.useProvide({});
122+
return () => {
123+
return slots.default?.();
124+
};
125+
},
126+
});

components/input/ClearableLabeledInput.tsx

Lines changed: 19 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
33
import PropTypes from '../_util/vue-types';
44
import { cloneElement } from '../_util/vnode';
55
import type { PropType, VNode } from 'vue';
6-
import { ref, defineComponent } from 'vue';
6+
import { defineComponent } from 'vue';
77
import { tuple } from '../_util/type';
88
import type { Direction, SizeType } from '../config-provider';
99
import type { MouseEventHandler } from '../_util/EventInterface';
10-
import { getInputClassName, hasAddon, hasPrefixSuffix } from './util';
10+
import { hasAddon } from './util';
11+
import { FormItemInputContext } from '../form/FormItemContext';
12+
import type { InputStatus } from '../_util/statusUtils';
13+
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
1114

1215
const ClearableInputType = ['text', 'input'];
1316

@@ -34,20 +37,13 @@ export default defineComponent({
3437
bordered: { type: Boolean, default: true },
3538
triggerFocus: { type: Function as PropType<() => void> },
3639
hidden: Boolean,
40+
status: String as PropType<InputStatus>,
3741
},
3842
setup(props, { slots, attrs }) {
39-
const containerRef = ref();
40-
const onInputMouseUp: MouseEventHandler = e => {
41-
if (containerRef.value?.contains(e.target as Element)) {
42-
const { triggerFocus } = props;
43-
triggerFocus?.();
44-
}
45-
};
43+
const statusContext = FormItemInputContext.useInject();
44+
4645
const renderClearIcon = (prefixCls: string) => {
47-
const { allowClear, value, disabled, readonly, handleReset, suffix = slots.suffix } = props;
48-
if (!allowClear) {
49-
return null;
50-
}
46+
const { value, disabled, readonly, handleReset, suffix = slots.suffix } = props;
5147
const needClear = !disabled && !readonly && value;
5248
const className = `${prefixCls}-clear-icon`;
5349
return (
@@ -66,133 +62,20 @@ export default defineComponent({
6662
/>
6763
);
6864
};
69-
70-
const renderSuffix = (prefixCls: string) => {
71-
const { suffix = slots.suffix?.(), allowClear } = props;
72-
if (suffix || allowClear) {
73-
return (
74-
<span class={`${prefixCls}-suffix`}>
75-
{renderClearIcon(prefixCls)}
76-
{suffix}
77-
</span>
78-
);
79-
}
80-
return null;
81-
};
82-
83-
const renderLabeledIcon = (prefixCls: string, element: VNode) => {
65+
const renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => {
8466
const {
85-
focused,
8667
value,
87-
prefix = slots.prefix?.(),
88-
size,
89-
suffix = slots.suffix?.(),
90-
disabled,
9168
allowClear,
9269
direction,
93-
readonly,
9470
bordered,
9571
hidden,
72+
status: customStatus,
9673
addonAfter = slots.addonAfter,
9774
addonBefore = slots.addonBefore,
9875
} = props;
99-
const suffixNode = renderSuffix(prefixCls);
100-
if (!hasPrefixSuffix({ prefix, suffix, allowClear })) {
101-
return cloneElement(element, {
102-
value,
103-
});
104-
}
105-
106-
const prefixNode = prefix ? <span class={`${prefixCls}-prefix`}>{prefix}</span> : null;
107-
108-
const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, {
109-
[`${prefixCls}-affix-wrapper-focused`]: focused,
110-
[`${prefixCls}-affix-wrapper-disabled`]: disabled,
111-
[`${prefixCls}-affix-wrapper-sm`]: size === 'small',
112-
[`${prefixCls}-affix-wrapper-lg`]: size === 'large',
113-
[`${prefixCls}-affix-wrapper-input-with-clear-btn`]: suffix && allowClear && value,
114-
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
115-
[`${prefixCls}-affix-wrapper-readonly`]: readonly,
116-
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
117-
// className will go to addon wrapper
118-
[`${attrs.class}`]: !hasAddon({ addonAfter, addonBefore }) && attrs.class,
119-
});
120-
return (
121-
<span
122-
ref={containerRef}
123-
class={affixWrapperCls}
124-
style={attrs.style}
125-
onMouseup={onInputMouseUp}
126-
hidden={hidden}
127-
>
128-
{prefixNode}
129-
{cloneElement(element, {
130-
style: null,
131-
value,
132-
class: getInputClassName(prefixCls, bordered, size, disabled),
133-
})}
134-
{suffixNode}
135-
</span>
136-
);
137-
};
138-
139-
const renderInputWithLabel = (prefixCls: string, labeledElement: VNode) => {
140-
const {
141-
addonBefore = slots.addonBefore?.(),
142-
addonAfter = slots.addonAfter?.(),
143-
size,
144-
direction,
145-
hidden,
146-
} = props;
147-
// Not wrap when there is not addons
148-
if (!hasAddon({ addonBefore, addonAfter })) {
149-
return labeledElement;
150-
}
151-
152-
const wrapperClassName = `${prefixCls}-group`;
153-
const addonClassName = `${wrapperClassName}-addon`;
154-
const addonBeforeNode = addonBefore ? (
155-
<span class={addonClassName}>{addonBefore}</span>
156-
) : null;
157-
const addonAfterNode = addonAfter ? <span class={addonClassName}>{addonAfter}</span> : null;
158-
159-
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, {
160-
[`${wrapperClassName}-rtl`]: direction === 'rtl',
161-
});
16276

163-
const mergedGroupClassName = classNames(
164-
`${prefixCls}-group-wrapper`,
165-
{
166-
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
167-
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
168-
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
169-
},
170-
attrs.class,
171-
);
172-
173-
// Need another wrapper for changing display:table to display:inline-block
174-
// and put style prop in wrapper
175-
return (
176-
<span class={mergedGroupClassName} style={attrs.style} hidden={hidden}>
177-
<span class={mergedWrapperClassName}>
178-
{addonBeforeNode}
179-
{cloneElement(labeledElement, { style: null })}
180-
{addonAfterNode}
181-
</span>
182-
</span>
183-
);
184-
};
77+
const { status: contextStatus, hasFeedback } = statusContext;
18578

186-
const renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => {
187-
const {
188-
value,
189-
allowClear,
190-
direction,
191-
bordered,
192-
hidden,
193-
addonAfter = slots.addonAfter,
194-
addonBefore = slots.addonBefore,
195-
} = props;
19679
if (!allowClear) {
19780
return cloneElement(element, {
19881
value,
@@ -201,6 +84,11 @@ export default defineComponent({
20184
const affixWrapperCls = classNames(
20285
`${prefixCls}-affix-wrapper`,
20386
`${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
87+
getStatusClassNames(
88+
`${prefixCls}-affix-wrapper`,
89+
getMergedStatus(contextStatus, customStatus),
90+
hasFeedback,
91+
),
20492
{
20593
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
20694
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
@@ -209,7 +97,7 @@ export default defineComponent({
20997
},
21098
);
21199
return (
212-
<span class={affixWrapperCls} style={attrs.style} hidden={hidden}>
100+
<span className={affixWrapperCls} style={attrs.style} hidden={hidden}>
213101
{cloneElement(element, {
214102
style: null,
215103
value,
@@ -224,7 +112,7 @@ export default defineComponent({
224112
if (inputType === ClearableInputType[0]) {
225113
return renderTextAreaWithClearIcon(prefixCls, element);
226114
}
227-
return renderInputWithLabel(prefixCls, renderLabeledIcon(prefixCls, element));
115+
return null;
228116
};
229117
},
230118
});

components/input/Group.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { PropType } from 'vue';
22
import { computed, defineComponent } from 'vue';
33
import type { SizeType } from '../config-provider';
4+
import { FormItemInputContext } from '../form/FormItemContext';
45
import type { FocusEventHandler, MouseEventHandler } from '../_util/EventInterface';
56
import useConfigInject from '../_util/hooks/useConfigInject';
67

@@ -17,6 +18,10 @@ export default defineComponent({
1718
},
1819
setup(props, { slots }) {
1920
const { prefixCls, direction } = useConfigInject('input-group', props);
21+
const formItemInputContext = FormItemInputContext.useInject();
22+
FormItemInputContext.useProvide(formItemInputContext, {
23+
isFormItemInput: false,
24+
});
2025
const cls = computed(() => {
2126
const pre = prefixCls.value;
2227
return {

0 commit comments

Comments
 (0)