diff --git a/playground/app/pages/components/form-field.vue b/playground/app/pages/components/form-field.vue index 5dc1764bf6..e495ed434a 100644 --- a/playground/app/pages/components/form-field.vue +++ b/playground/app/pages/components/form-field.vue @@ -5,7 +5,9 @@ const sizes = Object.keys(theme.variants.size) as Array
-
+
diff --git a/src/runtime/components/FormField.vue b/src/runtime/components/FormField.vue index f8d8a3ff11..cf5253b95b 100644 --- a/src/runtime/components/FormField.vue +++ b/src/runtime/components/FormField.vue @@ -19,6 +19,20 @@ export interface FormFieldProps { description?: string help?: string error?: string | boolean + + /** + * An array of errors for this field. + * Note that only one error is displayed by default. You can use `maxErrors` to control the number of displayed errors. + * @defaultValue `1` + */ + errors?: string[] + + /** + * The maximum number of errors to display. If `false` or negative, display all available errors. + * @defaultValue `1` + */ + maxErrors?: number | false + hint?: string /** * @defaultValue 'md' @@ -42,7 +56,7 @@ export interface FormFieldSlots { description(props: { description?: string }): any help(props: { help?: string }): any error(props: { error?: string | boolean }): any - default(props: { error?: string | boolean }): any + default(props: { error?: string | boolean, errors?: string[] }): any } @@ -54,7 +68,7 @@ import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFo import { tv } from '../utils/tv' import type { FormError, FormFieldInjectedOptions } from '../types/form' -const props = defineProps() +const props = withDefaults(defineProps(), { maxErrors: 1 }) const slots = defineSlots() const appConfig = useAppConfig() as FormField['AppConfig'] @@ -66,7 +80,24 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.formField || const formErrors = inject | null>('form-errors', null) -const error = computed(() => props.error || formErrors?.value?.find(error => error.name && (error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern))))?.message) +const errors = computed(() => + (props.error && typeof props.error === 'string' ? [props.error] : props.errors) + || formErrors?.value?.flatMap((error) => { + if (!error.name) return [] + if (error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern))) { + return [error.message] + } + return [] + })) + +const error = computed(() => errors.value?.[0] ?? props.error) + +const displayedErrors = computed(() => + props.maxErrors === false + || (!!props.maxErrors && props.maxErrors < 0) + ? errors.value + : errors.value?.slice(0, props.maxErrors) +) const id = ref(useId()) // Copies id's initial value to bind aria-attributes such as aria-describedby. @@ -113,9 +144,16 @@ provide(formFieldInjectionKey, computed(() => ({
- + -
+ +
{{ error }} diff --git a/test/components/FormField.spec.ts b/test/components/FormField.spec.ts index c15ac6e379..ea2589e064 100644 --- a/test/components/FormField.spec.ts +++ b/test/components/FormField.spec.ts @@ -68,6 +68,10 @@ describe('FormField', () => { ['with required', { props: { label: 'Username', required: true } }], ['with help', { props: { help: 'Username must be unique' } }], ['with error', { props: { error: 'Username is already taken' } }], + ['with multiple errors', { props: { errors: ['Username is already taken', 'This should not be visible'] } }], + ['with maxErrors', { props: { maxErrors: 2, errors: ['Username is already taken', 'This should be visible'] } }], + ['with maxErrors negative', { props: { maxErrors: -1, errors: ['Username is already taken', 'This should be visible', 'This should be visible'] } }], + ['with maxErrors false', { props: { maxErrors: false, errors: ['Username is already taken', 'This should be visible', 'This should be visible'] } }], ['with hint', { props: { hint: 'Use letters, numbers, and special characters' } }], ...sizes.map((size: string) => [`with size ${size}`, { props: { label: 'Username', description: 'Enter your username', size } }]), ['with as', { props: { as: 'section' } }], diff --git a/test/components/__snapshots__/FormField-vue.spec.ts.snap b/test/components/__snapshots__/FormField-vue.spec.ts.snap index bee8f6809a..40d39bd755 100644 --- a/test/components/__snapshots__/FormField-vue.spec.ts.snap +++ b/test/components/__snapshots__/FormField-vue.spec.ts.snap @@ -148,6 +148,59 @@ exports[`FormField > renders with label slot correctly 1`] = `
" `; +exports[`FormField > renders with maxErrors correctly 1`] = ` +"
+
+ + +
+
+
Username is already taken
+
This should be visible
+
+
" +`; + +exports[`FormField > renders with maxErrors false correctly 1`] = ` +"
+
+ + +
+
+
Username is already taken
+
This should be visible
+
This should be visible
+
+
" +`; + +exports[`FormField > renders with maxErrors negative correctly 1`] = ` +"
+
+ + +
+
+
Username is already taken
+
This should be visible
+
This should be visible
+
+
" +`; + +exports[`FormField > renders with multiple errors correctly 1`] = ` +"
+
+ + +
+
+
Username is already taken
+
+
" +`; + exports[`FormField > renders with required correctly 1`] = ` "
diff --git a/test/components/__snapshots__/FormField.spec.ts.snap b/test/components/__snapshots__/FormField.spec.ts.snap index bee8f6809a..40d39bd755 100644 --- a/test/components/__snapshots__/FormField.spec.ts.snap +++ b/test/components/__snapshots__/FormField.spec.ts.snap @@ -148,6 +148,59 @@ exports[`FormField > renders with label slot correctly 1`] = `
" `; +exports[`FormField > renders with maxErrors correctly 1`] = ` +"
+
+ + +
+
+
Username is already taken
+
This should be visible
+
+
" +`; + +exports[`FormField > renders with maxErrors false correctly 1`] = ` +"
+
+ + +
+
+
Username is already taken
+
This should be visible
+
This should be visible
+
+
" +`; + +exports[`FormField > renders with maxErrors negative correctly 1`] = ` +"
+
+ + +
+
+
Username is already taken
+
This should be visible
+
This should be visible
+
+
" +`; + +exports[`FormField > renders with multiple errors correctly 1`] = ` +"
+
+ + +
+
+
Username is already taken
+
+
" +`; + exports[`FormField > renders with required correctly 1`] = ` "