UNPKG

@base-ui-components/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.

134 lines (132 loc) 4.13 kB
'use client'; import * as React from 'react'; import { useEventCallback } from '@base-ui-components/utils/useEventCallback'; import { useControlled } from '@base-ui-components/utils/useControlled'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useFieldRootContext } from "../root/FieldRootContext.js"; import { fieldValidityMapping } from "../utils/constants.js"; import { useRenderElement } from "../../utils/useRenderElement.js"; import { useField } from "../useField.js"; import { useBaseUiId } from "../../utils/useBaseUiId.js"; import { useFieldControlValidation } from "./useFieldControlValidation.js"; /** * The form control to label and validate. * Renders an `<input>` element. * * You can omit this part and use any Base UI input component instead. For example, * [Input](https://base-ui.com/react/components/input), [Checkbox](https://base-ui.com/react/components/checkbox), * or [Select](https://base-ui.com/react/components/select), among others, will work with Field out of the box. * * Documentation: [Base UI Field](https://base-ui.com/react/components/field) */ export const FieldControl = /*#__PURE__*/React.forwardRef(function FieldControl(componentProps, forwardedRef) { const { render, className, id: idProp, name: nameProp, value: valueProp, disabled: disabledProp = false, onValueChange, defaultValue, ...elementProps } = componentProps; const { state: fieldState, name: fieldName, disabled: fieldDisabled } = useFieldRootContext(); const disabled = fieldDisabled || disabledProp; const name = fieldName ?? nameProp; const state = React.useMemo(() => ({ ...fieldState, disabled }), [fieldState, disabled]); const { setControlId, labelId, setTouched, setDirty, validityData, setFocused, setFilled, validationMode } = useFieldRootContext(); const { getValidationProps, getInputValidationProps, commitValidation, inputRef } = useFieldControlValidation(); const id = useBaseUiId(idProp); useIsoLayoutEffect(() => { setControlId(id); return () => { setControlId(undefined); }; }, [id, setControlId]); useIsoLayoutEffect(() => { const hasExternalValue = valueProp != null; if (inputRef.current?.value || hasExternalValue && valueProp !== '') { setFilled(true); } else if (hasExternalValue && valueProp === '') { setFilled(false); } }, [inputRef, setFilled, valueProp]); const [value, setValueUnwrapped] = useControlled({ controlled: valueProp, default: defaultValue, name: 'FieldControl', state: 'value' }); const setValue = useEventCallback((nextValue, event) => { setValueUnwrapped(nextValue); onValueChange?.(nextValue, event); }); useField({ id, name, commitValidation, value, getValue: () => inputRef.current?.value, controlRef: inputRef }); const element = useRenderElement('input', componentProps, { ref: forwardedRef, state, props: [{ id, disabled, name, ref: inputRef, 'aria-labelledby': labelId, value, onChange(event) { if (value != null) { setValue(event.currentTarget.value, event.nativeEvent); } setDirty(event.currentTarget.value !== validityData.initialValue); setFilled(event.currentTarget.value !== ''); }, onFocus() { setFocused(true); }, onBlur(event) { setTouched(true); setFocused(false); if (validationMode === 'onBlur') { commitValidation(event.currentTarget.value); } }, onKeyDown(event) { if (event.currentTarget.tagName === 'INPUT' && event.key === 'Enter') { setTouched(true); commitValidation(event.currentTarget.value); } } }, getValidationProps(), getInputValidationProps(), elementProps], customStyleHookMapping: fieldValidityMapping }); return element; }); if (process.env.NODE_ENV !== "production") FieldControl.displayName = "FieldControl";