/** * Stage 4.5 port of Base UI React's `useListNavigation` hook. * * React source: * `base-ui/packages/react/src/floating-ui-react/hooks/useListNavigation.ts` * * Adds arrow-key navigation (1D + 2D grid), Home/End, typeable-combobox * integration, virtual focus (`aria-activedescendant`), and tree-aware * submenu open/close behavior. This is the single biggest interaction * hook in the fork — every listbox / menu / menubar / combobox-grid * consumer in tier-3a reaches for it. * * ### React → Solid deviations * * 1. `useRef` → `MutableRef` (matches sibling hooks). `useValueAsRef` * (React fork helper that mirrors a prop into a ref) is replaced by * reading the prop directly inside handlers/effects — Solid props * are stable references for the lifetime of the component body, and * callers pass reactive accessors when live updates matter (see the * `MaybeAccessor` wrappers on `activeIndex`, `selectedIndex`, * `disabledIndices`, etc.). * * 2. `useStableCallback` → `createStableCallback` from * `src/internals/stable-callback.ts`. Used only where the React * source stabilized identity for effect deps — Solid effects don't * re-fire on callback identity so most handlers are plain functions. * * 3. `useIsoLayoutEffect` (with React single-callback + deps) → * `createRenderEffect`-backed `useIsoLayoutEffect(compute, apply)` * in split form. Tracked reads go in the `compute` factory; the * side-effect body runs in `apply`. * * 4. `useMemo` for `item` / `floating` / `trigger` / `reference` bags * collapses to plain literals. Solid doesn't re-run component bodies, * so the bag identity is already stable for the lifetime of the call * site. Closed-over handlers read the latest prop values directly. * * 5. `isReactEvent` branches and `.nativeEvent` unwraps drop out: Solid * event handlers receive native events directly. * * 6. Shadow-DOM-safe `contains()` from `src/internals/dom/contains.ts`. * * 7. `store.useState('open')` subscribes a Solid tracking context when * read inside `createEffect` / `useIsoLayoutEffect`; the matching * sync escape hatch is `store.state.open` (via * `components/floating-root-store.ts`). * * 8. The `MaybeAccessor` pattern: callers may pass either a literal * or a `() => T` for the reactive option fields. `readOption` * normalises both at the call site (inside effects/handlers) so the * live value is read at event time. * * ### Reason strings * * Every `store.setOpen` emits one of: * - `REASONS.listNavigation` — opening on arrow-down on trigger * (nested / main-orientation paths), or closing via the * cross-orientation "close submenu" key in nested mode. * - `REASONS.focusOut` — Shift+Tab on the floating element. * * Matches React's source strings 1:1 — the React fork carries these * same two in this hook. */ import type { FloatingTreeStore } from "../components/floating-tree-store.js"; import type { ElementProps, FloatingContext, FloatingRootContext, MutableRef } from "../types.js"; import { type MaybeAccessor } from "../../maybe-accessor.js"; type Orientation = "vertical" | "horizontal" | "both"; type DisabledIndices = ReadonlyArray | ((index: number) => boolean); export interface UseListNavigationProps { /** * A ref that holds an array of list items. * @default empty list */ listRef: MutableRef>; /** * The index of the currently active (focused or highlighted) item. May * be a literal or an accessor. * @default null */ activeIndex: MaybeAccessor; /** * Callback invoked when navigation changes the active index. */ onNavigate?: ((activeIndex: number | null, event: Event | undefined) => void) | undefined; /** * Whether the hook is enabled. * @default true */ enabled?: MaybeAccessor | undefined; /** * Currently selected item index. * @default null */ selectedIndex?: MaybeAccessor | undefined; /** * Whether to focus the item upon opening. `'auto'` infers from input * type (keyboard vs pointer). * @default 'auto' */ focusItemOnOpen?: MaybeAccessor | undefined; /** * Whether hovering an item synchronizes focus. * @default true */ focusItemOnHover?: MaybeAccessor | undefined; /** * Whether pressing an arrow key on the main axis opens the floating * element. * @default true */ openOnArrowKeyDown?: MaybeAccessor | undefined; /** * Manually-specified indices to skip during navigation. Overrides the * default `disabled` / `aria-disabled` detection. */ disabledIndices?: MaybeAccessor | undefined; /** * Whether focus can escape the list (requires `loopFocus` + `virtual`). * @default false */ allowEscape?: MaybeAccessor | undefined; /** * Whether focus should loop around when navigating past the ends. * @default false */ loopFocus?: MaybeAccessor | undefined; /** * Whether the list is nested (e.g. submenu). * @default false */ nested?: MaybeAccessor | undefined; /** * Parent list orientation (for nested lists in a `Composite`). */ parentOrientation?: MaybeAccessor | undefined; /** * Whether navigation is RTL. * @default false */ rtl?: MaybeAccessor | undefined; /** * Virtual focus via `aria-activedescendant`. * @default false */ virtual?: MaybeAccessor | undefined; /** * Orientation in which navigation occurs. * @default 'vertical' */ orientation?: MaybeAccessor | undefined; /** * Number of columns. `>1` enables grid navigation. * @default 1 */ cols?: MaybeAccessor | undefined; /** * Root component id. Used for `aria-activedescendant` when `virtual`. */ id?: MaybeAccessor | undefined; /** * Whether to clear the active index when the pointer leaves an item. * @default true */ resetOnPointerLeave?: MaybeAccessor | undefined; /** * External `FloatingTreeStore` when the context-provided one is not * reachable. */ externalTree?: FloatingTreeStore | undefined; } /** * Adds arrow-key-based navigation of a list of items, using real DOM focus * or virtual focus via `aria-activedescendant`. * @see https://floating-ui.com/docs/useListNavigation */ export declare function useListNavigation(context: FloatingRootContext | FloatingContext, props: UseListNavigationProps): ElementProps; export {}; //# sourceMappingURL=use-list-navigation.d.ts.map