mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-26 09:50:33 +01:00
chore(Form): catch-up with v2 changes (#2165)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -157,15 +157,12 @@ async function _validate(opts: { name?: string | string[], silent?: boolean, nes
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onSubmit(payload: Event) {
|
async function onSubmit(payload: Event) {
|
||||||
const event = payload as SubmitEvent
|
const event = payload as FormSubmitEvent<any>
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _validate({ nested: true })
|
await _validate({ nested: true })
|
||||||
const submitEvent: FormSubmitEvent<any> = {
|
event.data = props.state
|
||||||
...event,
|
emits('submit', event)
|
||||||
data: props.state
|
|
||||||
}
|
|
||||||
emits('submit', submitEvent)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!(error instanceof FormValidationException)) {
|
if (!(error instanceof FormValidationException)) {
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export interface FormFieldSlots {
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, inject, provide, type Ref, useId } from 'vue'
|
import { computed, ref, inject, provide, type Ref, useId } from 'vue'
|
||||||
import { Label } from 'radix-vue'
|
import { Label } from 'radix-vue'
|
||||||
import { formFieldInjectionKey } from '../composables/useFormField'
|
import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFormField'
|
||||||
import type { FormError, FormFieldInjectedOptions } from '../types/form'
|
import type { FormError, FormFieldInjectedOptions } from '../types/form'
|
||||||
|
|
||||||
const props = defineProps<FormFieldProps>()
|
const props = defineProps<FormFieldProps>()
|
||||||
@@ -55,8 +55,9 @@ const error = computed(() => props.error || formErrors?.value?.find(error => err
|
|||||||
|
|
||||||
const id = ref(useId())
|
const id = ref(useId())
|
||||||
|
|
||||||
|
provide(inputIdInjectionKey, id)
|
||||||
|
|
||||||
provide(formFieldInjectionKey, computed(() => ({
|
provide(formFieldInjectionKey, computed(() => ({
|
||||||
id: id.value,
|
|
||||||
error: error.value,
|
error: error.value,
|
||||||
name: props.name,
|
name: props.name,
|
||||||
size: props.size,
|
size: props.size,
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const slots = defineSlots<RadioGroupSlots<T>>()
|
|||||||
|
|
||||||
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emits)
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'loop', 'required'), emits)
|
||||||
|
|
||||||
const { emitFormChange, emitFormInput, color, name, size, id: _id, disabled } = useFormField<RadioGroupProps<T>>(props)
|
const { emitFormChange, emitFormInput, color, name, size, id: _id, disabled } = useFormField<RadioGroupProps<T>>(props, { bind: false })
|
||||||
const id = _id.value ?? useId()
|
const id = _id.value ?? useId()
|
||||||
|
|
||||||
const ui = computed(() => radioGroup({
|
const ui = computed(() => radioGroup({
|
||||||
|
|||||||
@@ -132,6 +132,8 @@ function autoResize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textareaRef.value.rows = props.rows
|
textareaRef.value.rows = props.rows
|
||||||
|
const overflow = textareaRef.value.style.overflow
|
||||||
|
textareaRef.value.style.overflow = 'hidden'
|
||||||
|
|
||||||
const styles = window.getComputedStyle(textareaRef.value)
|
const styles = window.getComputedStyle(textareaRef.value)
|
||||||
const paddingTop = Number.parseInt(styles.paddingTop)
|
const paddingTop = Number.parseInt(styles.paddingTop)
|
||||||
@@ -144,6 +146,8 @@ function autoResize() {
|
|||||||
if (newRows > props.rows) {
|
if (newRows > props.rows) {
|
||||||
textareaRef.value.rows = props.maxrows ? Math.min(newRows, props.maxrows) : newRows
|
textareaRef.value.rows = props.maxrows ? Math.min(newRows, props.maxrows) : newRows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textareaRef.value.style.overflow = overflow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,22 +18,26 @@ type Props<T> = {
|
|||||||
export const formOptionsInjectionKey: InjectionKey<ComputedRef<FormInjectedOptions>> = Symbol('nuxt-ui.form-options')
|
export const formOptionsInjectionKey: InjectionKey<ComputedRef<FormInjectedOptions>> = Symbol('nuxt-ui.form-options')
|
||||||
export const formBusInjectionKey: InjectionKey<UseEventBusReturn<FormEvent, string>> = Symbol('nuxt-ui.form-events')
|
export const formBusInjectionKey: InjectionKey<UseEventBusReturn<FormEvent, string>> = Symbol('nuxt-ui.form-events')
|
||||||
export const formFieldInjectionKey: InjectionKey<ComputedRef<FormFieldInjectedOptions<FormFieldProps>>> = Symbol('nuxt-ui.form-field')
|
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, string>>> = Symbol('nuxt-ui.form-inputs')
|
export const formInputsInjectionKey: InjectionKey<Ref<Record<string, string>>> = Symbol('nuxt-ui.form-inputs')
|
||||||
|
|
||||||
export function useFormField<T>(props?: Props<T>) {
|
export function useFormField<T>(props?: Props<T>, opts?: { bind?: boolean }) {
|
||||||
const formOptions = inject(formOptionsInjectionKey, undefined)
|
const formOptions = inject(formOptionsInjectionKey, undefined)
|
||||||
const formBus = inject(formBusInjectionKey, undefined)
|
const formBus = inject(formBusInjectionKey, undefined)
|
||||||
const formField = inject(formFieldInjectionKey, undefined)
|
const formField = inject(formFieldInjectionKey, undefined)
|
||||||
const formInputs = inject(formInputsInjectionKey, undefined)
|
const formInputs = inject(formInputsInjectionKey, undefined)
|
||||||
|
const inputId = inject(inputIdInjectionKey, undefined)
|
||||||
|
|
||||||
if (formField) {
|
if (formField && inputId) {
|
||||||
if (props?.id) {
|
if (opts?.bind === false || props?.legend) {
|
||||||
// Updates for="..." attribute on label if props.id is provided
|
// Removes for="..." attribute on label for RadioGroup and alike.
|
||||||
formField.value.id = props?.id
|
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) {
|
||||||
if (formInputs && formField.value.name) {
|
formInputs.value[formField.value.name] = inputId.value
|
||||||
formInputs.value[formField.value.name] = formField.value.id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +66,7 @@ export function useFormField<T>(props?: Props<T>) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: computed(() => props?.id ?? formField?.value.id),
|
id: computed(() => props?.id ?? inputId?.value),
|
||||||
name: computed(() => props?.name ?? formField?.value.name),
|
name: computed(() => props?.name ?? formField?.value.name),
|
||||||
size: computed(() => props?.size ?? formField?.value.size),
|
size: computed(() => props?.size ?? formField?.value.size),
|
||||||
color: computed(() => formField?.value.error ? 'error' : props?.color),
|
color: computed(() => formField?.value.error ? 'error' : props?.color),
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ export interface FormInjectedOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FormFieldInjectedOptions<T> {
|
export interface FormFieldInjectedOptions<T> {
|
||||||
id: string
|
|
||||||
name?: string
|
name?: string
|
||||||
size?: GetObjectField<T, 'size'>
|
size?: GetObjectField<T, 'size'>
|
||||||
error?: string | boolean
|
error?: string | boolean
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ describe('Form', () => {
|
|||||||
expect(passwordField.text()).toBe('Required')
|
expect(passwordField.text()).toBe('Required')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('valid submit works', async () => {
|
test('validate on submit works', async () => {
|
||||||
state.email = 'bob@dylan.com'
|
state.email = 'bob@dylan.com'
|
||||||
state.password = 'strongpassword'
|
state.password = 'strongpassword'
|
||||||
|
|
||||||
@@ -236,6 +236,7 @@ describe('Form', () => {
|
|||||||
|
|
||||||
expect(wrapper.setupState.onSubmit).toHaveBeenCalledTimes(1)
|
expect(wrapper.setupState.onSubmit).toHaveBeenCalledTimes(1)
|
||||||
expect(wrapper.setupState.onSubmit).toHaveBeenCalledWith(expect.objectContaining({
|
expect(wrapper.setupState.onSubmit).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
type: 'submit',
|
||||||
data: {
|
data: {
|
||||||
email: 'bob@dylan.com',
|
email: 'bob@dylan.com',
|
||||||
password: 'strongpassword'
|
password: 'strongpassword'
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ describe('RadioGroup', () => {
|
|||||||
items: ['Option 1', 'Option 2']
|
items: ['Option 1', 'Option 2']
|
||||||
},
|
},
|
||||||
slotTemplate: `
|
slotTemplate: `
|
||||||
<UFormField name="value">
|
<UFormField name="value" label="Radio group">
|
||||||
<URadioGroup id="input" v-model="state.value" :items="items" />
|
<URadioGroup id="input" v-model="state.value" :items="items" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
`
|
`
|
||||||
@@ -107,5 +107,11 @@ describe('RadioGroup', () => {
|
|||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.text()).not.toContain('Error message')
|
expect(wrapper.text()).not.toContain('Error message')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('no label for=... on FormField', async () => {
|
||||||
|
const { wrapper } = await createForm()
|
||||||
|
const formFieldLabel = wrapper.findAll('label').map(label => label.attributes()).filter(label => !label.for?.includes('Option'))[0]
|
||||||
|
expect(formFieldLabel.for).toBeUndefined()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user