-
Notifications
You must be signed in to change notification settings - Fork 700
/
Copy pathuseFormField.ts
93 lines (80 loc) · 3.7 KB
/
useFormField.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { inject, computed, type InjectionKey, type Ref, type ComputedRef } from 'vue'
import { type UseEventBusReturn, useDebounceFn } from '@vueuse/core'
import type { FormFieldProps } from '../types'
import type { FormEvent, FormInputEvents, FormFieldInjectedOptions, FormInjectedOptions } from '../types/form'
import type { GetObjectField } from '../types/utils'
type Props<T> = {
id?: string
name?: string
size?: GetObjectField<T, 'size'>
color?: GetObjectField<T, 'color'>
highlight?: boolean
disabled?: boolean
}
export const formOptionsInjectionKey: InjectionKey<ComputedRef<FormInjectedOptions>> = Symbol('nuxt-ui.form-options')
export const formBusInjectionKey: InjectionKey<UseEventBusReturn<FormEvent<any>, string>> = Symbol('nuxt-ui.form-events')
export const formFieldInjectionKey: InjectionKey<ComputedRef<FormFieldInjectedOptions<FormFieldProps>>> = Symbol('nuxt-ui.form-field')
export const inputIdInjectionKey: InjectionKey<Ref<string | undefined>> = Symbol('nuxt-ui.input-id')
export const formInputsInjectionKey: InjectionKey<Ref<Record<string, { id?: string, pattern?: RegExp }>>> = Symbol('nuxt-ui.form-inputs')
export const formLoadingInjectionKey: InjectionKey<Readonly<Ref<boolean>>> = Symbol('nuxt-ui.form-loading')
export function useFormField<T>(props?: Props<T>, opts?: { bind?: boolean, deferInputValidation?: boolean }) {
const formOptions = inject(formOptionsInjectionKey, undefined)
const formBus = inject(formBusInjectionKey, undefined)
const formField = inject(formFieldInjectionKey, undefined)
const formInputs = inject(formInputsInjectionKey, undefined)
const inputId = inject(inputIdInjectionKey, undefined)
if (formField && inputId) {
if (opts?.bind === false) {
// Removes for="..." attribute on label for RadioGroup and alike.
inputId.value = undefined
} else if (props?.id) {
// Updates for="..." attribute on label if props.id is provided.
inputId.value = props?.id
}
if (formInputs && formField.value.name && inputId.value) {
formInputs.value[formField.value.name] = { id: inputId.value, pattern: formField.value.errorPattern }
}
}
function emitFormEvent(type: FormInputEvents, name?: string, eager?: boolean) {
if (formBus && formField && name) {
formBus.emit({ type, name, eager })
}
}
function emitFormBlur() {
emitFormEvent('blur', formField?.value.name)
}
function emitFormFocus() {
emitFormEvent('focus', formField?.value.name)
}
function emitFormChange() {
emitFormEvent('change', formField?.value.name)
}
const emitFormInput = useDebounceFn(
() => {
emitFormEvent('input', formField?.value.name, !opts?.deferInputValidation || formField?.value.eagerValidation)
},
formField?.value.validateOnInputDelay ?? formOptions?.value.validateOnInputDelay ?? 0
)
return {
id: computed(() => props?.id ?? inputId?.value),
name: computed(() => props?.name ?? formField?.value.name),
size: computed(() => props?.size ?? formField?.value.size),
color: computed(() => formField?.value.error ? 'error' : props?.color),
highlight: computed(() => formField?.value.error ? true : props?.highlight),
disabled: computed(() => formOptions?.value.disabled || props?.disabled),
emitFormBlur,
emitFormInput,
emitFormChange,
emitFormFocus,
ariaAttrs: computed(() => {
if (!formField?.value) return
const descriptiveAttrs = ['error' as const, 'hint' as const, 'description' as const, 'help' as const]
.filter(type => formField?.value?.[type])
.map(type => `${formField?.value.ariaId}-${type}`) || []
return {
'aria-describedby': descriptiveAttrs.join(' '),
'aria-invalid': !!formField?.value.error
}
})
}
}