import React, { useCallback, useMemo, useRef, useState } from "react"; import { useClientLayoutEffect } from "../../../utils-external"; import { createStrictContext } from "../../../utils/helpers"; import { FormFieldType, useFormField } from "../../useFormField"; import { ComboboxProps } from "../types"; interface InputContextValue extends FormFieldType { clearInput: NonNullable; error?: ComboboxProps["error"]; focusInput: () => void; inputRef: React.RefObject; value: string; setValue: (text: string) => void; onChange: (newValue: string) => void; searchTerm: string; setSearchTerm: React.Dispatch>; shouldAutocomplete?: boolean; toggleOpenButtonRef: React.MutableRefObject; hideCaret: boolean; setHideCaret: React.Dispatch>; anchorRef: HTMLDivElement | null; setAnchorRef: React.Dispatch>; } const { Provider: InputContextProvider, useContext: useInputContext } = createStrictContext({ name: "InputContext", errorMessage: "useInputContext must be used within an InputContextProvider", }); interface Props { children: React.ReactNode; value: { defaultValue: ComboboxProps["defaultValue"]; description: ComboboxProps["description"]; disabled: ComboboxProps["disabled"]; readOnly: ComboboxProps["readOnly"]; error: ComboboxProps["error"]; errorId: ComboboxProps["errorId"]; id: ComboboxProps["id"]; value: ComboboxProps["value"]; onChange: ComboboxProps["onChange"]; onClear: ComboboxProps["onClear"]; shouldAutocomplete: ComboboxProps["shouldAutocomplete"]; size: ComboboxProps["size"]; }; } const InputProvider = ({ children, value: props }: Props) => { const { defaultValue = "", description, disabled, readOnly, error, errorId, id: externalId, value: externalValue, onChange: externalOnChange, onClear, shouldAutocomplete, size, } = props; const formFieldProps = useFormField( { description, disabled, readOnly, error, errorId, id: externalId, size, }, "comboboxfield", ); const inputRef = useRef(null); const toggleOpenButtonRef = useRef(null); const [internalValue, setInternalValue] = useState(defaultValue); const [hideCaret, setHideCaret] = useState(false); const [anchorRef, setAnchorRef] = useState(null); const value = useMemo( () => String(externalValue ?? internalValue), [externalValue, internalValue], ); const [searchTerm, setSearchTerm] = useState(value); const onChange = useCallback( (newValue: string) => { externalValue ?? setInternalValue(newValue); setSearchTerm(newValue); externalOnChange?.(newValue); }, [externalValue, externalOnChange], ); const clearInput = useCallback( (event: React.PointerEvent | React.KeyboardEvent | React.FocusEvent) => { onClear?.(event); externalOnChange?.(""); setInternalValue(""); setSearchTerm(""); }, [externalOnChange, onClear], ); const focusInput = useCallback(() => { inputRef.current?.focus?.(); }, []); useClientLayoutEffect(() => { if (shouldAutocomplete && inputRef && value !== searchTerm) { inputRef.current?.setSelectionRange?.(searchTerm.length, value.length); } }, [value, searchTerm, shouldAutocomplete]); const contextValue = { ...formFieldProps, clearInput, error, focusInput, inputRef, value, setValue: setInternalValue, onChange, searchTerm, setSearchTerm, shouldAutocomplete, toggleOpenButtonRef, hideCaret, setHideCaret, anchorRef, setAnchorRef, }; return ( {children} ); }; export { InputProvider as InputContextProvider, useInputContext };