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 contextKey = Symbol('contextKey');
|
||||||
const useProvide = (props: T) => {
|
const useProvide = (props: T, newProps?: T) => {
|
||||||
provide(contextKey, props);
|
const mergedProps = reactive<T>({} as T);
|
||||||
|
provide(contextKey, mergedProps);
|
||||||
|
watchEffect(() => {
|
||||||
|
Object.assign(mergedProps, props, newProps || {});
|
||||||
|
});
|
||||||
|
return mergedProps;
|
||||||
};
|
};
|
||||||
const useInject = () => {
|
const useInject = () => {
|
||||||
return inject(contextKey, defaultValue as T) || ({} as T);
|
return inject(contextKey, defaultValue as T) || ({} as T);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Select from '../select';
|
||||||
import { Group, Button } from '../radio';
|
import { Group, Button } from '../radio';
|
||||||
import type { CalendarMode } from './generateCalendar';
|
import type { CalendarMode } from './generateCalendar';
|
||||||
import type { Ref } from 'vue';
|
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 { Locale } from '../vc-picker/interface';
|
||||||
import type { GenerateConfig } from '../vc-picker/generate';
|
import type { GenerateConfig } from '../vc-picker/generate';
|
||||||
import { FormItemInputContext } from '../form/FormItemContext';
|
import { FormItemInputContext } from '../form/FormItemContext';
|
||||||
|
@ -170,13 +170,7 @@ export default defineComponent<CalendarHeaderProps<any>>({
|
||||||
setup(_props, { attrs }) {
|
setup(_props, { attrs }) {
|
||||||
const divRef = ref<HTMLDivElement>(null);
|
const divRef = ref<HTMLDivElement>(null);
|
||||||
const formItemInputContext = FormItemInputContext.useInject();
|
const formItemInputContext = FormItemInputContext.useInject();
|
||||||
const newFormItemInputContext = reactive({});
|
FormItemInputContext.useProvide(formItemInputContext, { isFormItemInput: false });
|
||||||
FormItemInputContext.useProvide(newFormItemInputContext);
|
|
||||||
watchEffect(() => {
|
|
||||||
Object.assign(newFormItemInputContext, formItemInputContext, {
|
|
||||||
isFormItemInput: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const props = { ..._props, ...attrs };
|
const props = { ..._props, ...attrs };
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ComputedRef, InjectionKey, ConcreteComponent, FunctionalComponent } from 'vue';
|
import type { ComputedRef, InjectionKey, ConcreteComponent } from 'vue';
|
||||||
import {
|
import {
|
||||||
watch,
|
watch,
|
||||||
computed,
|
computed,
|
||||||
|
@ -115,7 +115,12 @@ export interface FormItemStatusContextProps {
|
||||||
|
|
||||||
export const FormItemInputContext = createContext<FormItemStatusContextProps>({});
|
export const FormItemInputContext = createContext<FormItemStatusContextProps>({});
|
||||||
|
|
||||||
export const NoFormStatus: FunctionalComponent = (_, { slots }) => {
|
export const NoFormStatus = defineComponent({
|
||||||
FormItemInputContext.useProvide({});
|
name: 'NoFormStatus',
|
||||||
return slots.default?.();
|
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 PropTypes from '../_util/vue-types';
|
||||||
import { cloneElement } from '../_util/vnode';
|
import { cloneElement } from '../_util/vnode';
|
||||||
import type { PropType, VNode } from 'vue';
|
import type { PropType, VNode } from 'vue';
|
||||||
import { ref, defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { tuple } from '../_util/type';
|
import { tuple } from '../_util/type';
|
||||||
import type { Direction, SizeType } from '../config-provider';
|
import type { Direction, SizeType } from '../config-provider';
|
||||||
import type { MouseEventHandler } from '../_util/EventInterface';
|
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'];
|
const ClearableInputType = ['text', 'input'];
|
||||||
|
|
||||||
|
@ -34,20 +37,13 @@ export default defineComponent({
|
||||||
bordered: { type: Boolean, default: true },
|
bordered: { type: Boolean, default: true },
|
||||||
triggerFocus: { type: Function as PropType<() => void> },
|
triggerFocus: { type: Function as PropType<() => void> },
|
||||||
hidden: Boolean,
|
hidden: Boolean,
|
||||||
|
status: String as PropType<InputStatus>,
|
||||||
},
|
},
|
||||||
setup(props, { slots, attrs }) {
|
setup(props, { slots, attrs }) {
|
||||||
const containerRef = ref();
|
const statusContext = FormItemInputContext.useInject();
|
||||||
const onInputMouseUp: MouseEventHandler = e => {
|
|
||||||
if (containerRef.value?.contains(e.target as Element)) {
|
|
||||||
const { triggerFocus } = props;
|
|
||||||
triggerFocus?.();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const renderClearIcon = (prefixCls: string) => {
|
const renderClearIcon = (prefixCls: string) => {
|
||||||
const { allowClear, value, disabled, readonly, handleReset, suffix = slots.suffix } = props;
|
const { value, disabled, readonly, handleReset, suffix = slots.suffix } = props;
|
||||||
if (!allowClear) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const needClear = !disabled && !readonly && value;
|
const needClear = !disabled && !readonly && value;
|
||||||
const className = `${prefixCls}-clear-icon`;
|
const className = `${prefixCls}-clear-icon`;
|
||||||
return (
|
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 renderTextAreaWithClearIcon = (prefixCls: string, element: VNode) => {
|
||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
|
@ -190,9 +69,13 @@ export default defineComponent({
|
||||||
direction,
|
direction,
|
||||||
bordered,
|
bordered,
|
||||||
hidden,
|
hidden,
|
||||||
|
status: customStatus,
|
||||||
addonAfter = slots.addonAfter,
|
addonAfter = slots.addonAfter,
|
||||||
addonBefore = slots.addonBefore,
|
addonBefore = slots.addonBefore,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const { status: contextStatus, hasFeedback } = statusContext;
|
||||||
|
|
||||||
if (!allowClear) {
|
if (!allowClear) {
|
||||||
return cloneElement(element, {
|
return cloneElement(element, {
|
||||||
value,
|
value,
|
||||||
|
@ -201,6 +84,11 @@ export default defineComponent({
|
||||||
const affixWrapperCls = classNames(
|
const affixWrapperCls = classNames(
|
||||||
`${prefixCls}-affix-wrapper`,
|
`${prefixCls}-affix-wrapper`,
|
||||||
`${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
|
`${prefixCls}-affix-wrapper-textarea-with-clear-btn`,
|
||||||
|
getStatusClassNames(
|
||||||
|
`${prefixCls}-affix-wrapper`,
|
||||||
|
getMergedStatus(contextStatus, customStatus),
|
||||||
|
hasFeedback,
|
||||||
|
),
|
||||||
{
|
{
|
||||||
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
|
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
|
||||||
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
|
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
|
||||||
|
@ -209,7 +97,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<span class={affixWrapperCls} style={attrs.style} hidden={hidden}>
|
<span className={affixWrapperCls} style={attrs.style} hidden={hidden}>
|
||||||
{cloneElement(element, {
|
{cloneElement(element, {
|
||||||
style: null,
|
style: null,
|
||||||
value,
|
value,
|
||||||
|
@ -224,7 +112,7 @@ export default defineComponent({
|
||||||
if (inputType === ClearableInputType[0]) {
|
if (inputType === ClearableInputType[0]) {
|
||||||
return renderTextAreaWithClearIcon(prefixCls, element);
|
return renderTextAreaWithClearIcon(prefixCls, element);
|
||||||
}
|
}
|
||||||
return renderInputWithLabel(prefixCls, renderLabeledIcon(prefixCls, element));
|
return null;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, defineComponent } from 'vue';
|
||||||
import type { SizeType } from '../config-provider';
|
import type { SizeType } from '../config-provider';
|
||||||
|
import { FormItemInputContext } from '../form/FormItemContext';
|
||||||
import type { FocusEventHandler, MouseEventHandler } from '../_util/EventInterface';
|
import type { FocusEventHandler, MouseEventHandler } from '../_util/EventInterface';
|
||||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
|
|
||||||
|
@ -17,6 +18,10 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const { prefixCls, direction } = useConfigInject('input-group', props);
|
const { prefixCls, direction } = useConfigInject('input-group', props);
|
||||||
|
const formItemInputContext = FormItemInputContext.useInject();
|
||||||
|
FormItemInputContext.useProvide(formItemInputContext, {
|
||||||
|
isFormItemInput: false,
|
||||||
|
});
|
||||||
const cls = computed(() => {
|
const cls = computed(() => {
|
||||||
const pre = prefixCls.value;
|
const pre = prefixCls.value;
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,108 +1,18 @@
|
||||||
import type { VNode } from 'vue';
|
import { onBeforeUpdate, computed, onBeforeUnmount, onMounted, ref, defineComponent } from 'vue';
|
||||||
import {
|
|
||||||
getCurrentInstance,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onMounted,
|
|
||||||
watch,
|
|
||||||
ref,
|
|
||||||
defineComponent,
|
|
||||||
nextTick,
|
|
||||||
withDirectives,
|
|
||||||
} from 'vue';
|
|
||||||
import antInputDirective from '../_util/antInputDirective';
|
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import type { InputProps } from './inputProps';
|
import {
|
||||||
import inputProps from './inputProps';
|
FormItemInputContext,
|
||||||
import { getInputClassName } from './util';
|
NoFormStatus,
|
||||||
import ClearableLabeledInput from './ClearableLabeledInput';
|
useInjectFormItemContext,
|
||||||
import { useInjectFormItemContext } from '../form/FormItemContext';
|
} from '../form/FormItemContext';
|
||||||
import omit from '../_util/omit';
|
|
||||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
import type { ChangeEvent, FocusEventHandler } from '../_util/EventInterface';
|
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||||
|
import type { InputFocusOptions } from '../vc-input/utils/commonUtils';
|
||||||
export function fixControlledValue(value: string | number) {
|
import { hasPrefixSuffix } from '../vc-input/utils/commonUtils';
|
||||||
if (typeof value === 'undefined' || value === null) {
|
import VcInput from '../vc-input/Input';
|
||||||
return '';
|
import inputProps from './inputProps';
|
||||||
}
|
import omit from '../_util/omit';
|
||||||
return String(value);
|
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AInput',
|
name: 'AInput',
|
||||||
|
@ -110,45 +20,13 @@ export default defineComponent({
|
||||||
props: inputProps(),
|
props: inputProps(),
|
||||||
setup(props, { slots, attrs, expose, emit }) {
|
setup(props, { slots, attrs, expose, emit }) {
|
||||||
const inputRef = ref();
|
const inputRef = ref();
|
||||||
const clearableInputRef = ref();
|
|
||||||
let removePasswordTimeout: any;
|
|
||||||
const formItemContext = useInjectFormItemContext();
|
const formItemContext = useInjectFormItemContext();
|
||||||
|
const formItemInputContext = FormItemInputContext.useInject();
|
||||||
|
const mergedStatus = computed(() => getMergedStatus(formItemInputContext.status, props.status));
|
||||||
const { direction, prefixCls, size, autocomplete } = useConfigInject('input', props);
|
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) => {
|
const focus = (option?: InputFocusOptions) => {
|
||||||
triggerFocus(inputRef.value, option);
|
inputRef.value?.focus(option);
|
||||||
};
|
};
|
||||||
|
|
||||||
const blur = () => {
|
const blur = () => {
|
||||||
|
@ -171,28 +49,42 @@ export default defineComponent({
|
||||||
focus,
|
focus,
|
||||||
blur,
|
blur,
|
||||||
input: inputRef,
|
input: inputRef,
|
||||||
stateValue,
|
|
||||||
setSelectionRange,
|
setSelectionRange,
|
||||||
select,
|
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 handleBlur = (e: FocusEvent) => {
|
||||||
const { onFocus } = props;
|
removePasswordTimeout();
|
||||||
focused.value = true;
|
emit('blur', e);
|
||||||
onFocus?.(e);
|
|
||||||
nextTick(() => {
|
|
||||||
clearPasswordValueAttribute();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBlur: FocusEventHandler = e => {
|
const handleFocus = (e: FocusEvent) => {
|
||||||
const { onBlur } = props;
|
removePasswordTimeout();
|
||||||
focused.value = false;
|
emit('focus', e);
|
||||||
onBlur?.(e);
|
|
||||||
formItemContext.onFieldBlur();
|
|
||||||
nextTick(() => {
|
|
||||||
clearPasswordValueAttribute();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const triggerChange = (e: Event) => {
|
const triggerChange = (e: Event) => {
|
||||||
|
@ -201,168 +93,74 @@ export default defineComponent({
|
||||||
emit('input', e);
|
emit('input', e);
|
||||||
formItemContext.onFieldChange();
|
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 () => {
|
return () => {
|
||||||
const inputProps: any = {
|
const { hasFeedback, feedbackIcon } = formItemInputContext;
|
||||||
...attrs,
|
const {
|
||||||
...props,
|
allowClear,
|
||||||
prefixCls: prefixCls.value,
|
bordered = true,
|
||||||
inputType: 'input',
|
prefix = slots.prefix?.(),
|
||||||
value: fixControlledValue(stateValue.value),
|
suffix = slots.suffix?.(),
|
||||||
handleReset,
|
addonAfter = slots.addonAfter?.(),
|
||||||
focused: focused.value && !props.disabled,
|
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 (
|
return (
|
||||||
<ClearableLabeledInput
|
<VcInput
|
||||||
{...omit(inputProps, ['element', 'valueModifiers', 'suffix', 'showCount'])}
|
{...attrs}
|
||||||
ref={clearableInputRef}
|
{...omit(rest, ['onUpdate:value', 'onChange', 'onInput'])}
|
||||||
v-slots={{ ...slots, element: renderInput, suffix: renderShowCountSuffix }}
|
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 classNames from '../_util/classNames';
|
||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
||||||
import inputProps from './inputProps';
|
|
||||||
import Button from '../button';
|
import Button from '../button';
|
||||||
import { cloneElement } from '../_util/vnode';
|
import { cloneElement } from '../_util/vnode';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import isPlainObject from 'lodash-es/isPlainObject';
|
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 useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
import omit from '../_util/omit';
|
import omit from '../_util/omit';
|
||||||
import isMobile from '../_util/isMobile';
|
import isMobile from '../_util/isMobile';
|
||||||
|
import inputProps from './inputProps';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AInputSearch',
|
name: 'AInputSearch',
|
||||||
|
@ -29,6 +33,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup(props, { slots, attrs, expose, emit }) {
|
setup(props, { slots, attrs, expose, emit }) {
|
||||||
const inputRef = ref();
|
const inputRef = ref();
|
||||||
|
const composedRef = ref(false);
|
||||||
const focus = () => {
|
const focus = () => {
|
||||||
inputRef.value?.focus();
|
inputRef.value?.focus();
|
||||||
};
|
};
|
||||||
|
@ -55,12 +60,28 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSearch = (e: MouseEvent | KeyboardEvent) => {
|
const onSearch = (e: MouseEvent | KeyboardEvent) => {
|
||||||
emit('search', inputRef.value?.stateValue, e);
|
emit('search', inputRef.value?.input?.stateValue, e);
|
||||||
if (!isMobile.tablet) {
|
if (!isMobile.tablet) {
|
||||||
inputRef.value.focus();
|
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 { prefixCls, getPrefixCls, direction, size } = useConfigInject('input-search', props);
|
||||||
const inputPrefixCls = computed(() => getPrefixCls('input', props.inputPrefixCls));
|
const inputPrefixCls = computed(() => getPrefixCls('input', props.inputPrefixCls));
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -133,7 +154,9 @@ export default defineComponent({
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
{...omit(restProps, ['onUpdate:value', 'onSearch', 'enterButton'])}
|
{...omit(restProps, ['onUpdate:value', 'onSearch', 'enterButton'])}
|
||||||
{...attrs}
|
{...attrs}
|
||||||
onPressEnter={onSearch}
|
onPressEnter={onPressEnter}
|
||||||
|
onCompositionstart={handleOnCompositionStart}
|
||||||
|
onCompositionend={handleOnCompositionEnd}
|
||||||
size={size.value}
|
size={size.value}
|
||||||
prefixCls={inputPrefixCls.value}
|
prefixCls={inputPrefixCls.value}
|
||||||
addonAfter={button}
|
addonAfter={button}
|
||||||
|
|
|
@ -11,14 +11,15 @@ import {
|
||||||
import ClearableLabeledInput from './ClearableLabeledInput';
|
import ClearableLabeledInput from './ClearableLabeledInput';
|
||||||
import ResizableTextArea from './ResizableTextArea';
|
import ResizableTextArea from './ResizableTextArea';
|
||||||
import { textAreaProps } from './inputProps';
|
import { textAreaProps } from './inputProps';
|
||||||
import type { InputFocusOptions } from './Input';
|
import type { InputFocusOptions } from '../vc-input/utils/commonUtils';
|
||||||
import { fixControlledValue, resolveOnChange, triggerFocus } from './Input';
|
import { fixControlledValue, resolveOnChange, triggerFocus } from '../vc-input/utils/commonUtils';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import { useInjectFormItemContext } from '../form/FormItemContext';
|
import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItemContext';
|
||||||
import type { FocusEventHandler } from '../_util/EventInterface';
|
import type { FocusEventHandler } from '../_util/EventInterface';
|
||||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
import omit from '../_util/omit';
|
import omit from '../_util/omit';
|
||||||
import type { VueNode } from '../_util/type';
|
import type { VueNode } from '../_util/type';
|
||||||
|
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||||
|
|
||||||
function fixEmojiLength(value: string, maxLength: number) {
|
function fixEmojiLength(value: string, maxLength: number) {
|
||||||
return [...(value || '')].slice(0, maxLength).join('');
|
return [...(value || '')].slice(0, maxLength).join('');
|
||||||
|
@ -50,6 +51,8 @@ export default defineComponent({
|
||||||
props: textAreaProps(),
|
props: textAreaProps(),
|
||||||
setup(props, { attrs, expose, emit }) {
|
setup(props, { attrs, expose, emit }) {
|
||||||
const formItemContext = useInjectFormItemContext();
|
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 stateValue = ref(props.value === undefined ? props.defaultValue : props.value);
|
||||||
const resizableTextArea = ref();
|
const resizableTextArea = ref();
|
||||||
const mergedValue = ref('');
|
const mergedValue = ref('');
|
||||||
|
@ -186,12 +189,15 @@ export default defineComponent({
|
||||||
...omit(props, ['allowClear']),
|
...omit(props, ['allowClear']),
|
||||||
...attrs,
|
...attrs,
|
||||||
style: showCount.value ? {} : style,
|
style: showCount.value ? {} : style,
|
||||||
class: {
|
class: [
|
||||||
[`${prefixCls.value}-borderless`]: !bordered,
|
{
|
||||||
[`${customClass}`]: customClass && !showCount.value,
|
[`${prefixCls.value}-borderless`]: !bordered,
|
||||||
[`${prefixCls.value}-sm`]: size.value === 'small',
|
[`${customClass}`]: customClass && !showCount.value,
|
||||||
[`${prefixCls.value}-lg`]: size.value === 'large',
|
[`${prefixCls.value}-sm`]: size.value === 'small',
|
||||||
},
|
[`${prefixCls.value}-lg`]: size.value === 'large',
|
||||||
|
},
|
||||||
|
getStatusClassNames(prefixCls.value, mergedStatus.value),
|
||||||
|
],
|
||||||
showCount: null,
|
showCount: null,
|
||||||
prefixCls: prefixCls.value,
|
prefixCls: prefixCls.value,
|
||||||
onInput: handleChange,
|
onInput: handleChange,
|
||||||
|
@ -259,10 +265,11 @@ export default defineComponent({
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
value={mergedValue.value}
|
value={mergedValue.value}
|
||||||
v-slots={{ element: renderTextArea }}
|
v-slots={{ element: renderTextArea }}
|
||||||
|
status={props.status}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (showCount.value) {
|
if (showCount.value || formItemInputContext.hasFeedback) {
|
||||||
const valueLength = [...mergedValue.value].length;
|
const valueLength = [...mergedValue.value].length;
|
||||||
let dataCount: VueNode = '';
|
let dataCount: VueNode = '';
|
||||||
if (typeof showCount.value === 'object') {
|
if (typeof showCount.value === 'object') {
|
||||||
|
@ -277,6 +284,8 @@ export default defineComponent({
|
||||||
`${prefixCls.value}-textarea`,
|
`${prefixCls.value}-textarea`,
|
||||||
{
|
{
|
||||||
[`${prefixCls.value}-textarea-rtl`]: direction.value === 'rtl',
|
[`${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`,
|
`${prefixCls.value}-textarea-show-count`,
|
||||||
customClass,
|
customClass,
|
||||||
|
@ -285,6 +294,11 @@ export default defineComponent({
|
||||||
data-count={typeof dataCount !== 'object' ? dataCount : undefined}
|
data-count={typeof dataCount !== 'object' ? dataCount : undefined}
|
||||||
>
|
>
|
||||||
{textareaNode}
|
{textareaNode}
|
||||||
|
{formItemInputContext.hasFeedback && (
|
||||||
|
<span class={`${prefixCls.value}-textarea-suffix`}>
|
||||||
|
{formItemInputContext.feedbackIcon}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,26 @@ Basic usage example.
|
||||||
|
|
||||||
</docs>
|
</docs>
|
||||||
<template>
|
<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>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from 'vue';
|
import { watch, defineComponent, ref } from 'vue';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const value = ref<string>('');
|
const value = ref<string>('');
|
||||||
|
const value1 = ref<string>('');
|
||||||
|
watch(value, () => {
|
||||||
|
console.log(value.value);
|
||||||
|
});
|
||||||
|
watch(value1, () => {
|
||||||
|
console.log(value1.value);
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
|
value1,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@ Note: You don't need `Col` to control the width in the `compact` mode.
|
||||||
|
|
||||||
</docs>
|
</docs>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="site-input-group-wrapper">
|
||||||
<a-input-group size="large">
|
<a-input-group size="large">
|
||||||
<a-row :gutter="8">
|
<a-row :gutter="8">
|
||||||
<a-col :span="5">
|
<a-col :span="5">
|
||||||
|
@ -79,13 +79,15 @@ Note: You don't need `Col` to control the width in the `compact` mode.
|
||||||
/>
|
/>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="value13"
|
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="~"
|
placeholder="~"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="value14"
|
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"
|
placeholder="Maximum"
|
||||||
/>
|
/>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
@ -221,3 +223,29 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</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 />
|
<show-count />
|
||||||
<textarea-resize />
|
<textarea-resize />
|
||||||
<borderlessVue />
|
<borderlessVue />
|
||||||
|
<statusVue />
|
||||||
</demo-sort>
|
</demo-sort>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ import ShowCount from './show-count.vue';
|
||||||
import Addon from './addon.vue';
|
import Addon from './addon.vue';
|
||||||
import Tooltip from './tooltip.vue';
|
import Tooltip from './tooltip.vue';
|
||||||
import borderlessVue from './borderless.vue';
|
import borderlessVue from './borderless.vue';
|
||||||
|
import statusVue from './status.vue';
|
||||||
import CN from '../index.zh-CN.md';
|
import CN from '../index.zh-CN.md';
|
||||||
import US from '../index.en-US.md';
|
import US from '../index.en-US.md';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
@ -40,6 +42,7 @@ export default defineComponent({
|
||||||
CN,
|
CN,
|
||||||
US,
|
US,
|
||||||
components: {
|
components: {
|
||||||
|
statusVue,
|
||||||
Basic,
|
Basic,
|
||||||
AutosizeTextarea,
|
AutosizeTextarea,
|
||||||
Presuffix,
|
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 | | |
|
| 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 | | |
|
| allowClear | allow to remove input content with clear icon | boolean | | |
|
||||||
| bordered | Whether has border style | boolean | true | 4.5.0 |
|
| 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 | | |
|
| defaultValue | The initial input content | string | | |
|
||||||
| disabled | Whether the input is disabled. | boolean | false | |
|
| disabled | Whether the input is disabled. | boolean | false | |
|
||||||
| id | The ID for input | string | | |
|
| id | The ID for input | string | | |
|
||||||
| maxlength | max length | number | | 1.5.0 |
|
| maxlength | max length | number | | 1.5.0 |
|
||||||
| prefix | The prefix icon for the Input. | string\|slot | | |
|
| prefix | The prefix icon for the Input. | string\|slot | | |
|
||||||
| showCount | Whether show text count | boolean | false | 3.0 |
|
| 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 | | |
|
| 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` | |
|
| 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 | | |
|
| 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 | | |
|
| addonBefore | 带标签的 input,设置前置标签 | string\|slot | | |
|
||||||
| allowClear | 可以点击清除图标删除内容 | boolean | | |
|
| allowClear | 可以点击清除图标删除内容 | boolean | | |
|
||||||
| bordered | 是否有边框 | boolean | true | 3.0 |
|
| bordered | 是否有边框 | boolean | true | 3.0 |
|
||||||
|
| clearIcon | 自定义清除图标 (allowClear 为 true 时生效) | slot | `<CloseCircleFilled />` | 3.3.0 |
|
||||||
| defaultValue | 输入框默认内容 | string | | |
|
| defaultValue | 输入框默认内容 | string | | |
|
||||||
| disabled | 是否禁用状态,默认为 false | boolean | false | |
|
| disabled | 是否禁用状态,默认为 false | boolean | false | |
|
||||||
| id | 输入框的 id | string | | |
|
| id | 输入框的 id | string | | |
|
||||||
| maxlength | 最大长度 | number | | 1.5.0 |
|
| maxlength | 最大长度 | number | | 1.5.0 |
|
||||||
| prefix | 带有前缀图标的 input | string\|slot | | |
|
| prefix | 带有前缀图标的 input | string\|slot | | |
|
||||||
| showCount | 是否展示字数 | boolean | false | 3.0 |
|
| 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 | | |
|
| 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` | |
|
| 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 | | |
|
| value(v-model) | 输入框内容 | string | | |
|
||||||
|
|
|
@ -1,92 +1,25 @@
|
||||||
import type { ExtractPropTypes, PropType } from 'vue';
|
import type { ExtractPropTypes, PropType } from 'vue';
|
||||||
import PropTypes from '../_util/vue-types';
|
|
||||||
import type { SizeType } from '../config-provider';
|
|
||||||
import omit from '../_util/omit';
|
import omit from '../_util/omit';
|
||||||
import type { LiteralUnion, VueNode } from '../_util/type';
|
import type { VueNode } from '../_util/type';
|
||||||
import type {
|
import type { CompositionEventHandler } from '../_util/EventInterface';
|
||||||
ChangeEventHandler,
|
import { inputProps as vcInputProps } from '../vc-input/inputProps';
|
||||||
CompositionEventHandler,
|
|
||||||
FocusEventHandler,
|
|
||||||
KeyboardEventHandler,
|
|
||||||
} from '../_util/EventInterface';
|
|
||||||
export const inputDefaultValue = Symbol() as unknown as string;
|
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 {
|
export interface AutoSizeType {
|
||||||
minRows?: number;
|
minRows?: number;
|
||||||
maxRows?: 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 {
|
export interface ShowCountProps {
|
||||||
formatter: (args: { count: number; maxlength?: number }) => VueNode;
|
formatter: (args: { count: number; maxlength?: number }) => VueNode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: none;
|
flex: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
> *:not(:last-child) {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-show-count-suffix {
|
&-show-count-suffix {
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
@input-prefix-cls: ~'@{ant-prefix}-input';
|
@input-prefix-cls: ~'@{ant-prefix}-input';
|
||||||
|
|
||||||
// ========================= 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;
|
margin: 0;
|
||||||
color: @disabled-color;
|
color: @disabled-color;
|
||||||
font-size: @font-size-sm;
|
font-size: @font-size-sm;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
@import './mixin';
|
@import './mixin';
|
||||||
@import './affix';
|
@import './affix';
|
||||||
@import './allow-clear';
|
@import './allow-clear';
|
||||||
|
@import './status';
|
||||||
|
|
||||||
@input-prefix-cls: ~'@{ant-prefix}-input';
|
@input-prefix-cls: ~'@{ant-prefix}-input';
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-password-icon {
|
&-password-icon.@{iconfont-css-prefix} {
|
||||||
color: @text-color-secondary;
|
color: @text-color-secondary;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
@ -60,6 +61,23 @@
|
||||||
content: attr(data-count);
|
content: attr(data-count);
|
||||||
pointer-events: none;
|
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 '../../style/index.less';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
|
// deps-lint-skip: form
|
||||||
// style dependencies
|
// style dependencies
|
||||||
import '../../button/style';
|
import '../../button/style';
|
||||||
|
|
|
@ -30,14 +30,14 @@
|
||||||
border-color: @hoverBorderColor;
|
border-color: @hoverBorderColor;
|
||||||
box-shadow: @input-outline-offset @outline-blur-size @outline-width @outlineColor;
|
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;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// == when hover
|
// == when hover
|
||||||
.hover(@color: @input-hover-border-color) {
|
.hover(@color: @input-hover-border-color) {
|
||||||
border-color: @color;
|
border-color: @color;
|
||||||
border-right-width: @border-width-base !important;
|
border-right-width: @border-width-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled() {
|
.disabled() {
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
background-color: @input-bg;
|
background-color: @input-bg;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border: @border-width-base @border-style-base @input-border-color;
|
border: @border-width-base @border-style-base @input-border-color;
|
||||||
border-radius: @border-radius-base;
|
border-radius: @control-border-radius;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
.placeholder(); // Reset placeholder
|
.placeholder(); // Reset placeholder
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: @input-addon-bg;
|
background-color: @input-addon-bg;
|
||||||
border: @border-width-base @border-style-base @input-border-color;
|
border: @border-width-base @border-style-base @input-border-color;
|
||||||
border-radius: @border-radius-base;
|
border-radius: @control-border-radius;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
|
||||||
// Reset Select's style in addon
|
// Reset Select's style in addon
|
||||||
|
@ -297,8 +297,8 @@
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
.@{ant-prefix}-input-search & {
|
.@{ant-prefix}-input-search & {
|
||||||
border-top-left-radius: @border-radius-base;
|
border-top-left-radius: @control-border-radius;
|
||||||
border-bottom-left-radius: @border-radius-base;
|
border-bottom-left-radius: @control-border-radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,8 +384,8 @@
|
||||||
& > .@{ant-prefix}-select:first-child > .@{ant-prefix}-select-selector,
|
& > .@{ant-prefix}-select:first-child > .@{ant-prefix}-select-selector,
|
||||||
& > .@{ant-prefix}-select-auto-complete:first-child .@{ant-prefix}-input,
|
& > .@{ant-prefix}-select-auto-complete:first-child .@{ant-prefix}-input,
|
||||||
& > .@{ant-prefix}-cascader-picker:first-child .@{ant-prefix}-input {
|
& > .@{ant-prefix}-cascader-picker:first-child .@{ant-prefix}-input {
|
||||||
border-top-left-radius: @border-radius-base;
|
border-top-left-radius: @control-border-radius;
|
||||||
border-bottom-left-radius: @border-radius-base;
|
border-bottom-left-radius: @control-border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > *:last-child,
|
& > *:last-child,
|
||||||
|
@ -393,8 +393,8 @@
|
||||||
& > .@{ant-prefix}-cascader-picker:last-child .@{ant-prefix}-input,
|
& > .@{ant-prefix}-cascader-picker:last-child .@{ant-prefix}-input,
|
||||||
& > .@{ant-prefix}-cascader-picker-focused:last-child .@{ant-prefix}-input {
|
& > .@{ant-prefix}-cascader-picker-focused:last-child .@{ant-prefix}-input {
|
||||||
border-right-width: @border-width-base;
|
border-right-width: @border-width-base;
|
||||||
border-top-right-radius: @border-radius-base;
|
border-top-right-radius: @control-border-radius;
|
||||||
border-bottom-right-radius: @border-radius-base;
|
border-bottom-right-radius: @control-border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/12493
|
// https://github.com/ant-design/ant-design/issues/12493
|
||||||
|
@ -416,9 +416,55 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .@{ant-prefix}-input {
|
& > .@{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';
|
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) => {
|
const isValid = (value: any) => {
|
||||||
return (
|
return (
|
||||||
value !== undefined &&
|
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