mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-11-29 18:48:32 +08:00
refactor: input
This commit is contained in:
parent
cd6c6ff048
commit
7ec6831508
@ -24,6 +24,8 @@ export default (
|
||||
virtual: ComputedRef<boolean>;
|
||||
dropdownMatchSelectWidth: ComputedRef<boolean | number>;
|
||||
getPopupContainer: ComputedRef<ConfigProviderProps['getPopupContainer']>;
|
||||
getPrefixCls: ConfigProviderProps['getPrefixCls'];
|
||||
autocomplete: ComputedRef<string>;
|
||||
} => {
|
||||
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
|
||||
'configProvider',
|
||||
@ -48,6 +50,7 @@ export default (
|
||||
() => props.dropdownMatchSelectWidth ?? configProvider.dropdownMatchSelectWidth,
|
||||
);
|
||||
const size = computed(() => props.size || configProvider.componentSize);
|
||||
const autocomplete = computed(() => props.autocomplete || configProvider.input?.autocomplete);
|
||||
return {
|
||||
configProvider,
|
||||
prefixCls,
|
||||
@ -63,5 +66,7 @@ export default (
|
||||
virtual,
|
||||
dropdownMatchSelectWidth,
|
||||
rootPrefixCls,
|
||||
getPrefixCls: configProvider.getPrefixCls,
|
||||
autocomplete,
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { VueNode } from './type';
|
||||
export const isFunction = val => typeof val === 'function';
|
||||
|
||||
export const controlDefaultValue = Symbol('controlDefaultValue') as any;
|
||||
export const isArray = Array.isArray;
|
||||
export const isString = val => typeof val === 'string';
|
||||
export const isSymbol = val => typeof val === 'symbol';
|
||||
|
@ -35,7 +35,7 @@ export default defineComponent({
|
||||
__ANT_BUTTON: true,
|
||||
props,
|
||||
slots: ['icon'],
|
||||
emits: ['click'],
|
||||
emits: ['click', 'mousedown'],
|
||||
setup(props, { slots, attrs, emit }) {
|
||||
const { prefixCls, autoInsertSpaceInButton, direction } = useConfigInject('btn', props);
|
||||
|
||||
|
@ -160,6 +160,9 @@ export const configProviderProps = {
|
||||
csp: {
|
||||
type: Object as PropType<CSPConfig>,
|
||||
},
|
||||
input: {
|
||||
type: Object as PropType<{ autocomplete: string }>,
|
||||
},
|
||||
autoInsertSpaceInButton: PropTypes.looseBool,
|
||||
locale: {
|
||||
type: Object as PropType<Locale>,
|
||||
|
@ -3,22 +3,23 @@ import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
|
||||
import { getInputClassName } from './Input';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { cloneElement } from '../_util/vnode';
|
||||
import { getComponent } from '../_util/props-util';
|
||||
import type { VNode } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import type { PropType, VNode } from 'vue';
|
||||
import { ref, defineComponent } from 'vue';
|
||||
import { tuple } from '../_util/type';
|
||||
import type { Direction, SizeType } from '../config-provider';
|
||||
import type { MouseEventHandler } from '../_util/EventInterface';
|
||||
|
||||
export function hasPrefixSuffix(instance: any) {
|
||||
return !!(
|
||||
getComponent(instance, 'prefix') ||
|
||||
getComponent(instance, 'suffix') ||
|
||||
instance.$props.allowClear
|
||||
);
|
||||
export function hasPrefixSuffix(propsAndSlots: any) {
|
||||
return !!(propsAndSlots.prefix || propsAndSlots.suffix || propsAndSlots.allowClear);
|
||||
}
|
||||
|
||||
function hasAddon(propsAndSlots: any) {
|
||||
return !!(propsAndSlots.addonBefore || propsAndSlots.addonAfter);
|
||||
}
|
||||
|
||||
const ClearableInputType = ['text', 'input'];
|
||||
|
||||
const ClearableLabeledInput = defineComponent({
|
||||
export default defineComponent({
|
||||
name: 'ClearableLabeledInput',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
@ -27,93 +28,125 @@ const ClearableLabeledInput = defineComponent({
|
||||
value: PropTypes.any,
|
||||
defaultValue: PropTypes.any,
|
||||
allowClear: PropTypes.looseBool,
|
||||
element: PropTypes.VNodeChild,
|
||||
element: PropTypes.any,
|
||||
handleReset: PropTypes.func,
|
||||
disabled: PropTypes.looseBool,
|
||||
size: PropTypes.oneOf(tuple('small', 'large', 'default')),
|
||||
suffix: PropTypes.VNodeChild,
|
||||
prefix: PropTypes.VNodeChild,
|
||||
addonBefore: PropTypes.VNodeChild,
|
||||
addonAfter: PropTypes.VNodeChild,
|
||||
direction: { type: String as PropType<Direction> },
|
||||
size: { type: String as PropType<SizeType> },
|
||||
suffix: PropTypes.any,
|
||||
prefix: PropTypes.any,
|
||||
addonBefore: PropTypes.any,
|
||||
addonAfter: PropTypes.any,
|
||||
readonly: PropTypes.looseBool,
|
||||
isFocused: PropTypes.looseBool,
|
||||
focused: PropTypes.looseBool,
|
||||
bordered: PropTypes.looseBool,
|
||||
triggerFocus: { type: Function as PropType<() => void> },
|
||||
},
|
||||
methods: {
|
||||
renderClearIcon(prefixCls: string) {
|
||||
const { allowClear, value, disabled, readonly, inputType, handleReset } = this.$props;
|
||||
setup(props, { slots, attrs }) {
|
||||
const containerRef = ref();
|
||||
const onInputMouseUp: MouseEventHandler = e => {
|
||||
if (containerRef.value?.contains(e.target as Element)) {
|
||||
const { triggerFocus } = props;
|
||||
triggerFocus?.();
|
||||
}
|
||||
};
|
||||
const renderClearIcon = (prefixCls: string) => {
|
||||
const { allowClear, value, disabled, readonly, handleReset } = props;
|
||||
if (!allowClear) {
|
||||
return null;
|
||||
}
|
||||
const showClearIcon =
|
||||
!disabled && !readonly && value !== undefined && value !== null && value !== '';
|
||||
const className =
|
||||
inputType === ClearableInputType[0]
|
||||
? `${prefixCls}-textarea-clear-icon`
|
||||
: `${prefixCls}-clear-icon`;
|
||||
const needClear = !disabled && !readonly && value;
|
||||
const className = `${prefixCls}-clear-icon`;
|
||||
return (
|
||||
<CloseCircleFilled
|
||||
onClick={handleReset}
|
||||
class={classNames(className, {
|
||||
[`${className}-hidden`]: !showClearIcon,
|
||||
})}
|
||||
class={classNames(
|
||||
{
|
||||
[`${className}-hidden`]: !needClear,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
role="button"
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
renderSuffix(prefixCls: string) {
|
||||
const { suffix, allowClear } = this.$props;
|
||||
const renderSuffix = (prefixCls: string) => {
|
||||
const { suffix = slots.suffix?.(), allowClear } = props;
|
||||
if (suffix || allowClear) {
|
||||
return (
|
||||
<span class={`${prefixCls}-suffix`}>
|
||||
{this.renderClearIcon(prefixCls)}
|
||||
{renderClearIcon(prefixCls)}
|
||||
{suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
renderLabeledIcon(prefixCls: string, element: VNode): VNode {
|
||||
const props = this.$props;
|
||||
const { style } = this.$attrs;
|
||||
const suffix = this.renderSuffix(prefixCls);
|
||||
if (!hasPrefixSuffix(this)) {
|
||||
const renderLabeledIcon = (prefixCls: string, element: VNode) => {
|
||||
const {
|
||||
focused,
|
||||
value,
|
||||
prefix = slots.prefix?.(),
|
||||
size,
|
||||
suffix = slots.suffix?.(),
|
||||
disabled,
|
||||
allowClear,
|
||||
direction,
|
||||
readonly,
|
||||
bordered,
|
||||
addonAfter = slots.addonAfter,
|
||||
addonBefore = slots.addonBefore,
|
||||
} = props;
|
||||
const suffixNode = renderSuffix(prefixCls);
|
||||
if (!hasPrefixSuffix({ prefix, suffix, allowClear })) {
|
||||
return cloneElement(element, {
|
||||
value: props.value,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
const prefix = props.prefix ? (
|
||||
<span class={`${prefixCls}-prefix`}>{props.prefix}</span>
|
||||
) : null;
|
||||
const prefixNode = prefix ? <span class={`${prefixCls}-prefix`}>{prefix}</span> : null;
|
||||
|
||||
const affixWrapperCls = classNames(this.$attrs?.class, `${prefixCls}-affix-wrapper`, {
|
||||
[`${prefixCls}-affix-wrapper-focused`]: props.isFocused,
|
||||
[`${prefixCls}-affix-wrapper-disabled`]: props.disabled,
|
||||
[`${prefixCls}-affix-wrapper-sm`]: props.size === 'small',
|
||||
[`${prefixCls}-affix-wrapper-lg`]: props.size === 'large',
|
||||
[`${prefixCls}-affix-wrapper-input-with-clear-btn`]:
|
||||
props.suffix && props.allowClear && this.$props.value,
|
||||
const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, {
|
||||
[`${prefixCls}-affix-wrapper-focused`]: focused,
|
||||
[`${prefixCls}-affix-wrapper-disabled`]: disabled,
|
||||
[`${prefixCls}-affix-wrapper-sm`]: size === 'small',
|
||||
[`${prefixCls}-affix-wrapper-lg`]: size === 'large',
|
||||
[`${prefixCls}-affix-wrapper-input-with-clear-btn`]: suffix && allowClear && value,
|
||||
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
|
||||
[`${prefixCls}-affix-wrapper-readonly`]: readonly,
|
||||
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
|
||||
// className will go to addon wrapper
|
||||
[`${attrs.class}`]: !hasAddon({ addonAfter, addonBefore }) && attrs.class,
|
||||
});
|
||||
return (
|
||||
<span class={affixWrapperCls} style={style}>
|
||||
{prefix}
|
||||
<span
|
||||
ref={containerRef}
|
||||
class={affixWrapperCls}
|
||||
style={attrs.style}
|
||||
onMouseup={onInputMouseUp}
|
||||
>
|
||||
{prefixNode}
|
||||
{cloneElement(element, {
|
||||
style: null,
|
||||
value: props.value,
|
||||
class: getInputClassName(prefixCls, props.size, props.disabled),
|
||||
value,
|
||||
class: getInputClassName(prefixCls, bordered, size, disabled),
|
||||
})}
|
||||
{suffix}
|
||||
{suffixNode}
|
||||
</span>
|
||||
) as VNode;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
renderInputWithLabel(prefixCls: string, labeledElement: VNode) {
|
||||
const { addonBefore, addonAfter, size } = this.$props;
|
||||
const { style, class: className } = this.$attrs;
|
||||
const renderInputWithLabel = (prefixCls: string, labeledElement: VNode) => {
|
||||
const {
|
||||
addonBefore = slots.addonBefore?.(),
|
||||
addonAfter = slots.addonAfter?.(),
|
||||
size,
|
||||
direction,
|
||||
} = props;
|
||||
// Not wrap when there is not addons
|
||||
if (!addonBefore && !addonAfter) {
|
||||
if (!hasAddon({ addonBefore, addonAfter })) {
|
||||
return labeledElement;
|
||||
}
|
||||
|
||||
@ -124,19 +157,24 @@ const ClearableLabeledInput = defineComponent({
|
||||
) : null;
|
||||
const addonAfterNode = addonAfter ? <span class={addonClassName}>{addonAfter}</span> : null;
|
||||
|
||||
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, {
|
||||
[wrapperClassName]: addonBefore || addonAfter,
|
||||
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, {
|
||||
[`${wrapperClassName}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
|
||||
const mergedGroupClassName = classNames(className, `${prefixCls}-group-wrapper`, {
|
||||
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
|
||||
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
|
||||
});
|
||||
const mergedGroupClassName = classNames(
|
||||
`${prefixCls}-group-wrapper`,
|
||||
{
|
||||
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
|
||||
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
|
||||
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
|
||||
},
|
||||
attrs.class,
|
||||
);
|
||||
|
||||
// Need another wrapper for changing display:table to display:inline-block
|
||||
// and put style prop in wrapper
|
||||
return (
|
||||
<span class={mergedGroupClassName} style={style}>
|
||||
<span class={mergedGroupClassName} style={attrs.style}>
|
||||
<span class={mergedWrapperClassName}>
|
||||
{addonBeforeNode}
|
||||
{cloneElement(labeledElement, { style: null })}
|
||||
@ -144,41 +182,49 @@ const ClearableLabeledInput = defineComponent({
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
renderTextAreaWithClearIcon(prefixCls: string, element: VNode) {
|
||||
const { value, allowClear } = this.$props;
|
||||
const { style, class: className } = this.$attrs;
|
||||
const renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => {
|
||||
const {
|
||||
value,
|
||||
allowClear,
|
||||
direction,
|
||||
bordered,
|
||||
addonAfter = slots.addonAfter,
|
||||
addonBefore = slots.addonBefore,
|
||||
} = props;
|
||||
if (!allowClear) {
|
||||
return cloneElement(element, { value });
|
||||
return cloneElement(element, {
|
||||
value,
|
||||
});
|
||||
}
|
||||
const affixWrapperCls = classNames(
|
||||
className,
|
||||
`${prefixCls}-affix-wrapper`,
|
||||
`${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
|
||||
{
|
||||
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
|
||||
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
|
||||
// className will go to addon wrapper
|
||||
[`${attrs.class}`]: !hasAddon({ addonAfter, addonBefore }) && attrs.class,
|
||||
},
|
||||
);
|
||||
return (
|
||||
<span class={affixWrapperCls} style={style}>
|
||||
<span class={affixWrapperCls} style={attrs.style}>
|
||||
{cloneElement(element, {
|
||||
style: null,
|
||||
value,
|
||||
})}
|
||||
{this.renderClearIcon(prefixCls)}
|
||||
{renderClearIcon(prefixCls)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
renderClearableLabeledInput() {
|
||||
const { prefixCls, inputType, element } = this.$props as any;
|
||||
return () => {
|
||||
const { prefixCls, inputType, element = slots.element?.() } = props;
|
||||
if (inputType === ClearableInputType[0]) {
|
||||
return this.renderTextAreaWithClearIcon(prefixCls, element);
|
||||
return renderTextAreaWithClearIcon(prefixCls, element);
|
||||
}
|
||||
return this.renderInputWithLabel(prefixCls, this.renderLabeledIcon(prefixCls, element));
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return this.renderClearableLabeledInput();
|
||||
return renderInputWithLabel(prefixCls, renderLabeledIcon(prefixCls, element));
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default ClearableLabeledInput;
|
||||
|
@ -1,36 +1,46 @@
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { getSlot } from '../_util/props-util';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { tuple } from '../_util/type';
|
||||
import type { SizeType } from '../config-provider';
|
||||
import type { FocusEventHandler, MouseEventHandler } from '../_util/EventInterface';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AInputGroup',
|
||||
props: {
|
||||
prefixCls: PropTypes.string,
|
||||
size: PropTypes.oneOf(tuple('small', 'large', 'default')),
|
||||
size: { type: String as PropType<SizeType> },
|
||||
compact: PropTypes.looseBool,
|
||||
onMouseenter: { type: Function as PropType<MouseEventHandler> },
|
||||
onMouseleave: { type: Function as PropType<MouseEventHandler> },
|
||||
onFocus: { type: Function as PropType<FocusEventHandler> },
|
||||
onBlur: { type: Function as PropType<FocusEventHandler> },
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
setup(props, { slots }) {
|
||||
const { prefixCls, direction } = useConfigInject('input-group', props);
|
||||
const cls = computed(() => {
|
||||
const pre = prefixCls.value;
|
||||
return {
|
||||
[`${pre}`]: true,
|
||||
[`${pre}-lg`]: props.size === 'large',
|
||||
[`${pre}-sm`]: props.size === 'small',
|
||||
[`${pre}-compact`]: props.compact,
|
||||
[`${pre}-rtl`]: direction.value === 'rtl',
|
||||
};
|
||||
});
|
||||
return () => {
|
||||
const {} = props;
|
||||
return (
|
||||
<span
|
||||
class={cls.value}
|
||||
onMouseenter={props.onMouseEnter}
|
||||
onMouseleave={props.onMouseLeave}
|
||||
onFocus={props.onFocus}
|
||||
onBlur={props.onBlur}
|
||||
>
|
||||
{slots.default?.()}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes() {
|
||||
const { prefixCls: customizePrefixCls, size, compact = false, configProvider } = this as any;
|
||||
const getPrefixCls = configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('input-group', customizePrefixCls);
|
||||
|
||||
return {
|
||||
[`${prefixCls}`]: true,
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
[`${prefixCls}-compact`]: compact,
|
||||
};
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return <span class={this.classes}>{getSlot(this)}</span>;
|
||||
},
|
||||
});
|
||||
|
@ -1,13 +1,25 @@
|
||||
import type { VNode } from 'vue';
|
||||
import { defineComponent, inject, nextTick, withDirectives } from 'vue';
|
||||
import {
|
||||
getCurrentInstance,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
watch,
|
||||
ref,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
withDirectives,
|
||||
} from 'vue';
|
||||
import antInputDirective from '../_util/antInputDirective';
|
||||
import classNames from '../_util/classNames';
|
||||
import type { InputProps } from './inputProps';
|
||||
import inputProps from './inputProps';
|
||||
import { hasProp, getComponent, getOptionProps } from '../_util/props-util';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import type { Direction, SizeType } from '../config-provider';
|
||||
import ClearableLabeledInput from './ClearableLabeledInput';
|
||||
import { useInjectFormItemContext } from '../form/FormItemContext';
|
||||
import omit from '../_util/omit';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import type { ChangeEvent, FocusEventHandler } from '../_util/EventInterface';
|
||||
import { controlDefaultValue } from '../_util/util';
|
||||
|
||||
export function fixControlledValue(value: string | number) {
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
@ -16,142 +28,250 @@ export function fixControlledValue(value: string | number) {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function resolveOnChange(target: HTMLInputElement, e: Event, onChange?: Function) {
|
||||
if (onChange) {
|
||||
const event = e as any;
|
||||
if (e.type === 'click') {
|
||||
// click clear icon
|
||||
//event = Object.create(e);
|
||||
Object.defineProperty(event, 'target', {
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'currentTarget', {
|
||||
writable: true,
|
||||
});
|
||||
event.target = target;
|
||||
event.currentTarget = target;
|
||||
const originalInputValue = target.value;
|
||||
// change target ref value cause e.target.value should be '' when clear input
|
||||
target.value = '';
|
||||
onChange(event);
|
||||
// reset target ref value
|
||||
target.value = originalInputValue;
|
||||
return;
|
||||
}
|
||||
onChange(event);
|
||||
export function resolveOnChange(
|
||||
target: HTMLInputElement,
|
||||
e: Event,
|
||||
onChange: Function,
|
||||
targetValue?: string,
|
||||
) {
|
||||
if (!onChange) {
|
||||
return;
|
||||
}
|
||||
const event: any = e;
|
||||
const originalInputValue = target.value;
|
||||
|
||||
if (e.type === 'click') {
|
||||
Object.defineProperty(event, 'target', {
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'currentTarget', {
|
||||
writable: true,
|
||||
});
|
||||
// click clear icon
|
||||
//event = Object.create(e);
|
||||
event.target = target;
|
||||
event.currentTarget = target;
|
||||
// change target ref value cause e.target.value should be '' when clear input
|
||||
target.value = '';
|
||||
onChange(event);
|
||||
// reset target ref value
|
||||
target.value = originalInputValue;
|
||||
return;
|
||||
}
|
||||
// Trigger by composition event, this means we need force change the input value
|
||||
if (targetValue !== undefined) {
|
||||
Object.defineProperty(event, 'target', {
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'currentTarget', {
|
||||
writable: true,
|
||||
});
|
||||
event.target = target;
|
||||
event.currentTarget = target;
|
||||
target.value = targetValue;
|
||||
onChange(event);
|
||||
return;
|
||||
}
|
||||
onChange(event);
|
||||
}
|
||||
|
||||
export function getInputClassName(prefixCls: string, size: string, disabled: boolean) {
|
||||
export function getInputClassName(
|
||||
prefixCls: string,
|
||||
bordered: boolean,
|
||||
size?: SizeType,
|
||||
disabled?: boolean,
|
||||
direction?: Direction,
|
||||
) {
|
||||
return classNames(prefixCls, {
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
[`${prefixCls}-borderless`]: !bordered,
|
||||
});
|
||||
}
|
||||
|
||||
export interface InputFocusOptions extends FocusOptions {
|
||||
cursor?: 'start' | 'end' | 'all';
|
||||
}
|
||||
export function triggerFocus(
|
||||
element?: HTMLInputElement | HTMLTextAreaElement,
|
||||
option?: InputFocusOptions,
|
||||
) {
|
||||
if (!element) return;
|
||||
|
||||
element.focus(option);
|
||||
|
||||
// Selection content
|
||||
const { cursor } = option || {};
|
||||
if (cursor) {
|
||||
const len = element.value.length;
|
||||
|
||||
switch (cursor) {
|
||||
case 'start':
|
||||
element.setSelectionRange(0, 0);
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
element.setSelectionRange(len, len);
|
||||
break;
|
||||
|
||||
default:
|
||||
element.setSelectionRange(0, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AInput',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
...inputProps,
|
||||
},
|
||||
setup() {
|
||||
setup(props, { slots, attrs, expose, emit }) {
|
||||
const inputRef = ref();
|
||||
const clearableInputRef = ref();
|
||||
let removePasswordTimeout: any;
|
||||
const formItemContext = useInjectFormItemContext();
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
removePasswordTimeout: undefined,
|
||||
input: null,
|
||||
clearableInput: null,
|
||||
formItemContext,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
const props = this.$props;
|
||||
const value = typeof props.value === 'undefined' ? props.defaultValue : props.value;
|
||||
return {
|
||||
stateValue: typeof value === 'undefined' ? '' : value,
|
||||
isFocused: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.stateValue = val;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
nextTick(() => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
if (this.autofocus) {
|
||||
this.focus();
|
||||
const { direction, prefixCls, size, autocomplete } = useConfigInject('input', props);
|
||||
const stateValue = ref(props.value === controlDefaultValue ? props.defaultValue : props.value);
|
||||
const focused = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
if (props.value !== controlDefaultValue) {
|
||||
stateValue.value = props.value;
|
||||
}
|
||||
}
|
||||
this.clearPasswordValueAttribute();
|
||||
},
|
||||
);
|
||||
|
||||
const clearPasswordValueAttribute = () => {
|
||||
// https://github.com/ant-design/ant-design/issues/20541
|
||||
removePasswordTimeout = setTimeout(() => {
|
||||
if (
|
||||
inputRef.value?.getAttribute('type') === 'password' &&
|
||||
inputRef.value.hasAttribute('value')
|
||||
) {
|
||||
inputRef.value.removeAttribute('value');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const focus = (option?: InputFocusOptions) => {
|
||||
triggerFocus(inputRef.value, option);
|
||||
};
|
||||
|
||||
const blur = () => {
|
||||
inputRef.value?.blur();
|
||||
};
|
||||
|
||||
const setSelectionRange = (
|
||||
start: number,
|
||||
end: number,
|
||||
direction?: 'forward' | 'backward' | 'none',
|
||||
) => {
|
||||
inputRef.value?.setSelectionRange(start, end, direction);
|
||||
};
|
||||
|
||||
const select = () => {
|
||||
inputRef.value?.select();
|
||||
};
|
||||
|
||||
expose({
|
||||
focus,
|
||||
blur,
|
||||
inputRef,
|
||||
stateValue,
|
||||
setSelectionRange,
|
||||
select,
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.removePasswordTimeout) {
|
||||
clearTimeout(this.removePasswordTimeout);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInputFocus(e: Event) {
|
||||
this.isFocused = true;
|
||||
this.onFocus && this.onFocus(e);
|
||||
},
|
||||
|
||||
handleInputBlur(e: Event) {
|
||||
this.isFocused = false;
|
||||
this.onBlur && this.onBlur(e);
|
||||
this.formItemContext.onFieldBlur();
|
||||
},
|
||||
const onFocus: FocusEventHandler = e => {
|
||||
const { onFocus } = props;
|
||||
focused.value = true;
|
||||
onFocus?.(e);
|
||||
nextTick(() => {
|
||||
clearPasswordValueAttribute();
|
||||
});
|
||||
};
|
||||
|
||||
focus() {
|
||||
this.input.focus();
|
||||
},
|
||||
const onBlur: FocusEventHandler = e => {
|
||||
const { onBlur } = props;
|
||||
focused.value = false;
|
||||
onBlur?.(e);
|
||||
formItemContext.onFieldBlur();
|
||||
nextTick(() => {
|
||||
clearPasswordValueAttribute();
|
||||
});
|
||||
};
|
||||
|
||||
blur() {
|
||||
this.input.blur();
|
||||
},
|
||||
select() {
|
||||
this.input.select();
|
||||
},
|
||||
|
||||
saveClearableInput(input: HTMLInputElement) {
|
||||
this.clearableInput = input;
|
||||
},
|
||||
|
||||
saveInput(input: HTMLInputElement) {
|
||||
this.input = input;
|
||||
},
|
||||
|
||||
setValue(value: string | number, callback?: Function) {
|
||||
if (this.stateValue === value) {
|
||||
const triggerChange = (e: Event) => {
|
||||
emit('update:value', (e.target as HTMLInputElement).value);
|
||||
emit('change', e);
|
||||
emit('input', e);
|
||||
formItemContext.onFieldChange();
|
||||
};
|
||||
const instance = getCurrentInstance();
|
||||
const setValue = (value: string | number, callback?: Function) => {
|
||||
if (stateValue.value === value) {
|
||||
return;
|
||||
}
|
||||
if (!hasProp(this, 'value')) {
|
||||
this.stateValue = value;
|
||||
if (props.value === controlDefaultValue) {
|
||||
stateValue.value = value;
|
||||
} else {
|
||||
(this as any).$forceUpdate();
|
||||
instance.update();
|
||||
}
|
||||
nextTick(() => {
|
||||
callback && callback();
|
||||
});
|
||||
},
|
||||
triggerChange(e: Event) {
|
||||
this.$emit('update:value', (e.target as HTMLInputElement).value);
|
||||
this.$emit('change', e);
|
||||
this.$emit('input', e);
|
||||
this.formItemContext.onFieldChange();
|
||||
},
|
||||
handleReset(e: Event) {
|
||||
this.setValue('', () => {
|
||||
this.focus();
|
||||
};
|
||||
const handleReset = (e: MouseEvent) => {
|
||||
resolveOnChange(inputRef.value, e, triggerChange);
|
||||
setValue('', () => {
|
||||
focus();
|
||||
});
|
||||
resolveOnChange(this.input, e, this.triggerChange);
|
||||
},
|
||||
renderInput(prefixCls: string, { addonBefore, addonAfter }) {
|
||||
const otherProps = omit(this.$props, [
|
||||
};
|
||||
|
||||
const handleChange = (e: ChangeEvent) => {
|
||||
const { value, composing, isComposing } = e.target as any;
|
||||
// https://github.com/vueComponent/ant-design-vue/issues/2203
|
||||
if (((isComposing || composing) && props.lazy) || stateValue.value === value) return;
|
||||
const newVal = e.target.value;
|
||||
resolveOnChange(inputRef.value, e, triggerChange);
|
||||
setValue(newVal, () => {
|
||||
clearPasswordValueAttribute();
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.keyCode === 13) {
|
||||
emit('pressEnter', e);
|
||||
}
|
||||
emit('keydown', e);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
if (props.autofocus) {
|
||||
focus();
|
||||
}
|
||||
}
|
||||
clearPasswordValueAttribute();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(removePasswordTimeout);
|
||||
});
|
||||
|
||||
const renderInput = () => {
|
||||
const {
|
||||
addonBefore = slots.addonBefore,
|
||||
addonAfter = slots.addonAfter,
|
||||
disabled,
|
||||
bordered = true,
|
||||
valueModifiers = {},
|
||||
} = props;
|
||||
const otherProps = omit(props as InputProps & { inputType: any; placeholder: string }, [
|
||||
'prefixCls',
|
||||
'onPressEnter',
|
||||
'addonBefore',
|
||||
@ -159,36 +279,29 @@ export default defineComponent({
|
||||
'prefix',
|
||||
'suffix',
|
||||
'allowClear',
|
||||
// Input elements must be either controlled or uncontrolled,
|
||||
// specify either the value prop, or the defaultValue prop, but not both.
|
||||
'defaultValue',
|
||||
'lazy',
|
||||
'size',
|
||||
'inputPrefixCls',
|
||||
'loading',
|
||||
'inputType',
|
||||
'bordered',
|
||||
]);
|
||||
const {
|
||||
handleKeyDown,
|
||||
handleChange,
|
||||
handleInputFocus,
|
||||
handleInputBlur,
|
||||
size,
|
||||
disabled,
|
||||
valueModifiers = {},
|
||||
$attrs,
|
||||
} = this;
|
||||
const inputProps: any = {
|
||||
const inputProps = {
|
||||
...otherProps,
|
||||
...$attrs,
|
||||
id: otherProps.id ?? this.formItemContext.id.value,
|
||||
onKeydown: handleKeyDown,
|
||||
class: classNames(getInputClassName(prefixCls, size, disabled), {
|
||||
[$attrs.class as string]: $attrs.class && !addonBefore && !addonAfter,
|
||||
}),
|
||||
ref: this.saveInput,
|
||||
key: 'ant-input',
|
||||
onInput: handleChange,
|
||||
autocomplete: autocomplete.value,
|
||||
onChange: handleChange,
|
||||
onFocus: handleInputFocus,
|
||||
onBlur: handleInputBlur,
|
||||
onInput: handleChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeydown: handleKeyDown,
|
||||
class: classNames(
|
||||
getInputClassName(prefixCls.value, bordered, size.value, disabled, direction.value),
|
||||
{
|
||||
[attrs.class as string]: attrs.class && !addonBefore && !addonAfter,
|
||||
},
|
||||
),
|
||||
ref: inputRef,
|
||||
key: 'ant-input',
|
||||
};
|
||||
if (valueModifiers.lazy) {
|
||||
delete inputProps.onInput;
|
||||
@ -198,58 +311,185 @@ export default defineComponent({
|
||||
}
|
||||
const inputNode = <input {...inputProps} />;
|
||||
return withDirectives(inputNode as VNode, [[antInputDirective]]);
|
||||
},
|
||||
clearPasswordValueAttribute() {
|
||||
// https://github.com/ant-design/ant-design/issues/20541
|
||||
this.removePasswordTimeout = setTimeout(() => {
|
||||
if (
|
||||
this.input &&
|
||||
this.input.getAttribute &&
|
||||
this.input.getAttribute('type') === 'password' &&
|
||||
this.input.hasAttribute('value')
|
||||
) {
|
||||
this.input.removeAttribute('value');
|
||||
}
|
||||
});
|
||||
},
|
||||
handleChange(e: Event) {
|
||||
const { value, composing, isComposing } = e.target as any;
|
||||
// https://github.com/vueComponent/ant-design-vue/issues/2203
|
||||
if (((isComposing || composing) && this.lazy) || this.stateValue === value) return;
|
||||
this.setValue(value, this.clearPasswordValueAttribute);
|
||||
resolveOnChange(this.input, e, this.triggerChange);
|
||||
},
|
||||
handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.keyCode === 13) {
|
||||
this.$emit('pressEnter', e);
|
||||
}
|
||||
this.$emit('keydown', e);
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const { prefixCls: customizePrefixCls } = this.$props;
|
||||
const { stateValue, isFocused } = this.$data;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('input', customizePrefixCls);
|
||||
const addonAfter = getComponent(this, 'addonAfter');
|
||||
const addonBefore = getComponent(this, 'addonBefore');
|
||||
const suffix = getComponent(this, 'suffix');
|
||||
const prefix = getComponent(this, 'prefix');
|
||||
const props: any = {
|
||||
...this.$attrs,
|
||||
...getOptionProps(this),
|
||||
prefixCls,
|
||||
inputType: 'input',
|
||||
value: fixControlledValue(stateValue),
|
||||
element: this.renderInput(prefixCls, { addonAfter, addonBefore }),
|
||||
handleReset: this.handleReset,
|
||||
addonAfter,
|
||||
addonBefore,
|
||||
suffix,
|
||||
prefix,
|
||||
isFocused,
|
||||
};
|
||||
|
||||
return <ClearableLabeledInput {...props} ref={this.saveClearableInput} />;
|
||||
return () => {
|
||||
const inputProps: any = {
|
||||
...attrs,
|
||||
...props,
|
||||
prefixCls: prefixCls.value,
|
||||
inputType: 'input',
|
||||
value: fixControlledValue(stateValue.value),
|
||||
handleReset,
|
||||
focused: focused.value,
|
||||
};
|
||||
|
||||
return (
|
||||
<ClearableLabeledInput
|
||||
{...omit(inputProps, ['element', 'valueModifiers'])}
|
||||
ref={clearableInputRef}
|
||||
v-slots={{ ...slots, element: renderInput }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
// methods: {
|
||||
// handleInputFocus(e: Event) {
|
||||
// this.isFocused = true;
|
||||
// this.onFocus && this.onFocus(e);
|
||||
// },
|
||||
|
||||
// handleInputBlur(e: Event) {
|
||||
// this.isFocused = false;
|
||||
// this.onBlur && this.onBlur(e);
|
||||
// this.formItemContext.onFieldBlur();
|
||||
// },
|
||||
|
||||
// focus() {
|
||||
// this.input.focus();
|
||||
// },
|
||||
|
||||
// blur() {
|
||||
// this.input.blur();
|
||||
// },
|
||||
// select() {
|
||||
// this.input.select();
|
||||
// },
|
||||
|
||||
// saveClearableInput(input: HTMLInputElement) {
|
||||
// this.clearableInput = input;
|
||||
// },
|
||||
|
||||
// saveInput(input: HTMLInputElement) {
|
||||
// this.input = input;
|
||||
// },
|
||||
|
||||
// setValue(value: string | number, callback?: Function) {
|
||||
// if (this.stateValue === value) {
|
||||
// return;
|
||||
// }
|
||||
// if (!hasProp(this, 'value')) {
|
||||
// this.stateValue = value;
|
||||
// } else {
|
||||
// (this as any).$forceUpdate();
|
||||
// }
|
||||
// nextTick(() => {
|
||||
// callback && callback();
|
||||
// });
|
||||
// },
|
||||
// triggerChange(e: Event) {
|
||||
// this.$emit('update:value', (e.target as HTMLInputElement).value);
|
||||
// this.$emit('change', e);
|
||||
// this.$emit('input', e);
|
||||
// this.formItemContext.onFieldChange();
|
||||
// },
|
||||
// handleReset(e: Event) {
|
||||
// this.setValue('', () => {
|
||||
// this.focus();
|
||||
// });
|
||||
// resolveOnChange(this.input, e, this.triggerChange);
|
||||
// },
|
||||
// renderInput(prefixCls: string, { addonBefore, addonAfter }) {
|
||||
// const otherProps = omit(this.$props, [
|
||||
// 'prefixCls',
|
||||
// 'onPressEnter',
|
||||
// 'addonBefore',
|
||||
// 'addonAfter',
|
||||
// 'prefix',
|
||||
// 'suffix',
|
||||
// 'allowClear',
|
||||
// 'defaultValue',
|
||||
// 'lazy',
|
||||
// 'size',
|
||||
// 'inputPrefixCls',
|
||||
// 'loading',
|
||||
// ]);
|
||||
// const {
|
||||
// handleKeyDown,
|
||||
// handleChange,
|
||||
// handleInputFocus,
|
||||
// handleInputBlur,
|
||||
// size,
|
||||
// disabled,
|
||||
// valueModifiers = {},
|
||||
// $attrs,
|
||||
// } = this;
|
||||
// const inputProps: any = {
|
||||
// ...otherProps,
|
||||
// ...$attrs,
|
||||
// id: otherProps.id ?? this.formItemContext.id.value,
|
||||
// onKeydown: handleKeyDown,
|
||||
// class: classNames(getInputClassName(prefixCls, size, disabled), {
|
||||
// [$attrs.class as string]: $attrs.class && !addonBefore && !addonAfter,
|
||||
// }),
|
||||
// ref: this.saveInput,
|
||||
// key: 'ant-input',
|
||||
// onInput: handleChange,
|
||||
// onChange: handleChange,
|
||||
// onFocus: handleInputFocus,
|
||||
// onBlur: handleInputBlur,
|
||||
// };
|
||||
// if (valueModifiers.lazy) {
|
||||
// delete inputProps.onInput;
|
||||
// }
|
||||
// if (!inputProps.autofocus) {
|
||||
// delete inputProps.autofocus;
|
||||
// }
|
||||
// const inputNode = <input {...inputProps} />;
|
||||
// return withDirectives(inputNode as VNode, [[antInputDirective]]);
|
||||
// },
|
||||
// clearPasswordValueAttribute() {
|
||||
// // https://github.com/ant-design/ant-design/issues/20541
|
||||
// this.removePasswordTimeout = setTimeout(() => {
|
||||
// if (
|
||||
// this.input &&
|
||||
// this.input.getAttribute &&
|
||||
// this.input.getAttribute('type') === 'password' &&
|
||||
// this.input.hasAttribute('value')
|
||||
// ) {
|
||||
// this.input.removeAttribute('value');
|
||||
// }
|
||||
// });
|
||||
// },
|
||||
// handleChange(e: Event) {
|
||||
// const { value, composing, isComposing } = e.target as any;
|
||||
// // https://github.com/vueComponent/ant-design-vue/issues/2203
|
||||
// if (((isComposing || composing) && this.lazy) || this.stateValue === value) return;
|
||||
// this.setValue(value, this.clearPasswordValueAttribute);
|
||||
// resolveOnChange(this.input, e, this.triggerChange);
|
||||
// },
|
||||
// handleKeyDown(e: KeyboardEvent) {
|
||||
// if (e.keyCode === 13) {
|
||||
// this.$emit('pressEnter', e);
|
||||
// }
|
||||
// this.$emit('keydown', e);
|
||||
// },
|
||||
// },
|
||||
// render() {
|
||||
// const { prefixCls: customizePrefixCls } = this.$props;
|
||||
// const { stateValue, isFocused } = this.$data;
|
||||
// const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
// const prefixCls = getPrefixCls('input', customizePrefixCls);
|
||||
// const addonAfter = getComponent(this, 'addonAfter');
|
||||
// const addonBefore = getComponent(this, 'addonBefore');
|
||||
// const suffix = getComponent(this, 'suffix');
|
||||
// const prefix = getComponent(this, 'prefix');
|
||||
// const props: any = {
|
||||
// ...this.$attrs,
|
||||
// ...getOptionProps(this),
|
||||
// prefixCls,
|
||||
// inputType: 'input',
|
||||
// value: fixControlledValue(stateValue),
|
||||
// element: this.renderInput(prefixCls, { addonAfter, addonBefore }),
|
||||
// handleReset: this.handleReset,
|
||||
// addonAfter,
|
||||
// addonBefore,
|
||||
// suffix,
|
||||
// prefix,
|
||||
// isFocused,
|
||||
// };
|
||||
|
||||
// return <ClearableLabeledInput {...props} ref={this.saveClearableInput} />;
|
||||
// },
|
||||
});
|
||||
|
@ -1,20 +1,23 @@
|
||||
import classNames from '../_util/classNames';
|
||||
import { getComponent, getOptionProps } from '../_util/props-util';
|
||||
import { isValidElement } from '../_util/props-util';
|
||||
import { cloneElement } from '../_util/vnode';
|
||||
import Input from './Input';
|
||||
import EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
|
||||
import EyeInvisibleOutlined from '@ant-design/icons-vue/EyeInvisibleOutlined';
|
||||
import type { InputProps } from './inputProps';
|
||||
import inputProps from './inputProps';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import BaseMixin from '../_util/BaseMixin';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { computed, defineComponent, ref } from 'vue';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import omit from '../_util/omit';
|
||||
|
||||
const ActionMap = {
|
||||
click: 'onClick',
|
||||
hover: 'onMouseover',
|
||||
};
|
||||
|
||||
const defaultIconRender = (visible: boolean) =>
|
||||
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />;
|
||||
export default defineComponent({
|
||||
name: 'AInputPassword',
|
||||
mixins: [BaseMixin],
|
||||
@ -25,96 +28,76 @@ export default defineComponent({
|
||||
inputPrefixCls: PropTypes.string,
|
||||
action: PropTypes.string.def('click'),
|
||||
visibilityToggle: PropTypes.looseBool.def(true),
|
||||
iconRender: PropTypes.func.def((visible: boolean) =>
|
||||
visible ? <EyeOutlined /> : <EyeInvisibleOutlined />,
|
||||
),
|
||||
iconRender: PropTypes.func,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
input: null,
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
saveInput(node: any) {
|
||||
this.input = node;
|
||||
},
|
||||
focus() {
|
||||
this.input.focus();
|
||||
},
|
||||
blur() {
|
||||
this.input.blur();
|
||||
},
|
||||
onVisibleChange() {
|
||||
if (this.disabled) {
|
||||
setup(props, { slots, attrs, expose }) {
|
||||
const visible = ref(false);
|
||||
const onVisibleChange = () => {
|
||||
const { disabled } = props;
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
visible: !this.visible,
|
||||
});
|
||||
},
|
||||
getIcon(prefixCls) {
|
||||
const { action } = this.$props;
|
||||
const iconTrigger = ActionMap[action] || '';
|
||||
const iconRender = this.$slots.iconRender || this.$props.iconRender;
|
||||
const icon = iconRender(this.visible);
|
||||
visible.value = !visible.value;
|
||||
};
|
||||
const inputRef = ref();
|
||||
const focus = () => {
|
||||
inputRef.value?.focus();
|
||||
};
|
||||
const blur = () => {
|
||||
inputRef.value?.blur();
|
||||
};
|
||||
expose({
|
||||
focus,
|
||||
blur,
|
||||
});
|
||||
const getIcon = (prefixCls: string) => {
|
||||
const { action, iconRender = slots.iconRender || defaultIconRender } = props;
|
||||
const iconTrigger = ActionMap[action!] || '';
|
||||
const icon = iconRender(visible.value);
|
||||
const iconProps = {
|
||||
[iconTrigger]: this.onVisibleChange,
|
||||
onMousedown: (e: Event) => {
|
||||
[iconTrigger]: onVisibleChange,
|
||||
class: `${prefixCls}-icon`,
|
||||
key: 'passwordIcon',
|
||||
onMousedown: (e: MouseEvent) => {
|
||||
// Prevent focused state lost
|
||||
// https://github.com/ant-design/ant-design/issues/15173
|
||||
e.preventDefault();
|
||||
},
|
||||
onMouseup: (e: Event) => {
|
||||
// Prevent focused state lost
|
||||
// https://github.com/ant-design/ant-design/pull/23633/files
|
||||
onMouseup: (e: MouseEvent) => {
|
||||
// Prevent caret position change
|
||||
// https://github.com/ant-design/ant-design/issues/23524
|
||||
e.preventDefault();
|
||||
},
|
||||
class: `${prefixCls}-icon`,
|
||||
key: 'passwordIcon',
|
||||
};
|
||||
return cloneElement(icon, iconProps);
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
inputPrefixCls: customizeInputPrefixCls,
|
||||
size,
|
||||
suffix,
|
||||
action,
|
||||
visibilityToggle,
|
||||
iconRender,
|
||||
...restProps
|
||||
} = getOptionProps(this);
|
||||
const { class: className } = this.$attrs;
|
||||
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
|
||||
const prefixCls = getPrefixCls('input-password', customizePrefixCls);
|
||||
|
||||
const suffixIcon = visibilityToggle && this.getIcon(prefixCls);
|
||||
const inputClassName = classNames(prefixCls, className, {
|
||||
[`${prefixCls}-${size}`]: !!size,
|
||||
});
|
||||
const inputProps = {
|
||||
...restProps,
|
||||
prefixCls: inputPrefixCls,
|
||||
size,
|
||||
suffix: suffixIcon,
|
||||
prefix: getComponent(this, 'prefix'),
|
||||
addonAfter: getComponent(this, 'addonAfter'),
|
||||
addonBefore: getComponent(this, 'addonBefore'),
|
||||
...this.$attrs,
|
||||
type: this.visible ? 'text' : 'password',
|
||||
class: inputClassName,
|
||||
ref: 'input',
|
||||
return cloneElement(isValidElement(icon) ? icon : <span>{icon}</span>, iconProps);
|
||||
};
|
||||
const { prefixCls, getPrefixCls } = useConfigInject('input-password', props);
|
||||
const inputPrefixCls = computed(() => getPrefixCls('input', props.inputPrefixCls));
|
||||
const renderPassword = () => {
|
||||
const { size, visibilityToggle, ...restProps } = props;
|
||||
|
||||
const suffixIcon = visibilityToggle && getIcon(prefixCls.value);
|
||||
const inputClassName = classNames(prefixCls.value, attrs.class, {
|
||||
[`${prefixCls.value}-${size}`]: !!size,
|
||||
});
|
||||
|
||||
const omittedProps = {
|
||||
...omit(restProps, ['suffix', 'iconRender']),
|
||||
...attrs,
|
||||
type: visible.value ? 'text' : 'password',
|
||||
class: inputClassName,
|
||||
prefixCls: inputPrefixCls.value,
|
||||
suffix: suffixIcon,
|
||||
} as InputProps;
|
||||
|
||||
if (size) {
|
||||
omittedProps.size = size;
|
||||
}
|
||||
|
||||
return <Input ref={inputRef} {...omittedProps} v-slots={slots} />;
|
||||
};
|
||||
return () => {
|
||||
return renderPassword();
|
||||
};
|
||||
return <Input {...inputProps} ref={this.saveInput} />;
|
||||
},
|
||||
});
|
||||
|
@ -1,187 +1,145 @@
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import { computed, ref, defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import isMobile from '../_util/isMobile';
|
||||
import Input from './Input';
|
||||
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
|
||||
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
||||
import inputProps from './inputProps';
|
||||
import Button from '../button';
|
||||
import { cloneElement } from '../_util/vnode';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { getOptionProps, getComponent } from '../_util/props-util';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import isPlainObject from 'lodash-es/isPlainObject';
|
||||
import type { ChangeEvent, MouseEventHandler } from '../_util/EventInterface';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import omit from '../_util/omit';
|
||||
import isMobile from '../_util/isMobile';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AInputSearch',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
...inputProps,
|
||||
inputPrefixCls: PropTypes.string,
|
||||
// 不能设置默认值 https://github.com/vueComponent/ant-design-vue/issues/1916
|
||||
enterButton: PropTypes.VNodeChild,
|
||||
onSearch: PropTypes.func,
|
||||
enterButton: PropTypes.any,
|
||||
onSearch: {
|
||||
type: Function as PropType<
|
||||
(value: string, event?: ChangeEvent | MouseEvent | KeyboardEvent) => void
|
||||
>,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
input: null,
|
||||
setup(props, { slots, attrs, expose, emit }) {
|
||||
const inputRef = ref();
|
||||
const focus = () => {
|
||||
inputRef.value?.focus();
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
saveInput(node: HTMLInputElement) {
|
||||
this.input = node;
|
||||
},
|
||||
handleChange(e: Event) {
|
||||
this.$emit('update:value', (e.target as HTMLInputElement).value);
|
||||
const blur = () => {
|
||||
inputRef.value?.blur();
|
||||
};
|
||||
expose({
|
||||
focus,
|
||||
blur,
|
||||
});
|
||||
|
||||
const onChange = (e: ChangeEvent) => {
|
||||
emit('update:value', (e.target as HTMLInputElement).value);
|
||||
if (e && e.target && e.type === 'click') {
|
||||
this.$emit('search', (e.target as HTMLInputElement).value, e);
|
||||
emit('search', e.target.value, e);
|
||||
}
|
||||
this.$emit('change', e);
|
||||
},
|
||||
handleSearch(e: Event) {
|
||||
if (this.loading || this.disabled) {
|
||||
return;
|
||||
emit('change', e);
|
||||
};
|
||||
|
||||
const onMousedown: MouseEventHandler = e => {
|
||||
if (document.activeElement === inputRef.value?.inputRef.value) {
|
||||
e.preventDefault();
|
||||
}
|
||||
this.$emit('search', this.input.stateValue, e);
|
||||
};
|
||||
|
||||
const onSearch = (e: MouseEvent | KeyboardEvent) => {
|
||||
emit('search', inputRef.value?.stateValue, e);
|
||||
if (!isMobile.tablet) {
|
||||
this.input.focus();
|
||||
inputRef.value.focus();
|
||||
}
|
||||
},
|
||||
focus() {
|
||||
this.input.focus();
|
||||
},
|
||||
};
|
||||
|
||||
blur() {
|
||||
this.input.blur();
|
||||
},
|
||||
renderLoading(prefixCls: string) {
|
||||
const { size } = this.$props;
|
||||
let enterButton = getComponent(this, 'enterButton');
|
||||
// 兼容 <a-input-search enterButton />, 因enterButton类型为 any,此类写法 enterButton 为空字符串
|
||||
const { prefixCls, getPrefixCls, direction, size } = useConfigInject('input-search', props);
|
||||
const inputPrefixCls = computed(() => getPrefixCls('input', props.inputPrefixCls));
|
||||
return () => {
|
||||
const {
|
||||
disabled,
|
||||
loading,
|
||||
addonAfter = slots.addonAfter?.(),
|
||||
suffix = slots.suffix?.(),
|
||||
...restProps
|
||||
} = props;
|
||||
let { enterButton = slots.enterButton?.() } = props;
|
||||
enterButton = enterButton || enterButton === '';
|
||||
if (enterButton) {
|
||||
return (
|
||||
<Button class={`${prefixCls}-button`} type="primary" size={size} key="enterButton">
|
||||
<LoadingOutlined />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return <LoadingOutlined class={`${prefixCls}-icon`} key="loadingIcon" />;
|
||||
},
|
||||
renderSuffix(prefixCls: string) {
|
||||
const { loading } = this;
|
||||
const suffix = getComponent(this, 'suffix');
|
||||
let enterButton = getComponent(this, 'enterButton');
|
||||
// 兼容 <a-input-search enterButton />, 因enterButton类型为 any,此类写法 enterButton 为空字符串
|
||||
enterButton = enterButton || enterButton === '';
|
||||
if (loading && !enterButton) {
|
||||
return [suffix, this.renderLoading(prefixCls)];
|
||||
}
|
||||
const searchIcon = typeof enterButton === 'boolean' ? <SearchOutlined /> : null;
|
||||
const btnClassName = `${prefixCls.value}-button`;
|
||||
|
||||
if (enterButton) return suffix;
|
||||
|
||||
const icon = (
|
||||
<SearchOutlined class={`${prefixCls}-icon`} key="searchIcon" onClick={this.handleSearch} />
|
||||
);
|
||||
|
||||
if (suffix) {
|
||||
// let cloneSuffix = suffix;
|
||||
// if (isValidElement(cloneSuffix) && !cloneSuffix.key) {
|
||||
// cloneSuffix = cloneElement(cloneSuffix, {
|
||||
// key: 'originSuffix',
|
||||
// });
|
||||
// }
|
||||
return [suffix, icon];
|
||||
}
|
||||
|
||||
return icon;
|
||||
},
|
||||
renderAddonAfter(prefixCls: string) {
|
||||
const { size, disabled, loading } = this;
|
||||
const btnClassName = `${prefixCls}-button`;
|
||||
let enterButton = getComponent(this, 'enterButton');
|
||||
enterButton = enterButton || enterButton === '';
|
||||
const addonAfter = getComponent(this, 'addonAfter');
|
||||
if (loading && enterButton) {
|
||||
return [this.renderLoading(prefixCls), addonAfter];
|
||||
}
|
||||
if (!enterButton) return addonAfter;
|
||||
const enterButtonAsElement = Array.isArray(enterButton) ? enterButton[0] : enterButton;
|
||||
let button: any;
|
||||
const isAntdButton =
|
||||
enterButtonAsElement.type &&
|
||||
isPlainObject(enterButtonAsElement.type) &&
|
||||
enterButtonAsElement.type.__ANT_BUTTON;
|
||||
if (enterButtonAsElement.tagName === 'button' || isAntdButton) {
|
||||
|
||||
if (isAntdButton || enterButtonAsElement.tagName === 'button') {
|
||||
button = cloneElement(enterButtonAsElement, {
|
||||
onMousedown,
|
||||
onClick: onSearch,
|
||||
key: 'enterButton',
|
||||
class: isAntdButton ? btnClassName : '',
|
||||
...(isAntdButton ? { size } : {}),
|
||||
onClick: this.handleSearch,
|
||||
...(isAntdButton
|
||||
? {
|
||||
class: btnClassName,
|
||||
size: size.value,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
} else {
|
||||
button = (
|
||||
<Button
|
||||
class={btnClassName}
|
||||
type="primary"
|
||||
size={size}
|
||||
type={enterButton ? 'primary' : undefined}
|
||||
size={size.value}
|
||||
disabled={disabled}
|
||||
key="enterButton"
|
||||
onClick={this.handleSearch}
|
||||
onMousedown={onMousedown}
|
||||
onClick={onSearch}
|
||||
loading={loading}
|
||||
icon={searchIcon}
|
||||
>
|
||||
{enterButton === true || enterButton === '' ? <SearchOutlined /> : enterButton}
|
||||
{enterButton}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (addonAfter) {
|
||||
return [button, addonAfter];
|
||||
button = [button, addonAfter];
|
||||
}
|
||||
|
||||
return button;
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
inputPrefixCls: customizeInputPrefixCls,
|
||||
size,
|
||||
class: className,
|
||||
...restProps
|
||||
} = { ...getOptionProps(this), ...this.$attrs } as any;
|
||||
delete restProps.onSearch;
|
||||
delete restProps.loading;
|
||||
delete restProps.enterButton;
|
||||
delete restProps.addonBefore;
|
||||
delete restProps['onUpdate:value'];
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
|
||||
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
|
||||
|
||||
let enterButton = getComponent(this, 'enterButton');
|
||||
const addonBefore = getComponent(this, 'addonBefore');
|
||||
enterButton = enterButton || enterButton === '';
|
||||
let inputClassName: string;
|
||||
if (enterButton) {
|
||||
inputClassName = classNames(prefixCls, className, {
|
||||
[`${prefixCls}-enter-button`]: !!enterButton,
|
||||
[`${prefixCls}-${size}`]: !!size,
|
||||
});
|
||||
} else {
|
||||
inputClassName = classNames(prefixCls, className);
|
||||
}
|
||||
|
||||
const inputProps = {
|
||||
...restProps,
|
||||
prefixCls: inputPrefixCls,
|
||||
size,
|
||||
suffix: this.renderSuffix(prefixCls),
|
||||
prefix: getComponent(this, 'prefix'),
|
||||
addonAfter: this.renderAddonAfter(prefixCls),
|
||||
addonBefore,
|
||||
class: inputClassName,
|
||||
onPressEnter: this.handleSearch,
|
||||
onChange: this.handleChange,
|
||||
const cls = classNames(
|
||||
prefixCls.value,
|
||||
{
|
||||
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
||||
[`${prefixCls.value}-${size.value}`]: !!size.value,
|
||||
[`${prefixCls.value}-with-button`]: !!enterButton,
|
||||
},
|
||||
attrs.class,
|
||||
);
|
||||
return (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
{...omit(restProps, ['onUpdate:value', 'onSearch'])}
|
||||
{...attrs}
|
||||
onPressEnter={onSearch}
|
||||
size={size.value}
|
||||
prefixCls={inputPrefixCls.value}
|
||||
addonAfter={button}
|
||||
suffix={suffix}
|
||||
onChange={onChange}
|
||||
class={cls}
|
||||
disabled={disabled}
|
||||
v-slots={slots}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return <Input {...inputProps} ref={this.saveInput} />;
|
||||
},
|
||||
});
|
||||
|
@ -1,29 +1,62 @@
|
||||
import type { PropType } from 'vue';
|
||||
import type { ExtractPropTypes, PropType } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import type { SizeType } from '../config-provider';
|
||||
export default {
|
||||
import { controlDefaultValue } from '../_util/util';
|
||||
export const inputDefaultValue = Symbol() as unknown as string;
|
||||
const inputProps = {
|
||||
id: PropTypes.string,
|
||||
prefixCls: PropTypes.string,
|
||||
inputPrefixCls: PropTypes.string,
|
||||
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
value: {
|
||||
type: [String, Number, Symbol] as PropType<string | number>,
|
||||
default: controlDefaultValue,
|
||||
},
|
||||
placeholder: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
},
|
||||
type: PropTypes.string.def('text'),
|
||||
autocomplete: String,
|
||||
type: {
|
||||
type: String as PropType<
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'hidden'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'radio'
|
||||
| 'range'
|
||||
| 'reset'
|
||||
| 'search'
|
||||
| 'submit'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week'
|
||||
>,
|
||||
default: 'text',
|
||||
},
|
||||
name: PropTypes.string,
|
||||
size: { type: String as PropType<SizeType> },
|
||||
disabled: PropTypes.looseBool,
|
||||
readonly: PropTypes.looseBool,
|
||||
addonBefore: PropTypes.VNodeChild,
|
||||
addonAfter: PropTypes.VNodeChild,
|
||||
prefix: PropTypes.VNodeChild,
|
||||
suffix: PropTypes.VNodeChild,
|
||||
addonBefore: PropTypes.any,
|
||||
addonAfter: PropTypes.any,
|
||||
prefix: PropTypes.any,
|
||||
suffix: PropTypes.any,
|
||||
autofocus: PropTypes.looseBool,
|
||||
allowClear: PropTypes.looseBool,
|
||||
lazy: PropTypes.looseBool.def(true),
|
||||
maxlength: PropTypes.number,
|
||||
loading: PropTypes.looseBool,
|
||||
bordered: PropTypes.looseBool,
|
||||
onPressEnter: PropTypes.func,
|
||||
onKeydown: PropTypes.func,
|
||||
onKeyup: PropTypes.func,
|
||||
@ -34,3 +67,5 @@ export default {
|
||||
'onUpdate:value': PropTypes.func,
|
||||
valueModifiers: Object,
|
||||
};
|
||||
export default inputProps;
|
||||
export type InputProps = Partial<ExtractPropTypes<typeof inputProps>>;
|
||||
|
@ -6,29 +6,66 @@
|
||||
@search-prefix: ~'@{ant-prefix}-input-search';
|
||||
|
||||
.@{search-prefix} {
|
||||
&-icon {
|
||||
color: @text-color-secondary;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
color: @input-icon-hover-color;
|
||||
.@{ant-prefix}-input {
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: @input-hover-border-color;
|
||||
|
||||
+ .@{ant-prefix}-input-group-addon .@{search-prefix}-button:not(.@{ant-prefix}-btn-primary) {
|
||||
border-left-color: @input-hover-border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-enter-button {
|
||||
input {
|
||||
border-right: 0;
|
||||
}
|
||||
.@{ant-prefix}-input-affix-wrapper {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
& + .@{ant-prefix}-input-group-addon,
|
||||
input + .@{ant-prefix}-input-group-addon {
|
||||
// fix slight height diff in Firefox:
|
||||
// https://ant.design/components/auto-complete-cn/#components-auto-complete-demo-certain-category
|
||||
.@{ant-prefix}-input-lg {
|
||||
line-height: @line-height-base - 0.0002;
|
||||
}
|
||||
|
||||
> .@{ant-prefix}-input-group {
|
||||
> .@{ant-prefix}-input-group-addon:last-child {
|
||||
left: -1px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
||||
.@{search-prefix}-button {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
border-radius: 0 @border-radius-base @border-radius-base 0;
|
||||
}
|
||||
|
||||
.@{search-prefix}-button:not(.@{ant-prefix}-btn-primary) {
|
||||
color: @text-color-secondary;
|
||||
|
||||
&.@{ant-prefix}-btn-loading::before {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-button {
|
||||
height: @input-height-base;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-large &-button {
|
||||
height: @input-height-lg;
|
||||
}
|
||||
|
||||
&-small &-button {
|
||||
height: @input-height-sm;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user