Skip to content

Commit

Permalink
feat: add disable html validation on field level
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Aug 22, 2024
1 parent 681693d commit d1e4f15
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 9 deletions.
4 changes: 3 additions & 1 deletion packages/core/src/useCheckbox/useCheckbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export interface CheckboxProps<TValue = string> {
indeterminate?: boolean;

schema?: TypedSchema<TValue>;

disableHtmlValidation?: boolean;
}

export interface CheckboxDomInputProps extends AriaLabelableProps, InputBaseAttributes {
Expand Down Expand Up @@ -64,7 +66,7 @@ export function useCheckbox<TValue = string>(
const group: CheckboxGroupContext<TValue> | null = inject(CheckboxGroupKey, null);
const inputRef = elementRef || ref<HTMLElement>();
const field = useCheckboxField(props);
useInputValidity({ inputRef, field });
useInputValidity({ inputRef, field, disableHtmlValidation: props.disableHtmlValidation });
const { fieldValue, isTouched, setTouched, setValue, errorMessage, setErrors } = field;

const checked = computed({
Expand Down
81 changes: 80 additions & 1 deletion packages/core/src/useFormGroup/useFormGroup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ 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 {
inheritAttrs: false,
setup: (_, { attrs }) => {
const name = (attrs.name || 'test') as string;
const schema = attrs.schema as TypedSchema<any>;
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 };
},
Expand Down Expand Up @@ -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: `
<TGroup>
<TInput name="field1" :required="true" />
<TInput name="field2" :required="true" :disableHtmlValidation="true" />
</TGroup>
<TInput name="field3" :required="true" :disableHtmlValidation="true" />
`,
});

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: `
<TGroup>
<TInput name="field1" :required="true" />
</TGroup>
<TInput name="field2" :required="true" />
<TGroup :disableHtmlValidation="false">
<TInput name="field3" :required="true" />
</TGroup>
`,
});

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,
},
});
});
});
4 changes: 3 additions & 1 deletion packages/core/src/useNumberField/useNumberField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export interface NumberFieldProps {
formatOptions?: Intl.NumberFormatOptions;

schema?: TypedSchema<number>;

disableHtmlValidation?: boolean;
}

export function useNumberField(
Expand All @@ -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<string>(() => {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/useRadio/useRadioGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface RadioGroupContext<TValue> {
required: boolean;

readonly modelValue: TValue | undefined;

setErrors(message: Arrayable<string>): void;
setValue(value: TValue): void;
setTouched(touched: boolean): void;
Expand Down Expand Up @@ -59,6 +60,8 @@ export interface RadioGroupProps<TValue = string> {
required?: boolean;

schema?: TypedSchema<TValue>;

disableHtmlValidation?: boolean;
}

interface RadioGroupDomProps extends AriaLabelableProps, AriaDescribableProps, AriaValidatableProps {
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/useSearchField/useSearchField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export interface SearchFieldProps {
schema?: TypedSchema<string>;

onSubmit?: (value: string) => void;

disableHtmlValidation?: boolean;
}

export function useSearchField(
Expand All @@ -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;

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/useSwitch/useSwitch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export type SwitchProps = {
falseValue?: unknown;

schema?: TypedSchema<unknown>;

disableHtmlValidation?: boolean;
};

export function useSwitch(_props: Reactivify<SwitchProps, 'schema'>, elementRef?: Ref<HTMLInputElement>) {
Expand All @@ -72,7 +74,7 @@ export function useSwitch(_props: Reactivify<SwitchProps, 'schema'>, 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,
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/useTextField/useTextField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export interface TextFieldProps {
disabled?: boolean;

schema?: TypedSchema<string>;

disableHtmlValidation?: boolean;
}

export function useTextField(
Expand All @@ -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({
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/validation/useInputValidity.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -9,7 +9,7 @@ import { getConfig } from '../config';

interface InputValidityOptions {
inputRef?: Ref<Maybe<HTMLElement>>;
disableHtmlValidation?: boolean;
disableHtmlValidation?: MaybeRefOrGetter<boolean | undefined>;
field: FormField<any>;
events?: string[];
}
Expand All @@ -21,8 +21,8 @@ export function useInputValidity(opts: InputValidityOptions) {
const validityDetails = shallowRef<ValidityState>();
useMessageCustomValiditySync(errorMessage, opts.inputRef);
const isHtmlValidationDisabled = () =>
toValue(opts.disableHtmlValidation) ??
(formGroup || form)?.isHtmlValidationDisabled() ??
opts.disableHtmlValidation ??
getConfig().validation.disableHtmlValidation;

function validateNative(mutate?: boolean): ValidationResult {
Expand Down

0 comments on commit d1e4f15

Please sign in to comment.