import { nextTick, onActivated, onMounted, ref, type ComputedRef, type Ref, useId, computed } from 'vue' import useAriaInvalid from './useAriaInvalid' import { useDebounceFn, useFocus } from '@vueuse/core' export default ( props: Readonly<{ id?: string autofocus?: boolean formatter?: (value: string, evt: Readonly) => string lazyFormatter?: boolean disabled?: boolean ariaInvalid?: boolean | 'grammar' | 'spelling' state?: boolean | null modelModifiers?: Partial> }>, modelValue: Ref ): { input: Ref computedId: ComputedRef computedAriaInvalid: ComputedRef<'grammar' | 'spelling' | 'true' | 'false' | undefined> onInput: (evt: Readonly) => void onChange: (evt: Readonly) => void onBlur: (evt: Readonly) => void focus: () => void blur: () => void } => { const computedId = computed(() => (props?.id) ? props.id : useId()) const input = ref(null) const internalUpdateModelValue = useDebounceFn( (value: string | number) => { modelValue.value = value }, () => (props.modelModifiers?.lazy === true ? 0 : 50), { maxWait: () => (props.modelModifiers?.lazy === true ? NaN : 500) } ) const updateModelValue = (value: string | number, force = false): void => { if (props.modelModifiers?.lazy === true && !force) { return } void internalUpdateModelValue(value) } const { focused } = useFocus(input, { initialValue: props.autofocus }) const _formatValue = (value: string, evt: Readonly, force = false): string => { if (props.formatter !== undefined && (!props.lazyFormatter || force)) { return props.formatter(value, evt) } return value } onMounted(() => { if (input?.value) { input.value.value = modelValue?.value?.toString() ?? '' } }) onActivated(() => { void nextTick(() => { if (props?.autofocus) { focused.value = true } }) }) const computedAriaInvalid = useAriaInvalid( () => props.ariaInvalid, () => props.state ) const onInput = (evt: Readonly): void => { const { value } = evt.target as HTMLInputElement const formattedValue = _formatValue(value, evt) if (evt.defaultPrevented) { evt.preventDefault() return } const nextModel = formattedValue updateModelValue(nextModel) } const onChange = (evt: Readonly): void => { const { value } = evt.target as HTMLInputElement const formattedValue = _formatValue(value, evt) if (evt.defaultPrevented) { evt.preventDefault() return } const nextModel = formattedValue if (modelValue?.value !== nextModel) { updateModelValue(formattedValue, true) } } const onBlur = (evt: Readonly): void => { if (!props.modelModifiers?.lazy && !props.lazyFormatter) { return } const { value } = evt.target as HTMLInputElement const formattedValue = _formatValue(value, evt, true) const nextModel = formattedValue if (modelValue?.value !== nextModel) { updateModelValue(formattedValue, true) } } const focus = (): void => { if (!props.disabled) { focused.value = true } } const blur = (): void => { if (!props.disabled) { focused.value = false } } return { input, computedId, computedAriaInvalid, onInput, onChange, onBlur, focus, blur } }