UNPKG

@base-ui/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

131 lines (129 loc) 4.51 kB
'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { getCombinedFieldValidityData } from "../../field/utils/getCombinedFieldValidityData.mjs"; import { useFormContext } from "../form-context/FormContext.mjs"; export function useFieldControlRegistration(params) { const { commit, invalid, markedDirtyRef, name, setRegisteredFieldName, registeredFieldIdRef, setValidityData, validityData } = params; const { formRef } = useFormContext(); const activeFieldControlSourceRef = React.useRef(null); const registrationRef = React.useRef(null); const initialValueCapturedRef = React.useRef(false); const getValueForForm = useStableCallback(() => { const registration = registrationRef.current; if (!registration) { return undefined; } if (registration.getValue) { return registration.getValue(); } return registration.value; }); function getRegistrationValue(registration) { return registration.value === undefined ? getValueForForm() : registration.value; } const validate = useStableCallback(() => { const registration = registrationRef.current; markedDirtyRef.current = true; if (!registration) { commit(validityData.value); return; } commit(getRegistrationValue(registration)); }); function refreshRegistration() { const registration = registrationRef.current; if (!registration || !registration.id) { return; } formRef.current.fields.set(registration.id, { getValue: getValueForForm, name: name ?? registration.name, controlRef: registration.controlRef, validityData: getCombinedFieldValidityData(validityData, invalid), validate }); } function deleteRegistration(id = registrationRef.current?.id) { if (id) { formRef.current.fields.delete(id); } } // The baseline belongs to the field, not to a control instance: registration re-runs on every // value change, and a control that unmounts and remounts (or is swapped for another one) comes // back as a brand new registration. Capturing more than once would turn whichever value the // control happens to hold at that point into the initial value, so a modified field would read // pristine and its real initial value would read dirty. Consumers that want a fresh baseline // remount or key `<Field.Root>` itself. function captureInitialValue(registration) { if (initialValueCapturedRef.current) { return; } initialValueCapturedRef.current = true; const initialValue = getRegistrationValue(registration); setValidityData(prev => prev.initialValue === initialValue ? prev : { ...prev, initialValue }); } useIsoLayoutEffect(() => { const registration = registrationRef.current; if (!registration || !registration.id) { return; } setRegisteredFieldName(name ? undefined : registration.name); formRef.current.fields.set(registration.id, { getValue: getValueForForm, name: name ?? registration.name, controlRef: registration.controlRef, validityData: getCombinedFieldValidityData(validityData, invalid), validate }); }, [formRef, getValueForForm, invalid, name, setRegisteredFieldName, validate, validityData]); useIsoLayoutEffect(() => { const fields = formRef.current.fields; return () => { const id = registrationRef.current?.id; if (id) { fields.delete(id); } }; }, [formRef]); const register = useStableCallback((source, registration) => { if (!registration) { if (activeFieldControlSourceRef.current === source) { activeFieldControlSourceRef.current = null; deleteRegistration(); registrationRef.current = null; setRegisteredFieldName(undefined); registeredFieldIdRef.current = undefined; } return; } const previousId = registrationRef.current?.id; activeFieldControlSourceRef.current = source; registrationRef.current = registration; if (!name) { setRegisteredFieldName(registration.name); } registeredFieldIdRef.current = registration.id; if (previousId && previousId !== registration.id) { deleteRegistration(previousId); } captureInitialValue(registration); refreshRegistration(); }); return [validate, register]; }