From c64c4cdea0bef3321b361455e43b7ff1422b0b2a Mon Sep 17 00:00:00 2001 From: Thilo Hettmer <47103966+ThiloHettmer@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:55:57 +0200 Subject: [PATCH 1/4] fix(FormField): resolve minor accessibility and rendering issues (#4515) Co-authored-by: Romain Hamel --- src/runtime/components/FormField.vue | 2 +- src/runtime/composables/useFormField.ts | 9 +++- test/components/FormField.spec.ts | 20 ++++++++ .../__snapshots__/Form-vue.spec.ts.snap | 48 +++++++++---------- .../__snapshots__/Form.spec.ts.snap | 48 +++++++++---------- .../__snapshots__/FormField-vue.spec.ts.snap | 4 +- .../__snapshots__/FormField.spec.ts.snap | 4 +- 7 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/runtime/components/FormField.vue b/src/runtime/components/FormField.vue index e8c719c5..2587871b 100644 --- a/src/runtime/components/FormField.vue +++ b/src/runtime/components/FormField.vue @@ -121,7 +121,7 @@ provide(formFieldInjectionKey, computed(() => ({ {{ error }} -
+
{{ help }} diff --git a/src/runtime/composables/useFormField.ts b/src/runtime/composables/useFormField.ts index 7a5328ff..404c1e29 100644 --- a/src/runtime/composables/useFormField.ts +++ b/src/runtime/composables/useFormField.ts @@ -89,10 +89,15 @@ export function useFormField(props?: Props, opts?: { bind?: boolean, defer .filter(type => formField?.value?.[type]) .map(type => `${formField?.value.ariaId}-${type}`) || [] - return { - 'aria-describedby': descriptiveAttrs.join(' '), + const attrs: Record = { 'aria-invalid': !!formField?.value.error } + + if (descriptiveAttrs.length > 0) { + attrs['aria-describedby'] = descriptiveAttrs.join(' ') + } + + return attrs }) } } diff --git a/test/components/FormField.spec.ts b/test/components/FormField.spec.ts index 3bb40189..a3c4ac25 100644 --- a/test/components/FormField.spec.ts +++ b/test/components/FormField.spec.ts @@ -157,5 +157,25 @@ describe('FormField', () => { const attr = wrapper.find('[aria-invalid=true]') expect(attr.exists()).toBe(true) }) + + test('renders id for aria describedby when help prop is provided', async () => { + const wrapper = await renderFormField({ + props: { help: 'somehelp' }, + inputComponent + }) + + const attr = wrapper.find('[id=v-0-0-help]') + expect(attr.exists()).toBe(true) + }) + + test('renders no id for aria describedby when no help prop is provided', async () => { + const wrapper = await renderFormField({ + props: { label: 'Username', description: 'Enter your username' }, + inputComponent + }) + + const attr = wrapper.find('[id=v-0-0-help]') + expect(attr.exists()).toBe(false) + }) }) }) diff --git a/test/components/__snapshots__/Form-vue.spec.ts.snap b/test/components/__snapshots__/Form-vue.spec.ts.snap index 6701093d..bd9a2a4d 100644 --- a/test/components/__snapshots__/Form-vue.spec.ts.snap +++ b/test/components/__snapshots__/Form-vue.spec.ts.snap @@ -8,7 +8,7 @@ exports[`Form > custom validation works > with error 1`] = `
-
+
@@ -21,7 +21,7 @@ exports[`Form > custom validation works > with error 1`] = `
-
+
@@ -39,7 +39,7 @@ exports[`Form > custom validation works > without error 1`] = `
-
+
@@ -52,7 +52,7 @@ exports[`Form > custom validation works > without error 1`] = `
-
+
@@ -70,7 +70,7 @@ exports[`Form > joi validation works > with error 1`] = `
-
+
@@ -83,7 +83,7 @@ exports[`Form > joi validation works > with error 1`] = `
-
+
@@ -101,7 +101,7 @@ exports[`Form > joi validation works > without error 1`] = `
-
+
@@ -114,7 +114,7 @@ exports[`Form > joi validation works > without error 1`] = `
-
+
@@ -136,7 +136,7 @@ exports[`Form > superstruct validation works > with error 1`] = `
-
+
@@ -149,7 +149,7 @@ exports[`Form > superstruct validation works > with error 1`] = `
-
+
@@ -167,7 +167,7 @@ exports[`Form > superstruct validation works > without error 1`] = `
-
+
@@ -180,7 +180,7 @@ exports[`Form > superstruct validation works > without error 1`] = `
-
+
@@ -198,7 +198,7 @@ exports[`Form > valibot validation works > with error 1`] = `
-
+
@@ -211,7 +211,7 @@ exports[`Form > valibot validation works > with error 1`] = `
-
+
@@ -229,7 +229,7 @@ exports[`Form > valibot validation works > without error 1`] = `
-
+
@@ -242,7 +242,7 @@ exports[`Form > valibot validation works > without error 1`] = `
-
+
@@ -260,7 +260,7 @@ exports[`Form > yup validation works > with error 1`] = `
-
+
@@ -273,7 +273,7 @@ exports[`Form > yup validation works > with error 1`] = `
-
+
@@ -291,7 +291,7 @@ exports[`Form > yup validation works > without error 1`] = `
-
+
@@ -304,7 +304,7 @@ exports[`Form > yup validation works > without error 1`] = `
-
+
@@ -322,7 +322,7 @@ exports[`Form > zod validation works > with error 1`] = `
-
+
@@ -335,7 +335,7 @@ exports[`Form > zod validation works > with error 1`] = `
-
+
@@ -353,7 +353,7 @@ exports[`Form > zod validation works > without error 1`] = `
-
+
@@ -366,7 +366,7 @@ exports[`Form > zod validation works > without error 1`] = `
-
+
diff --git a/test/components/__snapshots__/Form.spec.ts.snap b/test/components/__snapshots__/Form.spec.ts.snap index 0a0e2677..d7c7f898 100644 --- a/test/components/__snapshots__/Form.spec.ts.snap +++ b/test/components/__snapshots__/Form.spec.ts.snap @@ -8,7 +8,7 @@ exports[`Form > custom validation works > with error 1`] = `
-
+
@@ -21,7 +21,7 @@ exports[`Form > custom validation works > with error 1`] = `
-
+
@@ -39,7 +39,7 @@ exports[`Form > custom validation works > without error 1`] = `
-
+
@@ -52,7 +52,7 @@ exports[`Form > custom validation works > without error 1`] = `
-
+
@@ -70,7 +70,7 @@ exports[`Form > joi validation works > with error 1`] = `
-
+
@@ -83,7 +83,7 @@ exports[`Form > joi validation works > with error 1`] = `
-
+
@@ -101,7 +101,7 @@ exports[`Form > joi validation works > without error 1`] = `
-
+
@@ -114,7 +114,7 @@ exports[`Form > joi validation works > without error 1`] = `
-
+
@@ -136,7 +136,7 @@ exports[`Form > superstruct validation works > with error 1`] = `
-
+
@@ -149,7 +149,7 @@ exports[`Form > superstruct validation works > with error 1`] = `
-
+
@@ -167,7 +167,7 @@ exports[`Form > superstruct validation works > without error 1`] = `
-
+
@@ -180,7 +180,7 @@ exports[`Form > superstruct validation works > without error 1`] = `
-
+
@@ -198,7 +198,7 @@ exports[`Form > valibot validation works > with error 1`] = `
-
+
@@ -211,7 +211,7 @@ exports[`Form > valibot validation works > with error 1`] = `
-
+
@@ -229,7 +229,7 @@ exports[`Form > valibot validation works > without error 1`] = `
-
+
@@ -242,7 +242,7 @@ exports[`Form > valibot validation works > without error 1`] = `
-
+
@@ -260,7 +260,7 @@ exports[`Form > yup validation works > with error 1`] = `
-
+
@@ -273,7 +273,7 @@ exports[`Form > yup validation works > with error 1`] = `
-
+
@@ -291,7 +291,7 @@ exports[`Form > yup validation works > without error 1`] = `
-
+
@@ -304,7 +304,7 @@ exports[`Form > yup validation works > without error 1`] = `
-
+
@@ -322,7 +322,7 @@ exports[`Form > zod validation works > with error 1`] = `
-
+
@@ -335,7 +335,7 @@ exports[`Form > zod validation works > with error 1`] = `
-
+
@@ -353,7 +353,7 @@ exports[`Form > zod validation works > without error 1`] = `
-
+
@@ -366,7 +366,7 @@ exports[`Form > zod validation works > without error 1`] = `
-
+
diff --git a/test/components/__snapshots__/FormField-vue.spec.ts.snap b/test/components/__snapshots__/FormField-vue.spec.ts.snap index bee8f680..be708f59 100644 --- a/test/components/__snapshots__/FormField-vue.spec.ts.snap +++ b/test/components/__snapshots__/FormField-vue.spec.ts.snap @@ -79,7 +79,7 @@ exports[`FormField > renders with help correctly 1`] = `
-
Username must be unique
+
Username must be unique
" `; @@ -91,7 +91,7 @@ exports[`FormField > renders with help slot correctly 1`] = `
-
Help slot
+
Help slot
" `; diff --git a/test/components/__snapshots__/FormField.spec.ts.snap b/test/components/__snapshots__/FormField.spec.ts.snap index bee8f680..be708f59 100644 --- a/test/components/__snapshots__/FormField.spec.ts.snap +++ b/test/components/__snapshots__/FormField.spec.ts.snap @@ -79,7 +79,7 @@ exports[`FormField > renders with help correctly 1`] = `
-
Username must be unique
+
Username must be unique
" `; @@ -91,7 +91,7 @@ exports[`FormField > renders with help slot correctly 1`] = `
-
Help slot
+
Help slot
" `; From bb99345f5b3074febe6d261dc29110bc00b29f01 Mon Sep 17 00:00:00 2001 From: kyyy <60952577+rdjanuar@users.noreply.github.com> Date: Thu, 17 Jul 2025 02:58:05 +0700 Subject: [PATCH 2/4] fix(RadioGroup): improve type safety for normalizeItem function (#4535) Co-authored-by: Sandro Circi --- src/runtime/components/RadioGroup.vue | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/runtime/components/RadioGroup.vue b/src/runtime/components/RadioGroup.vue index 3a07201f..bfd5e0b5 100644 --- a/src/runtime/components/RadioGroup.vue +++ b/src/runtime/components/RadioGroup.vue @@ -70,7 +70,9 @@ export type RadioGroupEmits = RadioGroupRootEmits & { change: [payload: Event] } -type SlotProps = (props: { item: T & { id: string }, modelValue?: RadioGroupValue }) => any +type NormalizeItem = Exclude + +type SlotProps = (props: { item: NormalizeItem, modelValue?: RadioGroupValue }) => any export interface RadioGroupSlots { legend(props?: {}): any @@ -114,21 +116,21 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.radioGroup | indicator: props.indicator })) -function normalizeItem(item: any) { +function normalizeItem(item: T): NormalizeItem { if (item === null) { return { id: `${id}:null`, value: undefined, label: undefined - } + } as NormalizeItem } - if (typeof item === 'string' || typeof item === 'number') { + if (typeof item === 'string' || typeof item === 'number' || typeof item === 'bigint') { return { id: `${id}:${item}`, value: String(item), label: String(item) - } + } as NormalizeItem } const value = get(item, props.valueKey as string) @@ -136,7 +138,7 @@ function normalizeItem(item: any) { const description = get(item, props.descriptionKey as string) return { - ...item, + ...(item as NormalizeItem), value, label, description, From 6ca7c8b7bfa248b586a8d4cd888c8e7d09267230 Mon Sep 17 00:00:00 2001 From: J-Michalek <71264422+J-Michalek@users.noreply.github.com> Date: Wed, 16 Jul 2025 22:28:45 +0200 Subject: [PATCH 3/4] feat(InputMenu): emit `remove-tag` event (#4511) --- src/runtime/components/InputMenu.vue | 12 +++++++----- test/components/InputMenu.spec.ts | 7 +++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/runtime/components/InputMenu.vue b/src/runtime/components/InputMenu.vue index 6c76f945..551478d8 100644 --- a/src/runtime/components/InputMenu.vue +++ b/src/runtime/components/InputMenu.vue @@ -128,15 +128,16 @@ export interface InputMenuProps = ArrayOr } export type InputMenuEmits, VK extends GetItemKeys | undefined, M extends boolean> = Pick & { - change: [payload: Event] - blur: [payload: FocusEvent] - focus: [payload: FocusEvent] - create: [item: string] + 'change': [payload: Event] + 'blur': [payload: FocusEvent] + 'focus': [payload: FocusEvent] + 'create': [item: string] /** Event handler when highlighted element changes. */ - highlight: [payload: { + 'highlight': [payload: { ref: HTMLElement value: GetModelValue } | undefined] + 'remove-tag': [item: GetModelValue] } & GetModelValueEmits type SlotProps = (props: { item: T, index: number }) => any @@ -366,6 +367,7 @@ function onRemoveTag(event: any) { const modelValue = props.modelValue as GetModelValue const filteredValue = modelValue.filter(value => !isEqual(value, event)) emits('update:modelValue', filteredValue as GetModelValue) + emits('remove-tag', event) onUpdate(filteredValue) } } diff --git a/test/components/InputMenu.spec.ts b/test/components/InputMenu.spec.ts index 3e644ac3..0fdf637d 100644 --- a/test/components/InputMenu.spec.ts +++ b/test/components/InputMenu.spec.ts @@ -108,6 +108,13 @@ describe('InputMenu', () => { await input.vm.$emit('update:open', false) expect(wrapper.emitted()).toMatchObject({ blur: [[{ type: 'blur' }]] }) }) + + test('remove-tag event', async () => { + const wrapper = mount(InputMenu, { props: { modelValue: ['Option 1'], items: ['Option 1', 'Option 2'], multiple: true } }) + const input = wrapper.findComponent({ name: 'TagsInputRoot' }) + await input.vm.$emit('remove-tag', 'Option 1') + expect(wrapper.emitted()).toMatchObject({ 'remove-tag': [['Option 1']] }) + }) }) describe('form integration', async () => { From 5db3b0f98c7e7687dd36a2803952bf11460e070a Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Thu, 17 Jul 2025 10:56:08 +0200 Subject: [PATCH 4/4] docs(icons): add `app` prefix to assets --- docs/content/1.getting-started/4.icons/1.nuxt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/1.getting-started/4.icons/1.nuxt.md b/docs/content/1.getting-started/4.icons/1.nuxt.md index b7b32f31..b1f84208 100644 --- a/docs/content/1.getting-started/4.icons/1.nuxt.md +++ b/docs/content/1.getting-started/4.icons/1.nuxt.md @@ -87,7 +87,7 @@ Read more about this in the `@nuxt/icon` documentation. You can use local SVG files to create a custom Iconify collection. -For example, place your icons' SVG files under a folder of your choice, for example, `./assets/icons`: +For example, place your icons' SVG files under a folder of your choice, for example, `./app/assets/icons`: ```bash assets/icons @@ -104,7 +104,7 @@ export default defineNuxtConfig({ icon: { customCollections: [{ prefix: 'custom', - dir: './assets/icons' + dir: './app/assets/icons' }] } })