diff --git a/packages/core/src/useCheckbox/useCheckbox.ts b/packages/core/src/useCheckbox/useCheckbox.ts index 54c18dfe..5cee752a 100644 --- a/packages/core/src/useCheckbox/useCheckbox.ts +++ b/packages/core/src/useCheckbox/useCheckbox.ts @@ -36,6 +36,8 @@ export interface CheckboxProps { indeterminate?: boolean; schema?: TypedSchema; + + disableHtmlValidation?: boolean; } export interface CheckboxDomInputProps extends AriaLabelableProps, InputBaseAttributes { @@ -64,7 +66,7 @@ export function useCheckbox( const group: CheckboxGroupContext | null = inject(CheckboxGroupKey, null); const inputRef = elementRef || ref(); const field = useCheckboxField(props); - useInputValidity({ inputRef, field }); + useInputValidity({ inputRef, field, disableHtmlValidation: props.disableHtmlValidation }); const { fieldValue, isTouched, setTouched, setValue, errorMessage, setErrors } = field; const checked = computed({ diff --git a/packages/core/src/useFormGroup/useFormGroup.spec.ts b/packages/core/src/useFormGroup/useFormGroup.spec.ts index 7876b743..4acb1f70 100644 --- a/packages/core/src/useFormGroup/useFormGroup.spec.ts +++ b/packages/core/src/useFormGroup/useFormGroup.spec.ts @@ -6,6 +6,7 @@ import { useTextField } from '../useTextField'; import { useForm } from '../useForm'; import { fireEvent, render, screen } from '@testing-library/vue'; import { flush } from '@test-utils/flush'; +import { configure } from '../config'; function createInputComponent(): Component { return { @@ -13,7 +14,12 @@ function createInputComponent(): Component { setup: (_, { attrs }) => { const name = (attrs.name || 'test') as string; const schema = attrs.schema as TypedSchema; - const { errorMessage, inputProps } = useTextField({ name, label: name, schema }); + const { errorMessage, inputProps } = useTextField({ + name, + label: name, + schema, + disableHtmlValidation: attrs.disableHtmlValidation as any, + }); return { errorMessage: errorMessage, inputProps, name, attrs }; }, @@ -443,4 +449,77 @@ describe('disabling HTML validation', () => { expect(errors[1]).toHaveTextContent(''); expect(errors[2]).toHaveTextContent('Constraints not satisfied'); }); + + test('can be disabled on the field level', async () => { + await render({ + components: { TInput: createInputComponent(), TGroup: createGroupComponent() }, + setup() { + useForm(); + + return {}; + }, + template: ` + + + + + + + `, + }); + + await flush(); + await fireEvent.touch(screen.getByTestId('field1')); + await fireEvent.touch(screen.getByTestId('field2')); + await fireEvent.touch(screen.getByTestId('field3')); + + const errors = screen.getAllByTestId('err'); + expect(errors[0]).toHaveTextContent('Constraints not satisfied'); + expect(errors[1]).toHaveTextContent(''); + expect(errors[2]).toHaveTextContent(''); + }); + + test('can be disabled globally and overridden', async () => { + configure({ + validation: { + disableHtmlValidation: true, + }, + }); + + await render({ + components: { TInput: createInputComponent(), TGroup: createGroupComponent() }, + setup() { + useForm({ disableHtmlValidation: true }); + + return {}; + }, + template: ` + + + + + + + + + + `, + }); + + await flush(); + await fireEvent.touch(screen.getByTestId('field1')); + await fireEvent.touch(screen.getByTestId('field2')); + await fireEvent.touch(screen.getByTestId('field3')); + + const errors = screen.getAllByTestId('err'); + expect(errors[0]).toHaveTextContent(''); + expect(errors[1]).toHaveTextContent(''); + expect(errors[2]).toHaveTextContent('Constraints not satisfied'); + + configure({ + validation: { + disableHtmlValidation: false, + }, + }); + }); }); diff --git a/packages/core/src/useNumberField/useNumberField.ts b/packages/core/src/useNumberField/useNumberField.ts index a6cdcf91..717a7d3f 100644 --- a/packages/core/src/useNumberField/useNumberField.ts +++ b/packages/core/src/useNumberField/useNumberField.ts @@ -63,6 +63,8 @@ export interface NumberFieldProps { formatOptions?: Intl.NumberFormatOptions; schema?: TypedSchema; + + disableHtmlValidation?: boolean; } export function useNumberField( @@ -81,7 +83,7 @@ export function useNumberField( schema: props.schema, }); - const { validityDetails } = useInputValidity({ inputRef, field }); + const { validityDetails } = useInputValidity({ inputRef, field, disableHtmlValidation: props.disableHtmlValidation }); const { displayError } = useErrorDisplay(field); const { fieldValue, setValue, setTouched, isTouched, errorMessage } = field; const formattedText = computed(() => { diff --git a/packages/core/src/useRadio/useRadioGroup.ts b/packages/core/src/useRadio/useRadioGroup.ts index 4d4ee8bf..0c6d1693 100644 --- a/packages/core/src/useRadio/useRadioGroup.ts +++ b/packages/core/src/useRadio/useRadioGroup.ts @@ -31,6 +31,7 @@ export interface RadioGroupContext { required: boolean; readonly modelValue: TValue | undefined; + setErrors(message: Arrayable): void; setValue(value: TValue): void; setTouched(touched: boolean): void; @@ -59,6 +60,8 @@ export interface RadioGroupProps { required?: boolean; schema?: TypedSchema; + + disableHtmlValidation?: boolean; } interface RadioGroupDomProps extends AriaLabelableProps, AriaDescribableProps, AriaValidatableProps { diff --git a/packages/core/src/useSearchField/useSearchField.ts b/packages/core/src/useSearchField/useSearchField.ts index 57a70110..af055acd 100644 --- a/packages/core/src/useSearchField/useSearchField.ts +++ b/packages/core/src/useSearchField/useSearchField.ts @@ -55,6 +55,8 @@ export interface SearchFieldProps { schema?: TypedSchema; onSubmit?: (value: string) => void; + + disableHtmlValidation?: boolean; } export function useSearchField( @@ -71,7 +73,11 @@ export function useSearchField( schema: props.schema, }); - const { validityDetails, updateValidity } = useInputValidity({ inputRef, field }); + const { validityDetails, updateValidity } = useInputValidity({ + inputRef, + field, + disableHtmlValidation: props.disableHtmlValidation, + }); const { displayError } = useErrorDisplay(field); const { fieldValue, setValue, isTouched, setTouched, errorMessage, isValid, errors } = field; diff --git a/packages/core/src/useSwitch/useSwitch.ts b/packages/core/src/useSwitch/useSwitch.ts index 9607a54f..da65e48c 100644 --- a/packages/core/src/useSwitch/useSwitch.ts +++ b/packages/core/src/useSwitch/useSwitch.ts @@ -53,6 +53,8 @@ export type SwitchProps = { falseValue?: unknown; schema?: TypedSchema; + + disableHtmlValidation?: boolean; }; export function useSwitch(_props: Reactivify, elementRef?: Ref) { @@ -72,7 +74,7 @@ export function useSwitch(_props: Reactivify, elementRef? schema: props.schema, }); - useInputValidity({ field, inputRef }); + useInputValidity({ field, inputRef, disableHtmlValidation: props.disableHtmlValidation }); const { fieldValue, setValue, isTouched, setTouched, errorMessage } = field; const { errorMessageProps, accessibleErrorProps } = createAccessibleErrorMessageProps({ inputId, diff --git a/packages/core/src/useTextField/useTextField.ts b/packages/core/src/useTextField/useTextField.ts index 31de4397..2f7cf900 100644 --- a/packages/core/src/useTextField/useTextField.ts +++ b/packages/core/src/useTextField/useTextField.ts @@ -57,6 +57,8 @@ export interface TextFieldProps { disabled?: boolean; schema?: TypedSchema; + + disableHtmlValidation?: boolean; } export function useTextField( @@ -73,7 +75,7 @@ export function useTextField( schema: props.schema, }); - const { validityDetails } = useInputValidity({ inputRef, field }); + const { validityDetails } = useInputValidity({ inputRef, field, disableHtmlValidation: props.disableHtmlValidation }); const { displayError } = useErrorDisplay(field); const { fieldValue, setValue, isTouched, setTouched, errorMessage, isValid, errors, setErrors } = field; const { labelProps, labelledByProps } = useLabel({ diff --git a/packages/core/src/validation/useInputValidity.ts b/packages/core/src/validation/useInputValidity.ts index 1da3e6f3..cf2fd353 100644 --- a/packages/core/src/validation/useInputValidity.ts +++ b/packages/core/src/validation/useInputValidity.ts @@ -1,4 +1,4 @@ -import { Ref, inject, nextTick, onMounted, shallowRef, watch } from 'vue'; +import { Ref, inject, nextTick, onMounted, shallowRef, watch, MaybeRefOrGetter, toValue } from 'vue'; import { useEventListener } from '../helpers/useEventListener'; import { FormKey } from '../useForm'; import { Maybe, ValidationResult } from '../types'; @@ -9,7 +9,7 @@ import { getConfig } from '../config'; interface InputValidityOptions { inputRef?: Ref>; - disableHtmlValidation?: boolean; + disableHtmlValidation?: MaybeRefOrGetter; field: FormField; events?: string[]; } @@ -21,8 +21,8 @@ export function useInputValidity(opts: InputValidityOptions) { const validityDetails = shallowRef(); useMessageCustomValiditySync(errorMessage, opts.inputRef); const isHtmlValidationDisabled = () => + toValue(opts.disableHtmlValidation) ?? (formGroup || form)?.isHtmlValidationDisabled() ?? - opts.disableHtmlValidation ?? getConfig().validation.disableHtmlValidation; function validateNative(mutate?: boolean): ValidationResult {