feat: input add clearIcon & status
parent
e9d41efcec
commit
093fa555ba
|
@ -1,9 +1,14 @@
|
|||
import { inject, provide } from 'vue';
|
||||
import { inject, provide, reactive, watchEffect } from 'vue';
|
||||
|
||||
function createContext<T>(defaultValue?: T) {
|
||||
function createContext<T extends Record<string, any>>(defaultValue?: T) {
|
||||
const contextKey = Symbol('contextKey');
|
||||
const useProvide = (props: T) => {
|
||||
provide(contextKey, props);
|
||||
const useProvide = (props: T, newProps?: T) => {
|
||||
const mergedProps = reactive<T>({} as T);
|
||||
provide(contextKey, mergedProps);
|
||||
watchEffect(() => {
|
||||
Object.assign(mergedProps, props, newProps || {});
|
||||
});
|
||||
return mergedProps;
|
||||
};
|
||||
const useInject = () => {
|
||||
return inject(contextKey, defaultValue as T) || ({} as T);
|
||||
|
|
|
@ -2,7 +2,7 @@ import Select from '../select';
|
|||
import { Group, Button } from '../radio';
|
||||
import type { CalendarMode } from './generateCalendar';
|
||||
import type { Ref } from 'vue';
|
||||
import { reactive, watchEffect, defineComponent, ref } from 'vue';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import type { Locale } from '../vc-picker/interface';
|
||||
import type { GenerateConfig } from '../vc-picker/generate';
|
||||
import { FormItemInputContext } from '../form/FormItemContext';
|
||||
|
@ -170,13 +170,7 @@ export default defineComponent<CalendarHeaderProps<any>>({
|
|||
setup(_props, { attrs }) {
|
||||
const divRef = ref<HTMLDivElement>(null);
|
||||
const formItemInputContext = FormItemInputContext.useInject();
|
||||
const newFormItemInputContext = reactive({});
|
||||
FormItemInputContext.useProvide(newFormItemInputContext);
|
||||
watchEffect(() => {
|
||||
Object.assign(newFormItemInputContext, formItemInputContext, {
|
||||
isFormItemInput: false,
|
||||
});
|
||||
});
|
||||
FormItemInputContext.useProvide(formItemInputContext, { isFormItemInput: false });
|
||||
|
||||
return () => {
|
||||
const props = { ..._props, ...attrs };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { ComputedRef, InjectionKey, ConcreteComponent, FunctionalComponent } from 'vue';
|
||||
import type { ComputedRef, InjectionKey, ConcreteComponent } from 'vue';
|
||||
import {
|
||||
watch,
|
||||
computed,
|
||||
|
@ -115,7 +115,12 @@ export interface FormItemStatusContextProps {
|
|||
|
||||
export const FormItemInputContext = createContext<FormItemStatusContextProps>({});
|
||||
|
||||
export const NoFormStatus: FunctionalComponent = (_, { slots }) => {
|
||||
FormItemInputContext.useProvide({});
|
||||
return slots.default?.();
|
||||
};
|
||||
export const NoFormStatus = defineComponent({
|
||||
name: 'NoFormStatus',
|
||||
setup(_, { slots }) {
|
||||
FormItemInputContext.useProvide({});
|
||||
return () => {
|
||||
return slots.default?.();
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,11 +3,14 @@ import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
|
|||
import PropTypes from '../_util/vue-types';
|
||||
import { cloneElement } from '../_util/vnode';
|
||||
import type { PropType, VNode } from 'vue';
|
||||
import { ref, defineComponent } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { tuple } from '../_util/type';
|
||||
import type { Direction, SizeType } from '../config-provider';
|
||||
import type { MouseEventHandler } from '../_util/EventInterface';
|
||||
import { getInputClassName, hasAddon, hasPrefixSuffix } from './util';
|
||||
import { hasAddon } from './util';
|
||||
import { FormItemInputContext } from '../form/FormItemContext';
|
||||
import type { InputStatus } from '../_util/statusUtils';
|
||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||
|
||||
const ClearableInputType = ['text', 'input'];
|
||||
|
||||
|
@ -34,20 +37,13 @@ export default defineComponent({
|
|||
bordered: { type: Boolean, default: true },
|
||||
triggerFocus: { type: Function as PropType<() => void> },
|
||||
hidden: Boolean,
|
||||
status: String as PropType<InputStatus>,
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
const containerRef = ref();
|
||||
const onInputMouseUp: MouseEventHandler = e => {
|
||||
if (containerRef.value?.contains(e.target as Element)) {
|
||||
const { triggerFocus } = props;
|
||||
triggerFocus?.();
|
||||
}
|
||||
};
|
||||
const statusContext = FormItemInputContext.useInject();
|
||||
|
||||
const renderClearIcon = (prefixCls: string) => {
|
||||
const { allowClear, value, disabled, readonly, handleReset, suffix = slots.suffix } = props;
|
||||
if (!allowClear) {
|
||||
return null;
|
||||
}
|
||||
const { value, disabled, readonly, handleReset, suffix = slots.suffix } = props;
|
||||
const needClear = !disabled && !readonly && value;
|
||||
const className = `${prefixCls}-clear-icon`;
|
||||
return (
|
||||
|
@ -66,123 +62,6 @@ export default defineComponent({
|
|||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSuffix = (prefixCls: string) => {
|
||||
const { suffix = slots.suffix?.(), allowClear } = props;
|
||||
if (suffix || allowClear) {
|
||||
return (
|
||||
<span class={`${prefixCls}-suffix`}>
|
||||
{renderClearIcon(prefixCls)}
|
||||
{suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderLabeledIcon = (prefixCls: string, element: VNode) => {
|
||||
const {
|
||||
focused,
|
||||
value,
|
||||
prefix = slots.prefix?.(),
|
||||
size,
|
||||
suffix = slots.suffix?.(),
|
||||
disabled,
|
||||
allowClear,
|
||||
direction,
|
||||
readonly,
|
||||
bordered,
|
||||
hidden,
|
||||
addonAfter = slots.addonAfter,
|
||||
addonBefore = slots.addonBefore,
|
||||
} = props;
|
||||
const suffixNode = renderSuffix(prefixCls);
|
||||
if (!hasPrefixSuffix({ prefix, suffix, allowClear })) {
|
||||
return cloneElement(element, {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
const prefixNode = prefix ? <span class={`${prefixCls}-prefix`}>{prefix}</span> : null;
|
||||
|
||||
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
|
||||
ref={containerRef}
|
||||
class={affixWrapperCls}
|
||||
style={attrs.style}
|
||||
onMouseup={onInputMouseUp}
|
||||
hidden={hidden}
|
||||
>
|
||||
{prefixNode}
|
||||
{cloneElement(element, {
|
||||
style: null,
|
||||
value,
|
||||
class: getInputClassName(prefixCls, bordered, size, disabled),
|
||||
})}
|
||||
{suffixNode}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const renderInputWithLabel = (prefixCls: string, labeledElement: VNode) => {
|
||||
const {
|
||||
addonBefore = slots.addonBefore?.(),
|
||||
addonAfter = slots.addonAfter?.(),
|
||||
size,
|
||||
direction,
|
||||
hidden,
|
||||
} = props;
|
||||
// Not wrap when there is not addons
|
||||
if (!hasAddon({ addonBefore, addonAfter })) {
|
||||
return labeledElement;
|
||||
}
|
||||
|
||||
const wrapperClassName = `${prefixCls}-group`;
|
||||
const addonClassName = `${wrapperClassName}-addon`;
|
||||
const addonBeforeNode = addonBefore ? (
|
||||
<span class={addonClassName}>{addonBefore}</span>
|
||||
) : null;
|
||||
const addonAfterNode = addonAfter ? <span class={addonClassName}>{addonAfter}</span> : null;
|
||||
|
||||
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, {
|
||||
[`${wrapperClassName}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
|
||||
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={attrs.style} hidden={hidden}>
|
||||
<span class={mergedWrapperClassName}>
|
||||
{addonBeforeNode}
|
||||
{cloneElement(labeledElement, { style: null })}
|
||||
{addonAfterNode}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => {
|
||||
const {
|
||||
value,
|
||||
|
@ -190,9 +69,13 @@ export default defineComponent({
|
|||
direction,
|
||||
bordered,
|
||||
hidden,
|
||||
status: customStatus,
|
||||
addonAfter = slots.addonAfter,
|
||||
addonBefore = slots.addonBefore,
|
||||
} = props;
|
||||
|
||||
const { status: contextStatus, hasFeedback } = statusContext;
|
||||
|
||||
if (!allowClear) {
|
||||
return cloneElement(element, {
|
||||
value,
|
||||
|
@ -201,6 +84,11 @@ export default defineComponent({
|
|||
const affixWrapperCls = classNames(
|
||||
`${prefixCls}-affix-wrapper`,
|
||||
`${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
|
||||
getStatusClassNames(
|
||||
`${prefixCls}-affix-wrapper`,
|
||||
getMergedStatus(contextStatus, customStatus),
|
||||
hasFeedback,
|
||||
),
|
||||
{
|
||||
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
|
||||
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
|
||||
|
@ -209,7 +97,7 @@ export default defineComponent({
|
|||
},
|
||||
);
|
||||
return (
|
||||
<span class={affixWrapperCls} style={attrs.style} hidden={hidden}>
|
||||
<span className={affixWrapperCls} style={attrs.style} hidden={hidden}>
|
||||
{cloneElement(element, {
|
||||
style: null,
|
||||
value,
|
||||
|
@ -224,7 +112,7 @@ export default defineComponent({
|
|||
if (inputType === ClearableInputType[0]) {
|
||||
return renderTextAreaWithClearIcon(prefixCls, element);
|
||||
}
|
||||
return renderInputWithLabel(prefixCls, renderLabeledIcon(prefixCls, element));
|
||||
return null;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { PropType } from 'vue';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import type { SizeType } from '../config-provider';
|
||||
import { FormItemInputContext } from '../form/FormItemContext';
|
||||
import type { FocusEventHandler, MouseEventHandler } from '../_util/EventInterface';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
|
||||
|
@ -17,6 +18,10 @@ export default defineComponent({
|
|||
},
|
||||
setup(props, { slots }) {
|
||||
const { prefixCls, direction } = useConfigInject('input-group', props);
|
||||
const formItemInputContext = FormItemInputContext.useInject();
|
||||
FormItemInputContext.useProvide(formItemInputContext, {
|
||||
isFormItemInput: false,
|
||||
});
|
||||
const cls = computed(() => {
|
||||
const pre = prefixCls.value;
|
||||
return {
|
||||
|
|
|
@ -1,108 +1,18 @@
|
|||
import type { VNode } from 'vue';
|
||||
import {
|
||||
getCurrentInstance,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
watch,
|
||||
ref,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
withDirectives,
|
||||
} from 'vue';
|
||||
import antInputDirective from '../_util/antInputDirective';
|
||||
import { onBeforeUpdate, computed, onBeforeUnmount, onMounted, ref, defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import type { InputProps } from './inputProps';
|
||||
import inputProps from './inputProps';
|
||||
import { getInputClassName } from './util';
|
||||
import ClearableLabeledInput from './ClearableLabeledInput';
|
||||
import { useInjectFormItemContext } from '../form/FormItemContext';
|
||||
import omit from '../_util/omit';
|
||||
import {
|
||||
FormItemInputContext,
|
||||
NoFormStatus,
|
||||
useInjectFormItemContext,
|
||||
} from '../form/FormItemContext';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import type { ChangeEvent, FocusEventHandler } from '../_util/EventInterface';
|
||||
|
||||
export function fixControlledValue(value: string | number) {
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
return '';
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export function resolveOnChange(
|
||||
target: HTMLInputElement,
|
||||
e: Event,
|
||||
onChange: Function,
|
||||
targetValue?: string,
|
||||
) {
|
||||
if (!onChange) {
|
||||
return;
|
||||
}
|
||||
const event: any = e;
|
||||
|
||||
if (e.type === 'click') {
|
||||
Object.defineProperty(event, 'target', {
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'currentTarget', {
|
||||
writable: true,
|
||||
});
|
||||
// click clear icon
|
||||
//event = Object.create(e);
|
||||
const currentTarget = target.cloneNode(true);
|
||||
|
||||
event.target = currentTarget;
|
||||
event.currentTarget = currentTarget;
|
||||
// change target ref value cause e.target.value should be '' when clear input
|
||||
(currentTarget as any).value = '';
|
||||
onChange(event);
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||
import type { InputFocusOptions } from '../vc-input/utils/commonUtils';
|
||||
import { hasPrefixSuffix } from '../vc-input/utils/commonUtils';
|
||||
import VcInput from '../vc-input/Input';
|
||||
import inputProps from './inputProps';
|
||||
import omit from '../_util/omit';
|
||||
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AInput',
|
||||
|
@ -110,45 +20,13 @@ export default defineComponent({
|
|||
props: inputProps(),
|
||||
setup(props, { slots, attrs, expose, emit }) {
|
||||
const inputRef = ref();
|
||||
const clearableInputRef = ref();
|
||||
let removePasswordTimeout: any;
|
||||
const formItemContext = useInjectFormItemContext();
|
||||
const formItemInputContext = FormItemInputContext.useInject();
|
||||
const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status));
|
||||
const { direction, prefixCls, size, autocomplete } = useConfigInject('input', props);
|
||||
const stateValue = ref(props.value === undefined ? props.defaultValue : props.value);
|
||||
const focused = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
stateValue.value = props.value;
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.disabled,
|
||||
() => {
|
||||
if (props.value !== undefined) {
|
||||
stateValue.value = props.value;
|
||||
}
|
||||
if (props.disabled) {
|
||||
focused.value = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
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);
|
||||
inputRef.value?.focus(option);
|
||||
};
|
||||
|
||||
const blur = () => {
|
||||
|
@ -171,28 +49,42 @@ export default defineComponent({
|
|||
focus,
|
||||
blur,
|
||||
input: inputRef,
|
||||
stateValue,
|
||||
setSelectionRange,
|
||||
select,
|
||||
});
|
||||
// ===================== Remove Password value =====================
|
||||
const removePasswordTimeoutRef = ref<any[]>([]);
|
||||
const removePasswordTimeout = () => {
|
||||
removePasswordTimeoutRef.value.push(
|
||||
setTimeout(() => {
|
||||
if (
|
||||
inputRef.value?.input &&
|
||||
inputRef.value?.input.getAttribute('type') === 'password' &&
|
||||
inputRef.value?.input.hasAttribute('value')
|
||||
) {
|
||||
inputRef.value?.input.removeAttribute('value');
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
onMounted(() => {
|
||||
removePasswordTimeout();
|
||||
});
|
||||
onBeforeUpdate(() => {
|
||||
removePasswordTimeoutRef.value.forEach(item => clearTimeout(item));
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
removePasswordTimeoutRef.value.forEach(item => clearTimeout(item));
|
||||
});
|
||||
|
||||
const onFocus: FocusEventHandler = e => {
|
||||
const { onFocus } = props;
|
||||
focused.value = true;
|
||||
onFocus?.(e);
|
||||
nextTick(() => {
|
||||
clearPasswordValueAttribute();
|
||||
});
|
||||
const handleBlur = (e: FocusEvent) => {
|
||||
removePasswordTimeout();
|
||||
emit('blur', e);
|
||||
};
|
||||
|
||||
const onBlur: FocusEventHandler = e => {
|
||||
const { onBlur } = props;
|
||||
focused.value = false;
|
||||
onBlur?.(e);
|
||||
formItemContext.onFieldBlur();
|
||||
nextTick(() => {
|
||||
clearPasswordValueAttribute();
|
||||
});
|
||||
const handleFocus = (e: FocusEvent) => {
|
||||
removePasswordTimeout();
|
||||
emit('focus', e);
|
||||
};
|
||||
|
||||
const triggerChange = (e: Event) => {
|
||||
|
@ -201,168 +93,74 @@ export default defineComponent({
|
|||
emit('input', e);
|
||||
formItemContext.onFieldChange();
|
||||
};
|
||||
const instance = getCurrentInstance();
|
||||
const setValue = (value: string | number, callback?: Function) => {
|
||||
if (stateValue.value === value) {
|
||||
return;
|
||||
}
|
||||
if (props.value === undefined) {
|
||||
stateValue.value = value;
|
||||
} else {
|
||||
nextTick(() => {
|
||||
if (inputRef.value.value !== stateValue.value) {
|
||||
instance.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
nextTick(() => {
|
||||
callback && callback();
|
||||
});
|
||||
};
|
||||
const handleReset = (e: MouseEvent) => {
|
||||
resolveOnChange(inputRef.value, e, triggerChange);
|
||||
setValue('', () => {
|
||||
focus();
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = (e: ChangeEvent) => {
|
||||
const { value, composing } = e.target as any;
|
||||
// https://github.com/vueComponent/ant-design-vue/issues/2203
|
||||
if ((((e as any).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 = {},
|
||||
htmlSize,
|
||||
} = props;
|
||||
const otherProps = omit(props as InputProps & { placeholder: string }, [
|
||||
'prefixCls',
|
||||
'onPressEnter',
|
||||
'addonBefore',
|
||||
'addonAfter',
|
||||
'prefix',
|
||||
'suffix',
|
||||
'allowClear',
|
||||
// Input elements must be either controlled or uncontrolled,
|
||||
// specify either the value prop, or the defaultValue prop, but not both.
|
||||
'defaultValue',
|
||||
'size',
|
||||
'bordered',
|
||||
'htmlSize',
|
||||
'lazy',
|
||||
'showCount',
|
||||
'valueModifiers',
|
||||
]);
|
||||
const inputProps = {
|
||||
...otherProps,
|
||||
...attrs,
|
||||
autocomplete: autocomplete.value,
|
||||
onChange: handleChange,
|
||||
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',
|
||||
size: htmlSize,
|
||||
id: otherProps.id ?? formItemContext.id.value,
|
||||
};
|
||||
if (valueModifiers.lazy) {
|
||||
delete inputProps.onInput;
|
||||
}
|
||||
if (!inputProps.autofocus) {
|
||||
delete inputProps.autofocus;
|
||||
}
|
||||
const inputNode = <input {...omit(inputProps, ['size'])} />;
|
||||
return withDirectives(inputNode as VNode, [[antInputDirective]]);
|
||||
};
|
||||
|
||||
const renderShowCountSuffix = () => {
|
||||
const value = stateValue.value;
|
||||
const { maxlength, suffix = slots.suffix?.(), showCount } = props;
|
||||
// Max length value
|
||||
const hasMaxLength = Number(maxlength) > 0;
|
||||
|
||||
if (suffix || showCount) {
|
||||
const valueLength = [...fixControlledValue(value)].length;
|
||||
let dataCount = null;
|
||||
if (typeof showCount === 'object') {
|
||||
dataCount = showCount.formatter({ count: valueLength, maxlength });
|
||||
} else {
|
||||
dataCount = `${valueLength}${hasMaxLength ? ` / ${maxlength}` : ''}`;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{!!showCount && (
|
||||
<span
|
||||
class={classNames(`${prefixCls.value}-show-count-suffix`, {
|
||||
[`${prefixCls.value}-show-count-has-suffix`]: !!suffix,
|
||||
})}
|
||||
>
|
||||
{dataCount}
|
||||
</span>
|
||||
)}
|
||||
{suffix}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return () => {
|
||||
const inputProps: any = {
|
||||
...attrs,
|
||||
...props,
|
||||
prefixCls: prefixCls.value,
|
||||
inputType: 'input',
|
||||
value: fixControlledValue(stateValue.value),
|
||||
handleReset,
|
||||
focused: focused.value && !props.disabled,
|
||||
};
|
||||
|
||||
const { hasFeedback, feedbackIcon } = formItemInputContext;
|
||||
const {
|
||||
allowClear,
|
||||
bordered = true,
|
||||
prefix = slots.prefix?.(),
|
||||
suffix = slots.suffix?.(),
|
||||
addonAfter = slots.addonAfter?.(),
|
||||
addonBefore = slots.addonBefore?.(),
|
||||
id = formItemContext.id?.value,
|
||||
...rest
|
||||
} = props;
|
||||
const suffixNode = (hasFeedback || suffix) && (
|
||||
<>
|
||||
{suffix}
|
||||
{hasFeedback && feedbackIcon}
|
||||
</>
|
||||
);
|
||||
const prefixClsValue = prefixCls.value;
|
||||
const inputHasPrefixSuffix = hasPrefixSuffix({ prefix, suffix }) || !!hasFeedback;
|
||||
const clearIcon = slots.clearIcon || (() => <CloseCircleFilled />);
|
||||
return (
|
||||
<ClearableLabeledInput
|
||||
{...omit(inputProps, ['element', 'valueModifiers', 'suffix', 'showCount'])}
|
||||
ref={clearableInputRef}
|
||||
v-slots={{ ...slots, element: renderInput, suffix: renderShowCountSuffix }}
|
||||
/>
|
||||
<VcInput
|
||||
{...attrs}
|
||||
{...omit(rest, ['onUpdate:value', 'onChange', 'onInput'])}
|
||||
onChange={triggerChange}
|
||||
id={id}
|
||||
ref={inputRef}
|
||||
prefixCls={prefixClsValue}
|
||||
autocomplete={autocomplete.value}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
suffix={suffixNode}
|
||||
allowClear={allowClear}
|
||||
addonAfter={addonAfter && <NoFormStatus>{addonAfter}</NoFormStatus>}
|
||||
addonBefore={addonBefore && <NoFormStatus>{addonBefore}</NoFormStatus>}
|
||||
inputClassName={classNames(
|
||||
{
|
||||
[`${prefixClsValue}-sm`]: size.value === 'small',
|
||||
[`${prefixClsValue}-lg`]: size.value === 'large',
|
||||
[`${prefixClsValue}-rtl`]: direction.value === 'rtl',
|
||||
[`${prefixClsValue}-borderless`]: !bordered,
|
||||
},
|
||||
!inputHasPrefixSuffix && getStatusClassNames(prefixClsValue, mergedStatus.value),
|
||||
)}
|
||||
affixWrapperClassName={classNames(
|
||||
{
|
||||
[`${prefixClsValue}-affix-wrapper-sm`]: size.value === 'small',
|
||||
[`${prefixClsValue}-affix-wrapper-lg`]: size.value === 'large',
|
||||
[`${prefixClsValue}-affix-wrapper-rtl`]: direction.value === 'rtl',
|
||||
[`${prefixClsValue}-affix-wrapper-borderless`]: !bordered,
|
||||
},
|
||||
getStatusClassNames(`${prefixClsValue}-affix-wrapper`, mergedStatus.value, hasFeedback),
|
||||
)}
|
||||
wrapperClassName={classNames({
|
||||
[`${prefixClsValue}-group-rtl`]: direction.value === 'rtl',
|
||||
})}
|
||||
groupClassName={classNames(
|
||||
{
|
||||
[`${prefixClsValue}-group-wrapper-sm`]: size.value === 'small',
|
||||
[`${prefixClsValue}-group-wrapper-lg`]: size.value === 'large',
|
||||
[`${prefixClsValue}-group-wrapper-rtl`]: direction.value === 'rtl',
|
||||
},
|
||||
getStatusClassNames(`${prefixClsValue}-group-wrapper`, mergedStatus.value, hasFeedback),
|
||||
)}
|
||||
v-slots={{ ...slots, clearIcon }}
|
||||
></VcInput>
|
||||
);
|
||||
};
|
||||
},
|
||||
|
|
|
@ -3,15 +3,19 @@ import { computed, ref, defineComponent } from 'vue';
|
|||
import classNames from '../_util/classNames';
|
||||
import Input from './Input';
|
||||
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 isPlainObject from 'lodash-es/isPlainObject';
|
||||
import type { ChangeEvent, MouseEventHandler } from '../_util/EventInterface';
|
||||
import type {
|
||||
ChangeEvent,
|
||||
CompositionEventHandler,
|
||||
MouseEventHandler,
|
||||
} from '../_util/EventInterface';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import omit from '../_util/omit';
|
||||
import isMobile from '../_util/isMobile';
|
||||
import inputProps from './inputProps';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AInputSearch',
|
||||
|
@ -29,6 +33,7 @@ export default defineComponent({
|
|||
},
|
||||
setup(props, { slots, attrs, expose, emit }) {
|
||||
const inputRef = ref();
|
||||
const composedRef = ref(false);
|
||||
const focus = () => {
|
||||
inputRef.value?.focus();
|
||||
};
|
||||
|
@ -55,12 +60,28 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const onSearch = (e: MouseEvent | KeyboardEvent) => {
|
||||
emit('search', inputRef.value?.stateValue, e);
|
||||
emit('search', inputRef.value?.input?.stateValue, e);
|
||||
if (!isMobile.tablet) {
|
||||
inputRef.value.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const onPressEnter = (e: KeyboardEvent) => {
|
||||
if (composedRef.value) {
|
||||
return;
|
||||
}
|
||||
onSearch(e);
|
||||
};
|
||||
|
||||
const handleOnCompositionStart: CompositionEventHandler = e => {
|
||||
composedRef.value = true;
|
||||
emit('compositionstart', e);
|
||||
};
|
||||
|
||||
const handleOnCompositionEnd: CompositionEventHandler = e => {
|
||||
composedRef.value = false;
|
||||
emit('compositionend', e);
|
||||
};
|
||||
const { prefixCls, getPrefixCls, direction, size } = useConfigInject('input-search', props);
|
||||
const inputPrefixCls = computed(() => getPrefixCls('input', props.inputPrefixCls));
|
||||
return () => {
|
||||
|
@ -133,7 +154,9 @@ export default defineComponent({
|
|||
ref={inputRef}
|
||||
{...omit(restProps, ['onUpdate:value', 'onSearch', 'enterButton'])}
|
||||
{...attrs}
|
||||
onPressEnter={onSearch}
|
||||
onPressEnter={onPressEnter}
|
||||
onCompositionstart={handleOnCompositionStart}
|
||||
onCompositionend={handleOnCompositionEnd}
|
||||
size={size.value}
|
||||
prefixCls={inputPrefixCls.value}
|
||||
addonAfter={button}
|
||||
|
|
|
@ -11,14 +11,15 @@ import {
|
|||
import ClearableLabeledInput from './ClearableLabeledInput';
|
||||
import ResizableTextArea from './ResizableTextArea';
|
||||
import { textAreaProps } from './inputProps';
|
||||
import type { InputFocusOptions } from './Input';
|
||||
import { fixControlledValue, resolveOnChange, triggerFocus } from './Input';
|
||||
import type { InputFocusOptions } from '../vc-input/utils/commonUtils';
|
||||
import { fixControlledValue, resolveOnChange, triggerFocus } from '../vc-input/utils/commonUtils';
|
||||
import classNames from '../_util/classNames';
|
||||
import { useInjectFormItemContext } from '../form/FormItemContext';
|
||||
import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItemContext';
|
||||
import type { FocusEventHandler } from '../_util/EventInterface';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import omit from '../_util/omit';
|
||||
import type { VueNode } from '../_util/type';
|
||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||
|
||||
function fixEmojiLength(value: string, maxLength: number) {
|
||||
return [...(value || '')].slice(0, maxLength).join('');
|
||||
|
@ -50,6 +51,8 @@ export default defineComponent({
|
|||
props: textAreaProps(),
|
||||
setup(props, { attrs, expose, emit }) {
|
||||
const formItemContext = useInjectFormItemContext();
|
||||
const formItemInputContext = FormItemInputContext.useInject();
|
||||
const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status));
|
||||
const stateValue = ref(props.value === undefined ? props.defaultValue : props.value);
|
||||
const resizableTextArea = ref();
|
||||
const mergedValue = ref('');
|
||||
|
@ -186,12 +189,15 @@ export default defineComponent({
|
|||
...omit(props, ['allowClear']),
|
||||
...attrs,
|
||||
style: showCount.value ? {} : style,
|
||||
class: {
|
||||
[`${prefixCls.value}-borderless`]: !bordered,
|
||||
[`${customClass}`]: customClass && !showCount.value,
|
||||
[`${prefixCls.value}-sm`]: size.value === 'small',
|
||||
[`${prefixCls.value}-lg`]: size.value === 'large',
|
||||
},
|
||||
class: [
|
||||
{
|
||||
[`${prefixCls.value}-borderless`]: !bordered,
|
||||
[`${customClass}`]: customClass && !showCount.value,
|
||||
[`${prefixCls.value}-sm`]: size.value === 'small',
|
||||
[`${prefixCls.value}-lg`]: size.value === 'large',
|
||||
},
|
||||
getStatusClassNames(prefixCls.value, mergedStatus.value),
|
||||
],
|
||||
showCount: null,
|
||||
prefixCls: prefixCls.value,
|
||||
onInput: handleChange,
|
||||
|
@ -259,10 +265,11 @@ export default defineComponent({
|
|||
{...inputProps}
|
||||
value={mergedValue.value}
|
||||
v-slots={{ element: renderTextArea }}
|
||||
status={props.status}
|
||||
/>
|
||||
);
|
||||
|
||||
if (showCount.value) {
|
||||
if (showCount.value || formItemInputContext.hasFeedback) {
|
||||
const valueLength = [...mergedValue.value].length;
|
||||
let dataCount: VueNode = '';
|
||||
if (typeof showCount.value === 'object') {
|
||||
|
@ -277,6 +284,8 @@ export default defineComponent({
|
|||
`${prefixCls.value}-textarea`,
|
||||
{
|
||||
[`${prefixCls.value}-textarea-rtl`]: direction.value === 'rtl',
|
||||
[`${prefixCls.value}-textarea-show-count`]: showCount.value,
|
||||
[`${prefixCls.value}-textarea-in-form-item`]: formItemInputContext.isFormItemInput,
|
||||
},
|
||||
`${prefixCls.value}-textarea-show-count`,
|
||||
customClass,
|
||||
|
@ -285,6 +294,11 @@ export default defineComponent({
|
|||
data-count={typeof dataCount !== 'object' ? dataCount : undefined}
|
||||
>
|
||||
{textareaNode}
|
||||
{formItemInputContext.hasFeedback && (
|
||||
<span class={`${prefixCls.value}-textarea-suffix`}>
|
||||
{formItemInputContext.feedbackIcon}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,15 +16,26 @@ Basic usage example.
|
|||
|
||||
</docs>
|
||||
<template>
|
||||
<a-input v-model:value="value" placeholder="Basic usage" />
|
||||
<a-space direction="vertical">
|
||||
<a-input v-model:value="value" placeholder="Basic usage" />
|
||||
<a-input v-model:value.lazy="value1" autofocus placeholder="Lazy usage" />
|
||||
</a-space>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { watch, defineComponent, ref } from 'vue';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const value = ref<string>('');
|
||||
const value1 = ref<string>('');
|
||||
watch(value, () => {
|
||||
console.log(value.value);
|
||||
});
|
||||
watch(value1, () => {
|
||||
console.log(value1.value);
|
||||
});
|
||||
return {
|
||||
value,
|
||||
value1,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ Note: You don't need `Col` to control the width in the `compact` mode.
|
|||
|
||||
</docs>
|
||||
<template>
|
||||
<div>
|
||||
<div class="site-input-group-wrapper">
|
||||
<a-input-group size="large">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="5">
|
||||
|
@ -79,13 +79,15 @@ Note: You don't need `Col` to control the width in the `compact` mode.
|
|||
/>
|
||||
<a-input
|
||||
v-model:value="value13"
|
||||
style="width: 30px; border-left: 0; pointer-events: none; background-color: #fff"
|
||||
class="site-input-split"
|
||||
style="width: 30px; border-left: 0; pointer-events: none"
|
||||
placeholder="~"
|
||||
disabled
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="value14"
|
||||
style="width: 100px; text-align: center; border-left: 0"
|
||||
class="site-input-right"
|
||||
style="width: 100px; text-align: center"
|
||||
placeholder="Maximum"
|
||||
/>
|
||||
</a-input-group>
|
||||
|
@ -221,3 +223,29 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.site-input-group-wrapper .site-input-split {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.site-input-group-wrapper .site-input-right {
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.site-input-group-wrapper .site-input-right:hover,
|
||||
.site-input-group-wrapper .site-input-right:focus {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.site-input-group-wrapper .ant-input-rtl.site-input-right {
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.site-input-group-wrapper .ant-input-rtl.site-input-right:hover,
|
||||
.site-input-group-wrapper .ant-input-rtl.site-input-right:focus {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
[data-theme='dark'] .site-input-group-wrapper .site-input-split {
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<show-count />
|
||||
<textarea-resize />
|
||||
<borderlessVue />
|
||||
<statusVue />
|
||||
</demo-sort>
|
||||
</template>
|
||||
|
||||
|
@ -32,6 +33,7 @@ import ShowCount from './show-count.vue';
|
|||
import Addon from './addon.vue';
|
||||
import Tooltip from './tooltip.vue';
|
||||
import borderlessVue from './borderless.vue';
|
||||
import statusVue from './status.vue';
|
||||
import CN from '../index.zh-CN.md';
|
||||
import US from '../index.en-US.md';
|
||||
import { defineComponent } from 'vue';
|
||||
|
@ -40,6 +42,7 @@ export default defineComponent({
|
|||
CN,
|
||||
US,
|
||||
components: {
|
||||
statusVue,
|
||||
Basic,
|
||||
AutosizeTextarea,
|
||||
Presuffix,
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<docs>
|
||||
---
|
||||
order: 19
|
||||
version: 3.3.0
|
||||
title:
|
||||
zh-CN: 自定义状态
|
||||
en-US: Status
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
使用 `status` 为 Input 添加状态,可选 `error` 或者 `warning`。
|
||||
|
||||
## en-US
|
||||
|
||||
Add status to Input with `status`, which could be `error` or `warning`.
|
||||
|
||||
</docs>
|
||||
<template>
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-input status="error" placeholder="Error" />
|
||||
<a-input status="warning" placeholder="Warning" />
|
||||
<a-input status="error" placeholder="Error with prefix">
|
||||
<template #prefix><ClockCircleOutlined /></template>
|
||||
</a-input>
|
||||
<a-input status="warning" placeholder="Warning with prefix">
|
||||
<template #prefix><ClockCircleOutlined /></template>
|
||||
</a-input>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ClockCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
components: {
|
||||
ClockCircleOutlined,
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -22,13 +22,15 @@ A basic widget for getting the user input is a text field. Keyboard and mouse ca
|
|||
| addonBefore | The label text displayed before (on the left side of) the input field. | string\|slot | | |
|
||||
| allowClear | allow to remove input content with clear icon | boolean | | |
|
||||
| bordered | Whether has border style | boolean | true | 4.5.0 |
|
||||
| clearIcon | custom clear icon when allowClear | slot | `<CloseCircleFilled />` | 3.3.0 |
|
||||
| defaultValue | The initial input content | string | | |
|
||||
| disabled | Whether the input is disabled. | boolean | false | |
|
||||
| id | The ID for input | string | | |
|
||||
| maxlength | max length | number | | 1.5.0 |
|
||||
| prefix | The prefix icon for the Input. | string\|slot | | |
|
||||
| showCount | Whether show text count | boolean | false | 3.0 |
|
||||
| size | The size of the input box. Note: in the context of a form, the `large` size is used. Available: `large` `default` `small` | string | `default` | |
|
||||
| status | Set validation status | 'error' \| 'warning' | - | 3.3.0 |
|
||||
| size | The size of the input box. Note: in the context of a form, the `middle` size is used. Available: `large` `middle` `small` | string | - | |
|
||||
| suffix | The suffix icon for the Input. | string\|slot | | |
|
||||
| type | The type of input, see: [MDN](https://developer.mozilla.org/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types)(use `<a-textarea />` instead of `type="textarea"`) | string | `text` | |
|
||||
| value(v-model) | The input content value | string | | |
|
||||
|
|
|
@ -23,13 +23,15 @@ cover: https://gw.alipayobjects.com/zos/alicdn/xS9YEJhfe/Input.svg
|
|||
| addonBefore | 带标签的 input,设置前置标签 | string\|slot | | |
|
||||
| allowClear | 可以点击清除图标删除内容 | boolean | | |
|
||||
| bordered | 是否有边框 | boolean | true | 3.0 |
|
||||
| clearIcon | 自定义清除图标 (allowClear 为 true 时生效) | slot | `<CloseCircleFilled />` | 3.3.0 |
|
||||
| defaultValue | 输入框默认内容 | string | | |
|
||||
| disabled | 是否禁用状态,默认为 false | boolean | false | |
|
||||
| id | 输入框的 id | string | | |
|
||||
| maxlength | 最大长度 | number | | 1.5.0 |
|
||||
| prefix | 带有前缀图标的 input | string\|slot | | |
|
||||
| showCount | 是否展示字数 | boolean | false | 3.0 |
|
||||
| size | 控件大小。注:标准表单内的输入框大小限制为 `large`。可选 `large` `default` `small` | string | `default` | |
|
||||
| status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 |
|
||||
| size | 控件大小。注:标准表单内的输入框大小限制为 `middle`。可选 `large` `middle` `small` | string | - | |
|
||||
| suffix | 带有后缀图标的 input | string\|slot | | |
|
||||
| type | 声明 input 类型,同原生 input 标签的 type 属性,见:[MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#属性)(请直接使用 `<a-textarea />` 代替 `type="textarea"`)。 | string | `text` | |
|
||||
| value(v-model) | 输入框内容 | string | | |
|
||||
|
|
|
@ -1,92 +1,25 @@
|
|||
import type { ExtractPropTypes, PropType } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import type { SizeType } from '../config-provider';
|
||||
import omit from '../_util/omit';
|
||||
import type { LiteralUnion, VueNode } from '../_util/type';
|
||||
import type {
|
||||
ChangeEventHandler,
|
||||
CompositionEventHandler,
|
||||
FocusEventHandler,
|
||||
KeyboardEventHandler,
|
||||
} from '../_util/EventInterface';
|
||||
import type { VueNode } from '../_util/type';
|
||||
import type { CompositionEventHandler } from '../_util/EventInterface';
|
||||
import { inputProps as vcInputProps } from '../vc-input/inputProps';
|
||||
|
||||
export const inputDefaultValue = Symbol() as unknown as string;
|
||||
const inputProps = () => ({
|
||||
id: String,
|
||||
prefixCls: String,
|
||||
inputPrefixCls: String,
|
||||
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
value: {
|
||||
type: [String, Number, Symbol] as PropType<string | number>,
|
||||
default: undefined,
|
||||
},
|
||||
placeholder: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
},
|
||||
autocomplete: String,
|
||||
type: {
|
||||
type: String as PropType<
|
||||
LiteralUnion<
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'hidden'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'radio'
|
||||
| 'range'
|
||||
| 'reset'
|
||||
| 'search'
|
||||
| 'submit'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week',
|
||||
string
|
||||
>
|
||||
>,
|
||||
default: 'text',
|
||||
},
|
||||
name: String,
|
||||
size: { type: String as PropType<SizeType> },
|
||||
disabled: { type: Boolean, default: undefined },
|
||||
readonly: { type: Boolean, default: undefined },
|
||||
addonBefore: PropTypes.any,
|
||||
addonAfter: PropTypes.any,
|
||||
prefix: PropTypes.any,
|
||||
suffix: PropTypes.any,
|
||||
autofocus: { type: Boolean, default: undefined },
|
||||
allowClear: { type: Boolean, default: undefined },
|
||||
lazy: { type: Boolean, default: true },
|
||||
maxlength: Number,
|
||||
loading: { type: Boolean, default: undefined },
|
||||
bordered: { type: Boolean, default: undefined },
|
||||
showCount: { type: [Boolean, Object] as PropType<boolean | ShowCountProps> },
|
||||
htmlSize: Number,
|
||||
onPressEnter: Function as PropType<KeyboardEventHandler>,
|
||||
onKeydown: Function as PropType<KeyboardEventHandler>,
|
||||
onKeyup: Function as PropType<KeyboardEventHandler>,
|
||||
onFocus: Function as PropType<FocusEventHandler>,
|
||||
onBlur: Function as PropType<FocusEventHandler>,
|
||||
onChange: Function as PropType<ChangeEventHandler>,
|
||||
onInput: Function as PropType<ChangeEventHandler>,
|
||||
'onUpdate:value': Function as PropType<(val: string) => void>,
|
||||
valueModifiers: Object,
|
||||
hidden: Boolean,
|
||||
});
|
||||
export default inputProps;
|
||||
export type InputProps = Partial<ExtractPropTypes<ReturnType<typeof inputProps>>>;
|
||||
|
||||
export interface AutoSizeType {
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
}
|
||||
const inputProps = () => {
|
||||
return omit(vcInputProps(), [
|
||||
'wrapperClassName',
|
||||
'groupClassName',
|
||||
'inputClassName',
|
||||
'affixWrapperClassName',
|
||||
]);
|
||||
};
|
||||
export default inputProps;
|
||||
export type InputProps = Partial<ExtractPropTypes<ReturnType<typeof inputProps>>>;
|
||||
export interface ShowCountProps {
|
||||
formatter: (args: { count: number; maxlength?: number }) => VueNode;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,10 @@
|
|||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
|
||||
> *:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&-show-count-suffix {
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
@input-prefix-cls: ~'@{ant-prefix}-input';
|
||||
|
||||
// ========================= Input =========================
|
||||
.@{iconfont-css-prefix}.@{ant-prefix}-input-clear-icon {
|
||||
.@{iconfont-css-prefix}.@{ant-prefix}-input-clear-icon,
|
||||
.@{ant-prefix}-input-clear-icon {
|
||||
margin: 0;
|
||||
color: @disabled-color;
|
||||
font-size: @font-size-sm;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
@import './mixin';
|
||||
@import './affix';
|
||||
@import './allow-clear';
|
||||
@import './status';
|
||||
|
||||
@input-prefix-cls: ~'@{ant-prefix}-input';
|
||||
|
||||
|
@ -24,7 +25,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-password-icon {
|
||||
&-password-icon.@{iconfont-css-prefix} {
|
||||
color: @text-color-secondary;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
@ -60,6 +61,23 @@
|
|||
content: attr(data-count);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.@{input-prefix-cls}-textarea-in-form-item {
|
||||
&::after {
|
||||
margin-bottom: -22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-textarea-suffix {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: @input-padding-horizontal-base;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import '../../style/index.less';
|
||||
import './index.less';
|
||||
|
||||
// deps-lint-skip: form
|
||||
// style dependencies
|
||||
import '../../button/style';
|
||||
|
|
|
@ -30,14 +30,14 @@
|
|||
border-color: @hoverBorderColor;
|
||||
box-shadow: @input-outline-offset @outline-blur-size @outline-width @outlineColor;
|
||||
}
|
||||
border-right-width: @border-width-base !important;
|
||||
border-right-width: @border-width-base;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
// == when hover
|
||||
.hover(@color: @input-hover-border-color) {
|
||||
border-color: @color;
|
||||
border-right-width: @border-width-base !important;
|
||||
border-right-width: @border-width-base;
|
||||
}
|
||||
|
||||
.disabled() {
|
||||
|
@ -66,7 +66,7 @@
|
|||
background-color: @input-bg;
|
||||
background-image: none;
|
||||
border: @border-width-base @border-style-base @input-border-color;
|
||||
border-radius: @border-radius-base;
|
||||
border-radius: @control-border-radius;
|
||||
transition: all 0.3s;
|
||||
.placeholder(); // Reset placeholder
|
||||
|
||||
|
@ -193,7 +193,7 @@
|
|||
text-align: center;
|
||||
background-color: @input-addon-bg;
|
||||
border: @border-width-base @border-style-base @input-border-color;
|
||||
border-radius: @border-radius-base;
|
||||
border-radius: @control-border-radius;
|
||||
transition: all 0.3s;
|
||||
|
||||
// Reset Select's style in addon
|
||||
|
@ -297,8 +297,8 @@
|
|||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
.@{ant-prefix}-input-search & {
|
||||
border-top-left-radius: @border-radius-base;
|
||||
border-bottom-left-radius: @border-radius-base;
|
||||
border-top-left-radius: @control-border-radius;
|
||||
border-bottom-left-radius: @control-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,8 +384,8 @@
|
|||
& > .@{ant-prefix}-select:first-child > .@{ant-prefix}-select-selector,
|
||||
& > .@{ant-prefix}-select-auto-complete:first-child .@{ant-prefix}-input,
|
||||
& > .@{ant-prefix}-cascader-picker:first-child .@{ant-prefix}-input {
|
||||
border-top-left-radius: @border-radius-base;
|
||||
border-bottom-left-radius: @border-radius-base;
|
||||
border-top-left-radius: @control-border-radius;
|
||||
border-bottom-left-radius: @control-border-radius;
|
||||
}
|
||||
|
||||
& > *:last-child,
|
||||
|
@ -393,8 +393,8 @@
|
|||
& > .@{ant-prefix}-cascader-picker:last-child .@{ant-prefix}-input,
|
||||
& > .@{ant-prefix}-cascader-picker-focused:last-child .@{ant-prefix}-input {
|
||||
border-right-width: @border-width-base;
|
||||
border-top-right-radius: @border-radius-base;
|
||||
border-bottom-right-radius: @border-radius-base;
|
||||
border-top-right-radius: @control-border-radius;
|
||||
border-bottom-right-radius: @control-border-radius;
|
||||
}
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/12493
|
||||
|
@ -416,9 +416,55 @@
|
|||
}
|
||||
|
||||
& > .@{ant-prefix}-input {
|
||||
border-radius: @border-radius-base 0 0 @border-radius-base;
|
||||
border-radius: @control-border-radius 0 0 @control-border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-color(
|
||||
@prefix-cls: @input-prefix-cls;
|
||||
@text-color: @input-color;
|
||||
@border-color: @input-border-color;
|
||||
@background-color: @input-bg;
|
||||
@hoverBorderColor: @primary-color-hover;
|
||||
@outlineColor: @primary-color-outline;
|
||||
) {
|
||||
&:not(.@{prefix-cls}-disabled):not(.@{prefix-cls}-borderless).@{prefix-cls} {
|
||||
&,
|
||||
&:hover {
|
||||
background: @background-color;
|
||||
border-color: @border-color;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&-focused {
|
||||
.active(@text-color, @hoverBorderColor, @outlineColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-color-common(
|
||||
@prefix-cls: @input-prefix-cls;
|
||||
@text-color: @input-color;
|
||||
@border-color: @input-border-color;
|
||||
@background-color: @input-bg;
|
||||
@hoverBorderColor: @primary-color-hover;
|
||||
@outlineColor: @primary-color-outline;
|
||||
) {
|
||||
.@{prefix-cls}-prefix {
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.group-status-color(
|
||||
@prefix-cls: @input-prefix-cls;
|
||||
@text-color: @input-color;
|
||||
@border-color: @input-border-color;
|
||||
) {
|
||||
.@{prefix-cls}-group-addon {
|
||||
color: @text-color;
|
||||
border-color: @border-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
@import './mixin';
|
||||
|
||||
@input-prefix-cls: ~'@{ant-prefix}-input';
|
||||
|
||||
@input-wrapper-cls: @input-prefix-cls, ~'@{input-prefix-cls}-affix-wrapper';
|
||||
|
||||
each(@input-wrapper-cls, {
|
||||
.@{value} {
|
||||
&-status-error {
|
||||
.status-color(@value, @error-color, @error-color, @input-bg, @error-color-hover, @error-color-outline);
|
||||
.status-color-common(@input-prefix-cls, @error-color, @error-color, @input-bg, @error-color-hover, @error-color-outline);
|
||||
}
|
||||
|
||||
&-status-warning {
|
||||
.status-color(@value, @warning-color, @warning-color, @input-bg, @warning-color-hover, @warning-color-outline);
|
||||
.status-color-common(@input-prefix-cls, @warning-color, @warning-color, @input-bg, @warning-color-hover, @warning-color-outline);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
.@{input-prefix-cls}-textarea {
|
||||
&-status-error,
|
||||
&-status-warning,
|
||||
&-status-success,
|
||||
&-status-validating {
|
||||
&.@{input-prefix-cls}-textarea-has-feedback {
|
||||
.@{input-prefix-cls} {
|
||||
padding-right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{input-prefix-cls}-group-wrapper {
|
||||
&-status-error {
|
||||
.group-status-color(@input-prefix-cls, @error-color, @error-color);
|
||||
}
|
||||
|
||||
&-status-warning {
|
||||
.group-status-color(@input-prefix-cls, @warning-color, @warning-color);
|
||||
}
|
||||
}
|
|
@ -1,22 +1,4 @@
|
|||
import type { Direction, SizeType } from '../config-provider';
|
||||
import classNames from '../_util/classNames';
|
||||
import { filterEmpty } from '../_util/props-util';
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
const isValid = (value: any) => {
|
||||
return (
|
||||
value !== undefined &&
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
import { defineComponent, ref } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import type { MouseEventHandler } from '../_util/EventInterface';
|
||||
import { cloneElement } from '../_util/vnode';
|
||||
import { baseInputProps } from './inputProps';
|
||||
import { hasAddon, hasPrefixSuffix } from './utils/commonUtils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BaseInput',
|
||||
inheritAttrs: false,
|
||||
props: baseInputProps(),
|
||||
setup(props, { slots, attrs }) {
|
||||
const containerRef = ref();
|
||||
const onInputMouseDown: MouseEventHandler = e => {
|
||||
if (containerRef.value?.contains(e.target as Element)) {
|
||||
const { triggerFocus } = props;
|
||||
triggerFocus?.();
|
||||
}
|
||||
};
|
||||
const getClearIcon = () => {
|
||||
const {
|
||||
allowClear,
|
||||
value,
|
||||
disabled,
|
||||
readonly,
|
||||
handleReset,
|
||||
suffix = slots.suffix,
|
||||
prefixCls,
|
||||
} = props;
|
||||
if (!allowClear) {
|
||||
return null;
|
||||
}
|
||||
const needClear = !disabled && !readonly && value;
|
||||
const className = `${prefixCls}-clear-icon`;
|
||||
const iconNode = slots.clearIcon?.() || '*';
|
||||
return (
|
||||
<span
|
||||
onClick={handleReset}
|
||||
// Do not trigger onBlur when clear input
|
||||
onMousedown={e => e.preventDefault()}
|
||||
class={classNames(
|
||||
{
|
||||
[`${className}-hidden`]: !needClear,
|
||||
[`${className}-has-suffix`]: !!suffix,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
role="button"
|
||||
tabindex={-1}
|
||||
>
|
||||
{iconNode}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return () => {
|
||||
const {
|
||||
focused,
|
||||
value,
|
||||
|
||||
disabled,
|
||||
allowClear,
|
||||
readonly,
|
||||
hidden,
|
||||
prefixCls,
|
||||
prefix = slots.prefix?.(),
|
||||
suffix = slots.suffix?.(),
|
||||
addonAfter = slots.addonAfter,
|
||||
addonBefore = slots.addonBefore,
|
||||
inputElement,
|
||||
affixWrapperClassName,
|
||||
wrapperClassName,
|
||||
groupClassName,
|
||||
} = props;
|
||||
let element = cloneElement(inputElement, {
|
||||
value,
|
||||
hidden,
|
||||
});
|
||||
// ================== Prefix & Suffix ================== //
|
||||
if (hasPrefixSuffix({ prefix, suffix, allowClear })) {
|
||||
const affixWrapperPrefixCls = `${prefixCls}-affix-wrapper`;
|
||||
const affixWrapperCls = classNames(
|
||||
affixWrapperPrefixCls,
|
||||
{
|
||||
[`${affixWrapperPrefixCls}-disabled`]: disabled,
|
||||
[`${affixWrapperPrefixCls}-focused`]: focused,
|
||||
[`${affixWrapperPrefixCls}-readonly`]: readonly,
|
||||
[`${affixWrapperPrefixCls}-input-with-clear-btn`]: suffix && allowClear && value,
|
||||
},
|
||||
!hasAddon({ addonAfter, addonBefore }) && attrs.class,
|
||||
affixWrapperClassName,
|
||||
);
|
||||
|
||||
const suffixNode = (suffix || allowClear) && (
|
||||
<span class={`${prefixCls}-suffix`}>
|
||||
{getClearIcon()}
|
||||
{suffix}
|
||||
</span>
|
||||
);
|
||||
|
||||
element = (
|
||||
<span
|
||||
class={affixWrapperCls}
|
||||
style={attrs.style}
|
||||
hidden={!hasAddon({ addonAfter, addonBefore }) && hidden}
|
||||
onMousedown={onInputMouseDown}
|
||||
ref={containerRef}
|
||||
>
|
||||
{prefix && <span class={`${prefixCls}-prefix`}>{prefix}</span>}
|
||||
{cloneElement(inputElement, {
|
||||
style: null,
|
||||
value,
|
||||
hidden: null,
|
||||
})}
|
||||
{suffixNode}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
// ================== Addon ================== //
|
||||
if (hasAddon({ addonAfter, addonBefore })) {
|
||||
const wrapperCls = `${prefixCls}-group`;
|
||||
const addonCls = `${wrapperCls}-addon`;
|
||||
|
||||
const mergedWrapperClassName = classNames(
|
||||
`${prefixCls}-wrapper`,
|
||||
wrapperCls,
|
||||
wrapperClassName,
|
||||
);
|
||||
|
||||
const mergedGroupClassName = classNames(
|
||||
`${prefixCls}-group-wrapper`,
|
||||
attrs.class,
|
||||
groupClassName,
|
||||
);
|
||||
|
||||
// Need another wrapper for changing display:table to display:inline-block
|
||||
// and put style prop in wrapper
|
||||
return (
|
||||
<span class={mergedGroupClassName} style={attrs.style} hidden={hidden}>
|
||||
<span class={mergedWrapperClassName}>
|
||||
{addonBefore && <span class={addonCls}>{addonBefore}</span>}
|
||||
{cloneElement(element, { style: null, hidden: null })}
|
||||
{addonAfter && <span class={addonCls}>{addonAfter}</span>}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return element;
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,261 @@
|
|||
// base 0.0.1-alpha.7
|
||||
import type { VNode } from 'vue';
|
||||
import {
|
||||
onMounted,
|
||||
defineComponent,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
ref,
|
||||
watch,
|
||||
withDirectives,
|
||||
} from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import type { ChangeEvent, FocusEventHandler } from '../_util/EventInterface';
|
||||
import omit from '../_util/omit';
|
||||
import type { InputProps } from './inputProps';
|
||||
import { inputProps } from './inputProps';
|
||||
import type { InputFocusOptions } from './utils/commonUtils';
|
||||
import {
|
||||
fixControlledValue,
|
||||
hasAddon,
|
||||
hasPrefixSuffix,
|
||||
resolveOnChange,
|
||||
triggerFocus,
|
||||
} from './utils/commonUtils';
|
||||
import antInputDirective from '../_util/antInputDirective';
|
||||
import BaseInput from './BaseInput';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'VCInput',
|
||||
inheritAttrs: false,
|
||||
props: inputProps(),
|
||||
setup(props, { slots, attrs, expose, emit }) {
|
||||
const stateValue = ref(props.value === undefined ? props.defaultValue : props.value);
|
||||
const focused = ref(false);
|
||||
const inputRef = ref<HTMLInputElement>();
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
stateValue.value = props.value;
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.disabled,
|
||||
() => {
|
||||
if (props.disabled) {
|
||||
focused.value = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
const focus = (option?: InputFocusOptions) => {
|
||||
if (inputRef.value) {
|
||||
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,
|
||||
input: inputRef,
|
||||
stateValue,
|
||||
setSelectionRange,
|
||||
select,
|
||||
});
|
||||
const triggerChange = (e: Event) => {
|
||||
emit('change', e);
|
||||
};
|
||||
const instance = getCurrentInstance();
|
||||
const setValue = (value: string | number, callback?: Function) => {
|
||||
if (stateValue.value === value) {
|
||||
return;
|
||||
}
|
||||
if (props.value === undefined) {
|
||||
stateValue.value = value;
|
||||
} else {
|
||||
nextTick(() => {
|
||||
if (inputRef.value.value !== stateValue.value) {
|
||||
instance.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
nextTick(() => {
|
||||
callback && callback();
|
||||
});
|
||||
};
|
||||
const handleChange = (e: ChangeEvent) => {
|
||||
const { value, composing } = e.target as any;
|
||||
// https://github.com/vueComponent/ant-design-vue/issues/2203
|
||||
if ((((e as any).isComposing || composing) && props.lazy) || stateValue.value === value)
|
||||
return;
|
||||
const newVal = e.target.value;
|
||||
resolveOnChange(inputRef.value, e, triggerChange);
|
||||
setValue(newVal);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.keyCode === 13) {
|
||||
emit('pressEnter', e);
|
||||
}
|
||||
emit('keydown', e);
|
||||
};
|
||||
|
||||
const handleFocus: FocusEventHandler = e => {
|
||||
focused.value = true;
|
||||
emit('focus', e);
|
||||
};
|
||||
|
||||
const handleBlur: FocusEventHandler = e => {
|
||||
focused.value = false;
|
||||
emit('blur', e);
|
||||
};
|
||||
|
||||
const handleReset = (e: MouseEvent) => {
|
||||
resolveOnChange(inputRef.value, e, triggerChange);
|
||||
setValue('', () => {
|
||||
focus();
|
||||
});
|
||||
};
|
||||
|
||||
const getInputElement = () => {
|
||||
const {
|
||||
addonBefore = slots.addonBefore,
|
||||
addonAfter = slots.addonAfter,
|
||||
disabled,
|
||||
valueModifiers = {},
|
||||
htmlSize,
|
||||
autocomplete,
|
||||
prefixCls,
|
||||
inputClassName,
|
||||
prefix = slots.prefix?.(),
|
||||
suffix = slots.suffix?.(),
|
||||
allowClear,
|
||||
type = 'text',
|
||||
} = props;
|
||||
const otherProps = omit(props as InputProps & { placeholder: string }, [
|
||||
'prefixCls',
|
||||
'onPressEnter',
|
||||
'addonBefore',
|
||||
'addonAfter',
|
||||
'prefix',
|
||||
'suffix',
|
||||
'allowClear',
|
||||
// Input elements must be either controlled or uncontrolled,
|
||||
// specify either the value prop, or the defaultValue prop, but not both.
|
||||
'defaultValue',
|
||||
'size',
|
||||
'bordered',
|
||||
'htmlSize',
|
||||
'lazy',
|
||||
'showCount',
|
||||
'valueModifiers',
|
||||
'showCount',
|
||||
'affixWrapperClassName',
|
||||
'groupClassName',
|
||||
'inputClassName',
|
||||
'wrapperClassName',
|
||||
]);
|
||||
const inputProps = {
|
||||
...otherProps,
|
||||
...attrs,
|
||||
autocomplete,
|
||||
onChange: handleChange,
|
||||
onInput: handleChange,
|
||||
onFocus: handleFocus,
|
||||
onBlur: handleBlur,
|
||||
onKeydown: handleKeyDown,
|
||||
class: classNames(
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
},
|
||||
inputClassName,
|
||||
!hasAddon({ addonAfter, addonBefore }) &&
|
||||
!hasPrefixSuffix({ prefix, suffix, allowClear }) &&
|
||||
attrs.class,
|
||||
),
|
||||
ref: inputRef,
|
||||
key: 'ant-input',
|
||||
size: htmlSize,
|
||||
type,
|
||||
};
|
||||
if (valueModifiers.lazy) {
|
||||
delete inputProps.onInput;
|
||||
}
|
||||
if (!inputProps.autofocus) {
|
||||
delete inputProps.autofocus;
|
||||
}
|
||||
const inputNode = <input {...omit(inputProps, ['size'])} />;
|
||||
return withDirectives(inputNode as VNode, [[antInputDirective]]);
|
||||
};
|
||||
const getSuffix = () => {
|
||||
const { maxlength, suffix = slots.suffix?.(), showCount, prefixCls } = props;
|
||||
// Max length value
|
||||
const hasMaxLength = Number(maxlength) > 0;
|
||||
|
||||
if (suffix || showCount) {
|
||||
const valueLength = [...fixControlledValue(stateValue.value)].length;
|
||||
const dataCount =
|
||||
typeof showCount === 'object'
|
||||
? showCount.formatter({ count: valueLength, maxlength })
|
||||
: `${valueLength}${hasMaxLength ? ` / ${maxlength}` : ''}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!showCount && (
|
||||
<span
|
||||
class={classNames(`${prefixCls}-show-count-suffix`, {
|
||||
[`${prefixCls}-show-count-has-suffix`]: !!suffix,
|
||||
})}
|
||||
>
|
||||
{dataCount}
|
||||
</span>
|
||||
)}
|
||||
{suffix}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
onMounted(() => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
if (props.autofocus) {
|
||||
focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
const { prefixCls, disabled, ...rest } = props;
|
||||
return (
|
||||
<BaseInput
|
||||
{...rest}
|
||||
{...attrs}
|
||||
prefixCls={prefixCls}
|
||||
inputElement={getInputElement()}
|
||||
handleReset={handleReset}
|
||||
value={fixControlledValue(stateValue.value)}
|
||||
focused={focused.value}
|
||||
triggerFocus={focus}
|
||||
suffix={getSuffix()}
|
||||
disabled={disabled}
|
||||
v-slots={slots}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
import type { ExtractPropTypes, PropType } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import type { SizeType } from '../config-provider';
|
||||
import type { LiteralUnion, VueNode } from '../_util/type';
|
||||
import type {
|
||||
ChangeEventHandler,
|
||||
CompositionEventHandler,
|
||||
FocusEventHandler,
|
||||
KeyboardEventHandler,
|
||||
MouseEventHandler,
|
||||
} from '../_util/EventInterface';
|
||||
import type { InputStatus } from '../_util/statusUtils';
|
||||
import type { InputFocusOptions } from './utils/commonUtils';
|
||||
export const inputDefaultValue = Symbol() as unknown as string;
|
||||
export const commonInputProps = () => {
|
||||
return {
|
||||
addonBefore: PropTypes.any,
|
||||
addonAfter: PropTypes.any,
|
||||
prefix: PropTypes.any,
|
||||
suffix: PropTypes.any,
|
||||
clearIcon: PropTypes.any,
|
||||
affixWrapperClassName: String,
|
||||
groupClassName: String,
|
||||
wrapperClassName: String,
|
||||
inputClassName: String,
|
||||
allowClear: { type: Boolean, default: undefined },
|
||||
};
|
||||
};
|
||||
export const baseInputProps = () => {
|
||||
return {
|
||||
...commonInputProps(),
|
||||
value: {
|
||||
type: [String, Number, Symbol] as PropType<string | number>,
|
||||
default: undefined,
|
||||
},
|
||||
defaultValue: {
|
||||
type: [String, Number, Symbol] as PropType<string | number>,
|
||||
default: undefined,
|
||||
},
|
||||
inputElement: PropTypes.any,
|
||||
prefixCls: String,
|
||||
disabled: { type: Boolean, default: undefined },
|
||||
focused: { type: Boolean, default: undefined },
|
||||
triggerFocus: Function as PropType<() => void>,
|
||||
readonly: { type: Boolean, default: undefined },
|
||||
handleReset: Function as PropType<MouseEventHandler>,
|
||||
hidden: { type: Boolean, default: undefined },
|
||||
};
|
||||
};
|
||||
export const inputProps = () => ({
|
||||
...baseInputProps(),
|
||||
id: String,
|
||||
placeholder: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
},
|
||||
autocomplete: String,
|
||||
type: {
|
||||
type: String as PropType<
|
||||
LiteralUnion<
|
||||
| 'button'
|
||||
| 'checkbox'
|
||||
| 'color'
|
||||
| 'date'
|
||||
| 'datetime-local'
|
||||
| 'email'
|
||||
| 'file'
|
||||
| 'hidden'
|
||||
| 'image'
|
||||
| 'month'
|
||||
| 'number'
|
||||
| 'password'
|
||||
| 'radio'
|
||||
| 'range'
|
||||
| 'reset'
|
||||
| 'search'
|
||||
| 'submit'
|
||||
| 'tel'
|
||||
| 'text'
|
||||
| 'time'
|
||||
| 'url'
|
||||
| 'week',
|
||||
string
|
||||
>
|
||||
>,
|
||||
default: 'text',
|
||||
},
|
||||
name: String,
|
||||
size: { type: String as PropType<SizeType> },
|
||||
autofocus: { type: Boolean, default: undefined },
|
||||
lazy: { type: Boolean, default: true },
|
||||
maxlength: Number,
|
||||
loading: { type: Boolean, default: undefined },
|
||||
bordered: { type: Boolean, default: undefined },
|
||||
showCount: { type: [Boolean, Object] as PropType<boolean | ShowCountProps> },
|
||||
htmlSize: Number,
|
||||
onPressEnter: Function as PropType<KeyboardEventHandler>,
|
||||
onKeydown: Function as PropType<KeyboardEventHandler>,
|
||||
onKeyup: Function as PropType<KeyboardEventHandler>,
|
||||
onFocus: Function as PropType<FocusEventHandler>,
|
||||
onBlur: Function as PropType<FocusEventHandler>,
|
||||
onChange: Function as PropType<ChangeEventHandler>,
|
||||
onInput: Function as PropType<ChangeEventHandler>,
|
||||
'onUpdate:value': Function as PropType<(val: string) => void>,
|
||||
onCompositionstart: Function as PropType<CompositionEventHandler>,
|
||||
onCompositionend: Function as PropType<CompositionEventHandler>,
|
||||
valueModifiers: Object,
|
||||
hidden: { type: Boolean, default: undefined },
|
||||
status: String as PropType<InputStatus>,
|
||||
});
|
||||
export type InputProps = Partial<ExtractPropTypes<ReturnType<typeof inputProps>>>;
|
||||
|
||||
export interface ShowCountProps {
|
||||
formatter: (args: { count: number; maxlength?: number }) => VueNode;
|
||||
}
|
||||
|
||||
export interface InputRef {
|
||||
focus: (options?: InputFocusOptions) => void;
|
||||
blur: () => void;
|
||||
setSelectionRange: (
|
||||
start: number,
|
||||
end: number,
|
||||
direction?: 'forward' | 'backward' | 'none',
|
||||
) => void;
|
||||
select: () => void;
|
||||
input: HTMLInputElement | null;
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import { filterEmpty } from '../../_util/props-util';
|
||||
const isValid = (value: any) => {
|
||||
return (
|
||||
value !== undefined &&
|
||||
value !== null &&
|
||||
(Array.isArray(value) ? filterEmpty(value).length : true)
|
||||
);
|
||||
};
|
||||
|
||||
export function hasPrefixSuffix(propsAndSlots: any) {
|
||||
return (
|
||||
isValid(propsAndSlots.prefix) ||
|
||||
isValid(propsAndSlots.suffix) ||
|
||||
isValid(propsAndSlots.allowClear)
|
||||
);
|
||||
}
|
||||
|
||||
export function hasAddon(propsAndSlots: any) {
|
||||
return isValid(propsAndSlots.addonBefore) || isValid(propsAndSlots.addonAfter);
|
||||
}
|
||||
|
||||
export function fixControlledValue(value: string | number) {
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
return '';
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export function resolveOnChange(
|
||||
target: HTMLInputElement,
|
||||
e: Event,
|
||||
onChange: Function,
|
||||
targetValue?: string,
|
||||
) {
|
||||
if (!onChange) {
|
||||
return;
|
||||
}
|
||||
const event: any = e;
|
||||
|
||||
if (e.type === 'click') {
|
||||
Object.defineProperty(event, 'target', {
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'currentTarget', {
|
||||
writable: true,
|
||||
});
|
||||
// click clear icon
|
||||
//event = Object.create(e);
|
||||
const currentTarget = target.cloneNode(true);
|
||||
|
||||
event.target = currentTarget;
|
||||
event.currentTarget = currentTarget;
|
||||
// change target ref value cause e.target.value should be '' when clear input
|
||||
(currentTarget as any).value = '';
|
||||
onChange(event);
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
/** https://github.com/Microsoft/TypeScript/issues/29729 */
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type LiteralUnion<T extends U, U> = T | (U & {});
|
Loading…
Reference in New Issue