import { type InputHTMLAttributes, type ReactNode } from "react";
/**
* @since 2.5.6
* @since 6.0.0 Renamed from `TextConstraints` to
* `TextFieldValidationOptions`
*/
export type TextFieldValidationOptions = Pick<
InputHTMLAttributes,
"minLength" | "maxLength" | "required" | "pattern"
>;
/**
* Since the default validation messages can be verbose, this type is used to
* configure when/how to display the native browser messages when the validation
* state changes during the `change` event phase. The validation message will
* always be shown on blur.
*
* When this is:
*
* - `true` -> always show the browser message when it exists
* - `false` -> never show the browser message
* - `"recommended"` -> only shows the browser message if it is one of the
* `RECOMMENDED_STATE_KEYS`/`RECOMMENDED_NUMBER_STATE_KEYS` validation errors
* - `keyof ValidityState` -> only shows the browser message if it is not the
* specific validation error
* - `(keyof ValidityState)[]` -> only shows the browser message if it is not
* the specific validation errors
*
* @see {@link RECOMMENDED_STATE_KEYS}
* @see {@link RECOMMENDED_NUMBER_STATE_KEYS}
* @since 2.5.6
* @since 6.0.0 Renamed from `ChangeValidationBehavior` to
* `TextFieldValidationType`
*/
export type TextFieldValidationType =
| "blur"
| "change"
| "recommended"
| keyof ValidityState
| readonly (keyof ValidityState)[];
/**
* @since 2.5.0
* @since 6.0.0 Renamed `validateOnChange` to `validationType`
*/
export interface ErrorMessageOptions extends TextFieldValidationOptions {
/**
* The current input or textarea's validity state.
*/
validity: ValidityState;
/**
* The browser defined validation message based on the validity state. This
* will be the empty string when there are no errors.
*/
validationMessage: string;
/**
* The current `TextField` or `TextArea` value.
*/
value: string;
/**
* This will only be `true` if called by the `useNumberField` hook.
*/
isNumber: boolean;
/**
* Boolean if this is triggered from a blur event instead of a change event.
*/
isBlurEvent: boolean;
/**
* The validation type defined by the `useTextField` hook.
*/
validationType: TextFieldValidationType;
}
/**
* A function to get a custom error message for specific errors. This is really
* useful when using the `pattern` attribute to give additional information or
* changing the native "language translated" error message.
*
* @param options - An object containing metadata that can be used to create an
* error message for your `TextField` or `TextArea`.
* @returns An error message to display or an empty string.
* @since 2.5.0
*/
export type GetErrorMessage = (options: ErrorMessageOptions) => string;
/**
* @internal
* @since 2.5.0
* @since 6.0.0 Added `"valid"` since it exists in the type definition
*/
const VALIDITY_STATE_KEYS: readonly (keyof ValidityState)[] = [
"badInput",
"customError",
"patternMismatch",
"rangeOverflow",
"rangeUnderflow",
"stepMismatch",
"tooLong",
"tooShort",
"typeMismatch",
"valid",
"valueMissing",
];
/**
* @internal
* @since 2.5.0
*/
export const RECOMMENDED_STATE_KEYS: readonly (keyof ValidityState)[] = [
"badInput",
"tooLong",
"valueMissing",
];
/**
* @internal
* @since 2.5.0
*/
export const RECOMMENDED_NUMBER_STATE_KEYS: readonly (keyof ValidityState)[] = [
...RECOMMENDED_STATE_KEYS,
"rangeOverflow",
"rangeUnderflow",
"tooShort",
"typeMismatch",
];
/**
* The validation message is actually kind of weird since it's possible for a
* form element to have multiple errors at once. The validation message will be
* the first error that appears, so need to make sure that the first error is
* one of the recommended state keys so the message appears for only those types
* of errors.
*
* @internal
* @since 2.5.0
*/
const isRecommended = (validity: ValidityState, isNumber: boolean): boolean => {
const errorable = isNumber
? RECOMMENDED_NUMBER_STATE_KEYS
: RECOMMENDED_STATE_KEYS;
return VALIDITY_STATE_KEYS.every((key) => {
const errored = validity[key];
return !errored || errorable.includes(key);
});
};
/**
* The default implementation for getting an error message for the `TextField`
* or `TextArea` components that relies on the behavior of the
* {@link ChangeValidationBehavior}
*
* @since 2.5.0
*/
export const defaultGetErrorMessage: GetErrorMessage = (options) => {
const {
isNumber,
isBlurEvent,
validity,
validationMessage,
validationType: validate,
} = options;
if (isBlurEvent || !validationMessage || validate === "change") {
return validationMessage;
}
if (validate === "blur") {
return "";
}
if (validate === "recommended") {
return isRecommended(validity, isNumber) ? validationMessage : "";
}
const keys = typeof validate === "string" ? [validate] : validate;
return keys.length > 0 &&
VALIDITY_STATE_KEYS.some((key) => validity[key] && keys.includes(key))
? validationMessage
: "";
};
/**
* @since 2.5.0
*/
export interface IsErroredOptions extends ErrorMessageOptions {
/**
* The current error message or an empty string.
*/
errorMessage: string;
}
/**
* A function that is used to determine if a `TextField` or `TextArea` is in an
* errored state.
*
* @param options - All the current options that can be used to determine the
* error state.
* @returns True if the component is considered to be in an errored state.
* @since 2.5.0
*/
export type IsErrored = (options: IsErroredOptions) => boolean;
/**
* The default implementation for checking if a `TextField` or `TextArea` is
* errored by returning `true` if the `errorMessage` string is truthy or the
* value is not within the `minLength` and `maxLength` constraints when they
* exist.
*
* @since 2.5.0
*/
export const defaultIsErrored: IsErrored = (options) => {
const { value, errorMessage, minLength, maxLength, isBlurEvent } = options;
return (
!!errorMessage ||
(typeof maxLength === "number" && value.length > maxLength) ||
(isBlurEvent && typeof minLength === "number" && value.length < minLength)
);
};
/**
* @since 6.0.0
*/
export interface GetErrorIconOptions {
/**
* This will be `true` if the `TextField` or `TextArea` is in an errored state.
*/
error: boolean;
/**
* The current error icon that was provided.
*/
errorIcon: ReactNode;
/**
* The current error message or an empty string.
*/
errorMessage: string;
}
/**
* A function that can be used to dynamically get an error icon based on the
* current visible error.
*
* @param options - The {@link GetErrorIconOptions}
* @returns An icon to render or falsey to render nothing.
* @since 2.5.0
* @since 6.0.0 Updated to accept a single object argument
*/
export type GetErrorIcon = (options: GetErrorIconOptions) => ReactNode;
/**
* The default implementation for showing an error icon in `TextField` and
* `TextArea` components that will only display when the error flag is enabled.
*
* @since 2.5.0
*/
export const defaultGetErrorIcon: GetErrorIcon = (options) => {
const { error, errorIcon } = options;
return error && errorIcon;
};