Skip to content

Commit 7ec6831

Browse files
committed
refactor: input
1 parent cd6c6ff commit 7ec6831

File tree

11 files changed

+856
-539
lines changed

11 files changed

+856
-539
lines changed

components/_util/hooks/useConfigInject.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export default (
2424
virtual: ComputedRef<boolean>;
2525
dropdownMatchSelectWidth: ComputedRef<boolean | number>;
2626
getPopupContainer: ComputedRef<ConfigProviderProps['getPopupContainer']>;
27+
getPrefixCls: ConfigProviderProps['getPrefixCls'];
28+
autocomplete: ComputedRef<string>;
2729
} => {
2830
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
2931
'configProvider',
@@ -48,6 +50,7 @@ export default (
4850
() => props.dropdownMatchSelectWidth ?? configProvider.dropdownMatchSelectWidth,
4951
);
5052
const size = computed(() => props.size || configProvider.componentSize);
53+
const autocomplete = computed(() => props.autocomplete || configProvider.input?.autocomplete);
5154
return {
5255
configProvider,
5356
prefixCls,
@@ -63,5 +66,7 @@ export default (
6366
virtual,
6467
dropdownMatchSelectWidth,
6568
rootPrefixCls,
69+
getPrefixCls: configProvider.getPrefixCls,
70+
autocomplete,
6671
};
6772
};

components/_util/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { VueNode } from './type';
22
export const isFunction = val => typeof val === 'function';
3-
3+
export const controlDefaultValue = Symbol('controlDefaultValue') as any;
44
export const isArray = Array.isArray;
55
export const isString = val => typeof val === 'string';
66
export const isSymbol = val => typeof val === 'symbol';

components/button/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default defineComponent({
3535
__ANT_BUTTON: true,
3636
props,
3737
slots: ['icon'],
38-
emits: ['click'],
38+
emits: ['click', 'mousedown'],
3939
setup(props, { slots, attrs, emit }) {
4040
const { prefixCls, autoInsertSpaceInButton, direction } = useConfigInject('btn', props);
4141

components/config-provider/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ export const configProviderProps = {
160160
csp: {
161161
type: Object as PropType<CSPConfig>,
162162
},
163+
input: {
164+
type: Object as PropType<{ autocomplete: string }>,
165+
},
163166
autoInsertSpaceInButton: PropTypes.looseBool,
164167
locale: {
165168
type: Object as PropType<Locale>,

components/input/ClearableLabeledInput.tsx

Lines changed: 133 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@ import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
33
import { getInputClassName } from './Input';
44
import PropTypes from '../_util/vue-types';
55
import { cloneElement } from '../_util/vnode';
6-
import { getComponent } from '../_util/props-util';
7-
import type { VNode } from 'vue';
8-
import { defineComponent } from 'vue';
6+
import type { PropType, VNode } from 'vue';
7+
import { ref, defineComponent } from 'vue';
98
import { tuple } from '../_util/type';
9+
import type { Direction, SizeType } from '../config-provider';
10+
import type { MouseEventHandler } from '../_util/EventInterface';
1011

11-
export function hasPrefixSuffix(instance: any) {
12-
return !!(
13-
getComponent(instance, 'prefix') ||
14-
getComponent(instance, 'suffix') ||
15-
instance.$props.allowClear
16-
);
12+
export function hasPrefixSuffix(propsAndSlots: any) {
13+
return !!(propsAndSlots.prefix || propsAndSlots.suffix || propsAndSlots.allowClear);
14+
}
15+
16+
function hasAddon(propsAndSlots: any) {
17+
return !!(propsAndSlots.addonBefore || propsAndSlots.addonAfter);
1718
}
1819

1920
const ClearableInputType = ['text', 'input'];
2021

21-
const ClearableLabeledInput = defineComponent({
22+
export default defineComponent({
2223
name: 'ClearableLabeledInput',
2324
inheritAttrs: false,
2425
props: {
@@ -27,93 +28,125 @@ const ClearableLabeledInput = defineComponent({
2728
value: PropTypes.any,
2829
defaultValue: PropTypes.any,
2930
allowClear: PropTypes.looseBool,
30-
element: PropTypes.VNodeChild,
31+
element: PropTypes.any,
3132
handleReset: PropTypes.func,
3233
disabled: PropTypes.looseBool,
33-
size: PropTypes.oneOf(tuple('small', 'large', 'default')),
34-
suffix: PropTypes.VNodeChild,
35-
prefix: PropTypes.VNodeChild,
36-
addonBefore: PropTypes.VNodeChild,
37-
addonAfter: PropTypes.VNodeChild,
34+
direction: { type: String as PropType<Direction> },
35+
size: { type: String as PropType<SizeType> },
36+
suffix: PropTypes.any,
37+
prefix: PropTypes.any,
38+
addonBefore: PropTypes.any,
39+
addonAfter: PropTypes.any,
3840
readonly: PropTypes.looseBool,
39-
isFocused: PropTypes.looseBool,
41+
focused: PropTypes.looseBool,
42+
bordered: PropTypes.looseBool,
43+
triggerFocus: { type: Function as PropType<() => void> },
4044
},
41-
methods: {
42-
renderClearIcon(prefixCls: string) {
43-
const { allowClear, value, disabled, readonly, inputType, handleReset } = this.$props;
45+
setup(props, { slots, attrs }) {
46+
const containerRef = ref();
47+
const onInputMouseUp: MouseEventHandler = e => {
48+
if (containerRef.value?.contains(e.target as Element)) {
49+
const { triggerFocus } = props;
50+
triggerFocus?.();
51+
}
52+
};
53+
const renderClearIcon = (prefixCls: string) => {
54+
const { allowClear, value, disabled, readonly, handleReset } = props;
4455
if (!allowClear) {
4556
return null;
4657
}
47-
const showClearIcon =
48-
!disabled && !readonly && value !== undefined && value !== null && value !== '';
49-
const className =
50-
inputType === ClearableInputType[0]
51-
? `${prefixCls}-textarea-clear-icon`
52-
: `${prefixCls}-clear-icon`;
58+
const needClear = !disabled && !readonly && value;
59+
const className = `${prefixCls}-clear-icon`;
5360
return (
5461
<CloseCircleFilled
5562
onClick={handleReset}
56-
class={classNames(className, {
57-
[`${className}-hidden`]: !showClearIcon,
58-
})}
63+
class={classNames(
64+
{
65+
[`${className}-hidden`]: !needClear,
66+
},
67+
className,
68+
)}
5969
role="button"
6070
/>
6171
);
62-
},
72+
};
6373

64-
renderSuffix(prefixCls: string) {
65-
const { suffix, allowClear } = this.$props;
74+
const renderSuffix = (prefixCls: string) => {
75+
const { suffix = slots.suffix?.(), allowClear } = props;
6676
if (suffix || allowClear) {
6777
return (
6878
<span class={`${prefixCls}-suffix`}>
69-
{this.renderClearIcon(prefixCls)}
79+
{renderClearIcon(prefixCls)}
7080
{suffix}
7181
</span>
7282
);
7383
}
7484
return null;
75-
},
85+
};
7686

77-
renderLabeledIcon(prefixCls: string, element: VNode): VNode {
78-
const props = this.$props;
79-
const { style } = this.$attrs;
80-
const suffix = this.renderSuffix(prefixCls);
81-
if (!hasPrefixSuffix(this)) {
87+
const renderLabeledIcon = (prefixCls: string, element: VNode) => {
88+
const {
89+
focused,
90+
value,
91+
prefix = slots.prefix?.(),
92+
size,
93+
suffix = slots.suffix?.(),
94+
disabled,
95+
allowClear,
96+
direction,
97+
readonly,
98+
bordered,
99+
addonAfter = slots.addonAfter,
100+
addonBefore = slots.addonBefore,
101+
} = props;
102+
const suffixNode = renderSuffix(prefixCls);
103+
if (!hasPrefixSuffix({ prefix, suffix, allowClear })) {
82104
return cloneElement(element, {
83-
value: props.value,
105+
value,
84106
});
85107
}
86108

87-
const prefix = props.prefix ? (
88-
<span class={`${prefixCls}-prefix`}>{props.prefix}</span>
89-
) : null;
109+
const prefixNode = prefix ? <span class={`${prefixCls}-prefix`}>{prefix}</span> : null;
90110

91-
const affixWrapperCls = classNames(this.$attrs?.class, `${prefixCls}-affix-wrapper`, {
92-
[`${prefixCls}-affix-wrapper-focused`]: props.isFocused,
93-
[`${prefixCls}-affix-wrapper-disabled`]: props.disabled,
94-
[`${prefixCls}-affix-wrapper-sm`]: props.size === 'small',
95-
[`${prefixCls}-affix-wrapper-lg`]: props.size === 'large',
96-
[`${prefixCls}-affix-wrapper-input-with-clear-btn`]:
97-
props.suffix && props.allowClear && this.$props.value,
111+
const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, {
112+
[`${prefixCls}-affix-wrapper-focused`]: focused,
113+
[`${prefixCls}-affix-wrapper-disabled`]: disabled,
114+
[`${prefixCls}-affix-wrapper-sm`]: size === 'small',
115+
[`${prefixCls}-affix-wrapper-lg`]: size === 'large',
116+
[`${prefixCls}-affix-wrapper-input-with-clear-btn`]: suffix && allowClear && value,
117+
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
118+
[`${prefixCls}-affix-wrapper-readonly`]: readonly,
119+
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
120+
// className will go to addon wrapper
121+
[`${attrs.class}`]: !hasAddon({ addonAfter, addonBefore }) && attrs.class,
98122
});
99123
return (
100-
<span class={affixWrapperCls} style={style}>
101-
{prefix}
124+
<span
125+
ref={containerRef}
126+
class={affixWrapperCls}
127+
style={attrs.style}
128+
onMouseup={onInputMouseUp}
129+
>
130+
{prefixNode}
102131
{cloneElement(element, {
103132
style: null,
104-
value: props.value,
105-
class: getInputClassName(prefixCls, props.size, props.disabled),
133+
value,
134+
class: getInputClassName(prefixCls, bordered, size, disabled),
106135
})}
107-
{suffix}
136+
{suffixNode}
108137
</span>
109-
) as VNode;
110-
},
138+
);
139+
};
111140

112-
renderInputWithLabel(prefixCls: string, labeledElement: VNode) {
113-
const { addonBefore, addonAfter, size } = this.$props;
114-
const { style, class: className } = this.$attrs;
141+
const renderInputWithLabel = (prefixCls: string, labeledElement: VNode) => {
142+
const {
143+
addonBefore = slots.addonBefore?.(),
144+
addonAfter = slots.addonAfter?.(),
145+
size,
146+
direction,
147+
} = props;
115148
// Not wrap when there is not addons
116-
if (!addonBefore && !addonAfter) {
149+
if (!hasAddon({ addonBefore, addonAfter })) {
117150
return labeledElement;
118151
}
119152

@@ -124,61 +157,74 @@ const ClearableLabeledInput = defineComponent({
124157
) : null;
125158
const addonAfterNode = addonAfter ? <span class={addonClassName}>{addonAfter}</span> : null;
126159

127-
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, {
128-
[wrapperClassName]: addonBefore || addonAfter,
160+
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, {
161+
[`${wrapperClassName}-rtl`]: direction === 'rtl',
129162
});
130163

131-
const mergedGroupClassName = classNames(className, `${prefixCls}-group-wrapper`, {
132-
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
133-
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
134-
});
164+
const mergedGroupClassName = classNames(
165+
`${prefixCls}-group-wrapper`,
166+
{
167+
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
168+
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
169+
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
170+
},
171+
attrs.class,
172+
);
135173

136174
// Need another wrapper for changing display:table to display:inline-block
137175
// and put style prop in wrapper
138176
return (
139-
<span class={mergedGroupClassName} style={style}>
177+
<span class={mergedGroupClassName} style={attrs.style}>
140178
<span class={mergedWrapperClassName}>
141179
{addonBeforeNode}
142180
{cloneElement(labeledElement, { style: null })}
143181
{addonAfterNode}
144182
</span>
145183
</span>
146184
);
147-
},
185+
};
148186

149-
renderTextAreaWithClearIcon(prefixCls: string, element: VNode) {
150-
const { value, allowClear } = this.$props;
151-
const { style, class: className } = this.$attrs;
187+
const renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => {
188+
const {
189+
value,
190+
allowClear,
191+
direction,
192+
bordered,
193+
addonAfter = slots.addonAfter,
194+
addonBefore = slots.addonBefore,
195+
} = props;
152196
if (!allowClear) {
153-
return cloneElement(element, { value });
197+
return cloneElement(element, {
198+
value,
199+
});
154200
}
155201
const affixWrapperCls = classNames(
156-
className,
157202
`${prefixCls}-affix-wrapper`,
158203
`${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
204+
{
205+
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
206+
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
207+
// className will go to addon wrapper
208+
[`${attrs.class}`]: !hasAddon({ addonAfter, addonBefore }) && attrs.class,
209+
},
159210
);
160211
return (
161-
<span class={affixWrapperCls} style={style}>
212+
<span class={affixWrapperCls} style={attrs.style}>
162213
{cloneElement(element, {
163214
style: null,
164215
value,
165216
})}
166-
{this.renderClearIcon(prefixCls)}
217+
{renderClearIcon(prefixCls)}
167218
</span>
168219
);
169-
},
220+
};
170221

171-
renderClearableLabeledInput() {
172-
const { prefixCls, inputType, element } = this.$props as any;
222+
return () => {
223+
const { prefixCls, inputType, element = slots.element?.() } = props;
173224
if (inputType === ClearableInputType[0]) {
174-
return this.renderTextAreaWithClearIcon(prefixCls, element);
225+
return renderTextAreaWithClearIcon(prefixCls, element);
175226
}
176-
return this.renderInputWithLabel(prefixCls, this.renderLabeledIcon(prefixCls, element));
177-
},
178-
},
179-
render() {
180-
return this.renderClearableLabeledInput();
227+
return renderInputWithLabel(prefixCls, renderLabeledIcon(prefixCls, element));
228+
};
181229
},
182230
});
183-
184-
export default ClearableLabeledInput;

0 commit comments

Comments
 (0)