Files
ob248.com/node_modules/@radix-ui/react-form/dist/index.mjs
2026-02-05 17:31:20 +00:00

452 lines
19 KiB
JavaScript

"use client";
// src/form.tsx
import * as React from "react";
import { composeEventHandlers } from "@radix-ui/primitive";
import { useComposedRefs } from "@radix-ui/react-compose-refs";
import { createContextScope } from "@radix-ui/react-context";
import { useId } from "@radix-ui/react-id";
import { Label as LabelPrimitive } from "@radix-ui/react-label";
import { Primitive } from "@radix-ui/react-primitive";
import { Fragment, jsx } from "react/jsx-runtime";
var [createFormContext, createFormScope] = createContextScope("Form");
var FORM_NAME = "Form";
var [ValidationProvider, useValidationContext] = createFormContext(FORM_NAME);
var [AriaDescriptionProvider, useAriaDescriptionContext] = createFormContext(FORM_NAME);
var Form = React.forwardRef(
(props, forwardedRef) => {
const { __scopeForm, onClearServerErrors = () => {
}, ...rootProps } = props;
const formRef = React.useRef(null);
const composedFormRef = useComposedRefs(forwardedRef, formRef);
const [validityMap, setValidityMap] = React.useState({});
const getFieldValidity = React.useCallback(
(fieldName) => validityMap[fieldName],
[validityMap]
);
const handleFieldValidityChange = React.useCallback(
(fieldName, validity) => setValidityMap((prevValidityMap) => ({
...prevValidityMap,
[fieldName]: { ...prevValidityMap[fieldName] ?? {}, ...validity }
})),
[]
);
const handleFieldValiditionClear = React.useCallback((fieldName) => {
setValidityMap((prevValidityMap) => ({ ...prevValidityMap, [fieldName]: void 0 }));
setCustomErrorsMap((prevCustomErrorsMap) => ({ ...prevCustomErrorsMap, [fieldName]: {} }));
}, []);
const [customMatcherEntriesMap, setCustomMatcherEntriesMap] = React.useState({});
const getFieldCustomMatcherEntries = React.useCallback(
(fieldName) => customMatcherEntriesMap[fieldName] ?? [],
[customMatcherEntriesMap]
);
const handleFieldCustomMatcherAdd = React.useCallback((fieldName, matcherEntry) => {
setCustomMatcherEntriesMap((prevCustomMatcherEntriesMap) => ({
...prevCustomMatcherEntriesMap,
[fieldName]: [...prevCustomMatcherEntriesMap[fieldName] ?? [], matcherEntry]
}));
}, []);
const handleFieldCustomMatcherRemove = React.useCallback((fieldName, matcherEntryId) => {
setCustomMatcherEntriesMap((prevCustomMatcherEntriesMap) => ({
...prevCustomMatcherEntriesMap,
[fieldName]: (prevCustomMatcherEntriesMap[fieldName] ?? []).filter(
(matcherEntry) => matcherEntry.id !== matcherEntryId
)
}));
}, []);
const [customErrorsMap, setCustomErrorsMap] = React.useState({});
const getFieldCustomErrors = React.useCallback(
(fieldName) => customErrorsMap[fieldName] ?? {},
[customErrorsMap]
);
const handleFieldCustomErrorsChange = React.useCallback((fieldName, customErrors) => {
setCustomErrorsMap((prevCustomErrorsMap) => ({
...prevCustomErrorsMap,
[fieldName]: { ...prevCustomErrorsMap[fieldName] ?? {}, ...customErrors }
}));
}, []);
const [messageIdsMap, setMessageIdsMap] = React.useState({});
const handleFieldMessageIdAdd = React.useCallback((fieldName, id) => {
setMessageIdsMap((prevMessageIdsMap) => {
const fieldDescriptionIds = new Set(prevMessageIdsMap[fieldName]).add(id);
return { ...prevMessageIdsMap, [fieldName]: fieldDescriptionIds };
});
}, []);
const handleFieldMessageIdRemove = React.useCallback((fieldName, id) => {
setMessageIdsMap((prevMessageIdsMap) => {
const fieldDescriptionIds = new Set(prevMessageIdsMap[fieldName]);
fieldDescriptionIds.delete(id);
return { ...prevMessageIdsMap, [fieldName]: fieldDescriptionIds };
});
}, []);
const getFieldDescription = React.useCallback(
(fieldName) => Array.from(messageIdsMap[fieldName] ?? []).join(" ") || void 0,
[messageIdsMap]
);
return /* @__PURE__ */ jsx(
ValidationProvider,
{
scope: __scopeForm,
getFieldValidity,
onFieldValidityChange: handleFieldValidityChange,
getFieldCustomMatcherEntries,
onFieldCustomMatcherEntryAdd: handleFieldCustomMatcherAdd,
onFieldCustomMatcherEntryRemove: handleFieldCustomMatcherRemove,
getFieldCustomErrors,
onFieldCustomErrorsChange: handleFieldCustomErrorsChange,
onFieldValiditionClear: handleFieldValiditionClear,
children: /* @__PURE__ */ jsx(
AriaDescriptionProvider,
{
scope: __scopeForm,
onFieldMessageIdAdd: handleFieldMessageIdAdd,
onFieldMessageIdRemove: handleFieldMessageIdRemove,
getFieldDescription,
children: /* @__PURE__ */ jsx(
Primitive.form,
{
...rootProps,
ref: composedFormRef,
onInvalid: composeEventHandlers(props.onInvalid, (event) => {
const firstInvalidControl = getFirstInvalidControl(event.currentTarget);
if (firstInvalidControl === event.target) firstInvalidControl.focus();
event.preventDefault();
}),
onSubmit: composeEventHandlers(props.onSubmit, onClearServerErrors, {
checkForDefaultPrevented: false
}),
onReset: composeEventHandlers(props.onReset, onClearServerErrors)
}
)
}
)
}
);
}
);
Form.displayName = FORM_NAME;
var FIELD_NAME = "FormField";
var [FormFieldProvider, useFormFieldContext] = createFormContext(FIELD_NAME);
var FormField = React.forwardRef(
(props, forwardedRef) => {
const { __scopeForm, name, serverInvalid = false, ...fieldProps } = props;
const validationContext = useValidationContext(FIELD_NAME, __scopeForm);
const validity = validationContext.getFieldValidity(name);
const id = useId();
return /* @__PURE__ */ jsx(FormFieldProvider, { scope: __scopeForm, id, name, serverInvalid, children: /* @__PURE__ */ jsx(
Primitive.div,
{
"data-valid": getValidAttribute(validity, serverInvalid),
"data-invalid": getInvalidAttribute(validity, serverInvalid),
...fieldProps,
ref: forwardedRef
}
) });
}
);
FormField.displayName = FIELD_NAME;
var LABEL_NAME = "FormLabel";
var FormLabel = React.forwardRef(
(props, forwardedRef) => {
const { __scopeForm, ...labelProps } = props;
const validationContext = useValidationContext(LABEL_NAME, __scopeForm);
const fieldContext = useFormFieldContext(LABEL_NAME, __scopeForm);
const htmlFor = labelProps.htmlFor || fieldContext.id;
const validity = validationContext.getFieldValidity(fieldContext.name);
return /* @__PURE__ */ jsx(
LabelPrimitive,
{
"data-valid": getValidAttribute(validity, fieldContext.serverInvalid),
"data-invalid": getInvalidAttribute(validity, fieldContext.serverInvalid),
...labelProps,
ref: forwardedRef,
htmlFor
}
);
}
);
FormLabel.displayName = LABEL_NAME;
var CONTROL_NAME = "FormControl";
var FormControl = React.forwardRef(
(props, forwardedRef) => {
const { __scopeForm, ...controlProps } = props;
const validationContext = useValidationContext(CONTROL_NAME, __scopeForm);
const fieldContext = useFormFieldContext(CONTROL_NAME, __scopeForm);
const ariaDescriptionContext = useAriaDescriptionContext(CONTROL_NAME, __scopeForm);
const ref = React.useRef(null);
const composedRef = useComposedRefs(forwardedRef, ref);
const name = controlProps.name || fieldContext.name;
const id = controlProps.id || fieldContext.id;
const customMatcherEntries = validationContext.getFieldCustomMatcherEntries(name);
const { onFieldValidityChange, onFieldCustomErrorsChange, onFieldValiditionClear } = validationContext;
const updateControlValidity = React.useCallback(
async (control) => {
if (hasBuiltInError(control.validity)) {
const controlValidity2 = validityStateToObject(control.validity);
onFieldValidityChange(name, controlValidity2);
return;
}
const formData = control.form ? new FormData(control.form) : new FormData();
const matcherArgs = [control.value, formData];
const syncCustomMatcherEntries = [];
const ayncCustomMatcherEntries = [];
customMatcherEntries.forEach((customMatcherEntry) => {
if (isAsyncCustomMatcherEntry(customMatcherEntry, matcherArgs)) {
ayncCustomMatcherEntries.push(customMatcherEntry);
} else if (isSyncCustomMatcherEntry(customMatcherEntry)) {
syncCustomMatcherEntries.push(customMatcherEntry);
}
});
const syncCustomErrors = syncCustomMatcherEntries.map(({ id: id2, match }) => {
return [id2, match(...matcherArgs)];
});
const syncCustomErrorsById = Object.fromEntries(syncCustomErrors);
const hasSyncCustomErrors = Object.values(syncCustomErrorsById).some(Boolean);
const hasCustomError = hasSyncCustomErrors;
control.setCustomValidity(hasCustomError ? DEFAULT_INVALID_MESSAGE : "");
const controlValidity = validityStateToObject(control.validity);
onFieldValidityChange(name, controlValidity);
onFieldCustomErrorsChange(name, syncCustomErrorsById);
if (!hasSyncCustomErrors && ayncCustomMatcherEntries.length > 0) {
const promisedCustomErrors = ayncCustomMatcherEntries.map(
({ id: id2, match }) => match(...matcherArgs).then((matches) => [id2, matches])
);
const asyncCustomErrors = await Promise.all(promisedCustomErrors);
const asyncCustomErrorsById = Object.fromEntries(asyncCustomErrors);
const hasAsyncCustomErrors = Object.values(asyncCustomErrorsById).some(Boolean);
const hasCustomError2 = hasAsyncCustomErrors;
control.setCustomValidity(hasCustomError2 ? DEFAULT_INVALID_MESSAGE : "");
const controlValidity2 = validityStateToObject(control.validity);
onFieldValidityChange(name, controlValidity2);
onFieldCustomErrorsChange(name, asyncCustomErrorsById);
}
},
[customMatcherEntries, name, onFieldCustomErrorsChange, onFieldValidityChange]
);
React.useEffect(() => {
const control = ref.current;
if (control) {
const handleChange = () => updateControlValidity(control);
control.addEventListener("change", handleChange);
return () => control.removeEventListener("change", handleChange);
}
}, [updateControlValidity]);
const resetControlValidity = React.useCallback(() => {
const control = ref.current;
if (control) {
control.setCustomValidity("");
onFieldValiditionClear(name);
}
}, [name, onFieldValiditionClear]);
React.useEffect(() => {
const form = ref.current?.form;
if (form) {
form.addEventListener("reset", resetControlValidity);
return () => form.removeEventListener("reset", resetControlValidity);
}
}, [resetControlValidity]);
React.useEffect(() => {
const control = ref.current;
const form = control?.closest("form");
if (form && fieldContext.serverInvalid) {
const firstInvalidControl = getFirstInvalidControl(form);
if (firstInvalidControl === control) firstInvalidControl.focus();
}
}, [fieldContext.serverInvalid]);
const validity = validationContext.getFieldValidity(name);
return /* @__PURE__ */ jsx(
Primitive.input,
{
"data-valid": getValidAttribute(validity, fieldContext.serverInvalid),
"data-invalid": getInvalidAttribute(validity, fieldContext.serverInvalid),
"aria-invalid": fieldContext.serverInvalid ? true : void 0,
"aria-describedby": ariaDescriptionContext.getFieldDescription(name),
title: "",
...controlProps,
ref: composedRef,
id,
name,
onInvalid: composeEventHandlers(props.onInvalid, (event) => {
const control = event.currentTarget;
updateControlValidity(control);
}),
onChange: composeEventHandlers(props.onChange, (_event) => {
resetControlValidity();
})
}
);
}
);
FormControl.displayName = CONTROL_NAME;
var DEFAULT_INVALID_MESSAGE = "This value is not valid";
var DEFAULT_BUILT_IN_MESSAGES = {
badInput: DEFAULT_INVALID_MESSAGE,
patternMismatch: "This value does not match the required pattern",
rangeOverflow: "This value is too large",
rangeUnderflow: "This value is too small",
stepMismatch: "This value does not match the required step",
tooLong: "This value is too long",
tooShort: "This value is too short",
typeMismatch: "This value does not match the required type",
valid: void 0,
valueMissing: "This value is missing"
};
var MESSAGE_NAME = "FormMessage";
var FormMessage = React.forwardRef(
(props, forwardedRef) => {
const { match, name: nameProp, ...messageProps } = props;
const fieldContext = useFormFieldContext(MESSAGE_NAME, props.__scopeForm);
const name = nameProp ?? fieldContext.name;
if (match === void 0) {
return /* @__PURE__ */ jsx(FormMessageImpl, { ...messageProps, ref: forwardedRef, name, children: props.children || DEFAULT_INVALID_MESSAGE });
} else if (typeof match === "function") {
return /* @__PURE__ */ jsx(FormCustomMessage, { match, ...messageProps, ref: forwardedRef, name });
} else {
return /* @__PURE__ */ jsx(FormBuiltInMessage, { match, ...messageProps, ref: forwardedRef, name });
}
}
);
FormMessage.displayName = MESSAGE_NAME;
var FormBuiltInMessage = React.forwardRef(
(props, forwardedRef) => {
const { match, forceMatch = false, name, children, ...messageProps } = props;
const validationContext = useValidationContext(MESSAGE_NAME, messageProps.__scopeForm);
const validity = validationContext.getFieldValidity(name);
const matches = forceMatch || validity?.[match];
if (matches) {
return /* @__PURE__ */ jsx(FormMessageImpl, { ref: forwardedRef, ...messageProps, name, children: children ?? DEFAULT_BUILT_IN_MESSAGES[match] });
}
return null;
}
);
var FormCustomMessage = React.forwardRef(
(props, forwardedRef) => {
const { match, forceMatch = false, name, id: idProp, children, ...messageProps } = props;
const validationContext = useValidationContext(MESSAGE_NAME, messageProps.__scopeForm);
const ref = React.useRef(null);
const composedRef = useComposedRefs(forwardedRef, ref);
const _id = useId();
const id = idProp ?? _id;
const customMatcherEntry = React.useMemo(() => ({ id, match }), [id, match]);
const { onFieldCustomMatcherEntryAdd, onFieldCustomMatcherEntryRemove } = validationContext;
React.useEffect(() => {
onFieldCustomMatcherEntryAdd(name, customMatcherEntry);
return () => onFieldCustomMatcherEntryRemove(name, customMatcherEntry.id);
}, [customMatcherEntry, name, onFieldCustomMatcherEntryAdd, onFieldCustomMatcherEntryRemove]);
const validity = validationContext.getFieldValidity(name);
const customErrors = validationContext.getFieldCustomErrors(name);
const hasMatchingCustomError = customErrors[id];
const matches = forceMatch || validity && !hasBuiltInError(validity) && hasMatchingCustomError;
if (matches) {
return /* @__PURE__ */ jsx(FormMessageImpl, { id, ref: composedRef, ...messageProps, name, children: children ?? DEFAULT_INVALID_MESSAGE });
}
return null;
}
);
var FormMessageImpl = React.forwardRef(
(props, forwardedRef) => {
const { __scopeForm, id: idProp, name, ...messageProps } = props;
const ariaDescriptionContext = useAriaDescriptionContext(MESSAGE_NAME, __scopeForm);
const _id = useId();
const id = idProp ?? _id;
const { onFieldMessageIdAdd, onFieldMessageIdRemove } = ariaDescriptionContext;
React.useEffect(() => {
onFieldMessageIdAdd(name, id);
return () => onFieldMessageIdRemove(name, id);
}, [name, id, onFieldMessageIdAdd, onFieldMessageIdRemove]);
return /* @__PURE__ */ jsx(Primitive.span, { id, ...messageProps, ref: forwardedRef });
}
);
var VALIDITY_STATE_NAME = "FormValidityState";
var FormValidityState = (props) => {
const { __scopeForm, name: nameProp, children } = props;
const validationContext = useValidationContext(VALIDITY_STATE_NAME, __scopeForm);
const fieldContext = useFormFieldContext(VALIDITY_STATE_NAME, __scopeForm);
const name = nameProp ?? fieldContext.name;
const validity = validationContext.getFieldValidity(name);
return /* @__PURE__ */ jsx(Fragment, { children: children(validity) });
};
FormValidityState.displayName = VALIDITY_STATE_NAME;
var SUBMIT_NAME = "FormSubmit";
var FormSubmit = React.forwardRef(
(props, forwardedRef) => {
const { __scopeForm, ...submitProps } = props;
return /* @__PURE__ */ jsx(Primitive.button, { type: "submit", ...submitProps, ref: forwardedRef });
}
);
FormSubmit.displayName = SUBMIT_NAME;
function validityStateToObject(validity) {
const object = {};
for (const key in validity) {
object[key] = validity[key];
}
return object;
}
function isHTMLElement(element) {
return element instanceof HTMLElement;
}
function isFormControl(element) {
return "validity" in element;
}
function isInvalid(control) {
return isFormControl(control) && (control.validity.valid === false || control.getAttribute("aria-invalid") === "true");
}
function getFirstInvalidControl(form) {
const elements = form.elements;
const [firstInvalidControl] = Array.from(elements).filter(isHTMLElement).filter(isInvalid);
return firstInvalidControl;
}
function isAsyncCustomMatcherEntry(entry, args) {
return entry.match.constructor.name === "AsyncFunction" || returnsPromise(entry.match, args);
}
function isSyncCustomMatcherEntry(entry) {
return entry.match.constructor.name === "Function";
}
function returnsPromise(func, args) {
return func(...args) instanceof Promise;
}
function hasBuiltInError(validity) {
let error = false;
for (const validityKey in validity) {
const key = validityKey;
if (key !== "valid" && key !== "customError" && validity[key]) {
error = true;
break;
}
}
return error;
}
function getValidAttribute(validity, serverInvalid) {
if (validity?.valid === true && !serverInvalid) return true;
return void 0;
}
function getInvalidAttribute(validity, serverInvalid) {
if (validity?.valid === false || serverInvalid) return true;
return void 0;
}
var Root = Form;
var Field = FormField;
var Label = FormLabel;
var Control = FormControl;
var Message = FormMessage;
var ValidityState = FormValidityState;
var Submit = FormSubmit;
export {
Control,
Field,
Form,
FormControl,
FormField,
FormLabel,
FormMessage,
FormSubmit,
FormValidityState,
Label,
Message,
Root,
Submit,
ValidityState,
createFormScope
};
//# sourceMappingURL=index.mjs.map