vuecssuiant-designantdreactantantd-vueenterprisefrontendui-designvue-antdvue-antd-uivue3vuecomponent
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
369 lines
9.6 KiB
369 lines
9.6 KiB
import type { VNode } from 'vue'; |
|
import { |
|
getCurrentInstance, |
|
onBeforeUnmount, |
|
onMounted, |
|
watch, |
|
ref, |
|
defineComponent, |
|
nextTick, |
|
withDirectives, |
|
} from 'vue'; |
|
import antInputDirective from '../_util/antInputDirective'; |
|
import classNames from '../_util/classNames'; |
|
import type { InputProps } from './inputProps'; |
|
import inputProps from './inputProps'; |
|
import { getInputClassName } from './util'; |
|
import ClearableLabeledInput from './ClearableLabeledInput'; |
|
import { useInjectFormItemContext } from '../form/FormItemContext'; |
|
import omit from '../_util/omit'; |
|
import useConfigInject from '../_util/hooks/useConfigInject'; |
|
import type { ChangeEvent, FocusEventHandler } from '../_util/EventInterface'; |
|
|
|
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); |
|
} |
|
} |
|
} |
|
|
|
export default defineComponent({ |
|
name: 'AInput', |
|
inheritAttrs: false, |
|
props: inputProps(), |
|
setup(props, { slots, attrs, expose, emit }) { |
|
const inputRef = ref(); |
|
const clearableInputRef = ref(); |
|
let removePasswordTimeout: any; |
|
const formItemContext = useInjectFormItemContext(); |
|
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); |
|
}; |
|
|
|
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 onFocus: FocusEventHandler = e => { |
|
const { onFocus } = props; |
|
focused.value = true; |
|
onFocus?.(e); |
|
nextTick(() => { |
|
clearPasswordValueAttribute(); |
|
}); |
|
}; |
|
|
|
const onBlur: FocusEventHandler = e => { |
|
const { onBlur } = props; |
|
focused.value = false; |
|
onBlur?.(e); |
|
formItemContext.onFieldBlur(); |
|
nextTick(() => { |
|
clearPasswordValueAttribute(); |
|
}); |
|
}; |
|
|
|
const triggerChange = (e: Event) => { |
|
emit('update:value', (e.target as HTMLInputElement).value); |
|
emit('change', e); |
|
emit('input', e); |
|
formItemContext.onFieldChange(); |
|
}; |
|
const instance = getCurrentInstance(); |
|
const setValue = (value: string | number, callback?: Function) => { |
|
if (stateValue.value === value) { |
|
return; |
|
} |
|
if (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, |
|
}; |
|
|
|
return ( |
|
<ClearableLabeledInput |
|
{...omit(inputProps, ['element', 'valueModifiers', 'suffix', 'showCount'])} |
|
ref={clearableInputRef} |
|
v-slots={{ ...slots, element: renderInput, suffix: renderShowCountSuffix }} |
|
/> |
|
); |
|
}; |
|
}, |
|
});
|
|
|