import type { JSX } from "@solidjs/web"; import { type Accessor, createContext, useContext } from "solid-js"; import type { OTPFieldInputState, OTPFieldRootChangeEventDetails, OTPFieldRootInvalidEventDetails, OTPFieldRootState, OTPFieldValidationType, } from "./otp-field-root-types.ts"; /** * Context published by `` to its descendant slot ``s. * * Solid 2 adaptation from Base UI React's `OTPFieldRootContext`: * * - Every reactive entry is exposed as `Accessor` so descendants pull live * values on demand without tripping the top-level reactive-read rule * (RFC 01). The React version ships snapshots; here we ship getters. * - Callbacks (`setValue`, `focusInput`, `queueFocusInput`, `handleInputBlur`, * `handleInputFocus`, `reportValueInvalid`, `getInputId`) are plain stable * functions owned by the root — slot inputs capture them into event * closures. * - `state` is an accessor returning the full `OTPFieldRootState` object so * each slot can derive its own `OTPFieldInputState` via the helper below. */ export interface OTPFieldRootContextValue { activeIndex: Accessor; autoComplete: Accessor; disabled: Accessor; form: Accessor; focusInput: (index: number) => void; queueFocusInput: (index: number, value: string) => void; getInputId: (index: number) => string | undefined; handleInputBlur: ( event: FocusEvent & { currentTarget: HTMLInputElement }, ) => void; handleInputFocus: ( index: number, event: FocusEvent & { currentTarget: HTMLInputElement }, ) => void; inputMode: Accessor["inputmode"]>; inputAriaLabelledBy: Accessor; invalid: Accessor; length: Accessor; mask: Accessor; pattern: Accessor; reportValueInvalid: ( value: string, details: OTPFieldRootInvalidEventDetails, ) => void; readOnly: Accessor; required: Accessor; sanitizeValue: Accessor<((value: string) => string) | undefined>; setValue: ( value: string, details: OTPFieldRootChangeEventDetails, ) => string | null; state: Accessor; validationType: Accessor; value: Accessor; /** Marker — `true` when provided by a real ``. */ readonly present: boolean; } const NOOP = (): void => {}; export const NOOP_OTP_FIELD_ROOT_CONTEXT: OTPFieldRootContextValue = { activeIndex: () => 0, autoComplete: () => undefined, disabled: () => false, form: () => undefined, focusInput: NOOP, queueFocusInput: NOOP, getInputId: () => undefined, handleInputBlur: NOOP, handleInputFocus: NOOP, inputMode: () => undefined, inputAriaLabelledBy: () => undefined, invalid: () => false, length: () => 0, mask: () => false, pattern: () => undefined, reportValueInvalid: NOOP, readOnly: () => false, required: () => false, sanitizeValue: () => undefined, setValue: () => null, state: () => ({ disabled: false, valid: null, touched: false, dirty: false, filled: false, focused: false, complete: false, length: 0, readOnly: false, required: false, value: "", }), validationType: () => "numeric", value: () => "", present: false, }; export const OTPFieldRootContext = createContext( NOOP_OTP_FIELD_ROOT_CONTEXT, ); /** * Read the nearest `` context. Throws when no real root is an * ancestor — slot ``s are meaningless without their root. */ export function useOTPFieldRootContext(): OTPFieldRootContextValue { const context = useContext(OTPFieldRootContext); if (!context.present) { throw new Error( "Base UI: OTPFieldRootContext is missing. OTPField parts must be placed within .", ); } return context; } /** * Derive per-slot state from the root state + this slot's value and index. * Pure helper — call it from a memo if you want reactive updates. */ export function getOTPFieldInputState( state: OTPFieldRootState, value: string, index: number, ): OTPFieldInputState { return { ...state, value, index, filled: value !== "", }; }