import type { CascaderCollapse, CascaderFlatNode, CascaderIndex, CascaderNode, CascaderPathSegment, CascaderSelectable, } from "@/components/reui/cascader/cascader-types" /** Root level key in `childrenOf`. NUL-prefixed so no real value collides. */ export const CASCADER_ROOT_KEY = "\u0000root" /** * Paging pseudo-node prefix. Written as an escape sequence, not a raw 0x00 * byte: a literal NUL makes this module read as binary and `grep -I` skips it. */ export const CASCADER_MORE_PREFIX = "\u0000more:" /* -------------------------------------------------------------------------- */ /* Scroll layout */ /* -------------------------------------------------------------------------- */ /** * The four scroll classes. In the lib because `cascader-columns.tsx` needs them * too and must not import `cascader.tsx`. Shell > bound > scrollport > rows. */ /** * The bound. `min()` over an undefined custom property is an INVALID * declaration that drops the whole max-height, hence the `100vh` fallback: an * inline panel has no positioner, so no `--available-height`. `24rem` is the * default cap, so the common case sets no variable at all. */ export const CASCADER_LIST_HEIGHT_CLASS = "max-h-[min(var(--available-height,100vh),var(--cascader-max-height,24rem))]" /** * Each style's list padding: rows take it as `padding`, the scrollport as * `scroll-padding` floored at 4px so lyra's `0` does not strand a row. * Mirrored per style from `registry/styles/style-*.css`; keep them in step. */ export const CASCADER_LIST_PAD_CLASS = "[--cascader-list-pad:4px]" /** The scrollport: a `max-h-*` on the ScrollArea ROOT bounds nothing. */ export const CASCADER_SCROLL_CLASS = "size-full min-h-0 **:data-[slot=scroll-area-viewport]:h-full **:data-[slot=scroll-area-viewport]:overscroll-contain **:data-[slot=scroll-area-viewport]:scroll-py-[max(var(--cascader-list-pad,4px),4px)]" /** The rows' box; `data-empty:p-0` keeps an empty state from double inset. */ export const CASCADER_ROWS_CLASS = "p-(--cascader-list-pad,4px) data-empty:p-0" /* -------------------------------------------------------------------------- */ /* Tab order */ /* -------------------------------------------------------------------------- */ /** * What counts as a keyboard stop. Base UI's ScrollArea viewport makes ITSELF * tabbable when content overflows (`hiddenState.x && hiddenState.y ? -1 : 0`), * an unnamed stop per level that `ui/scroll-area.tsx` will not let us suppress. */ const CASCADER_TAB_STOP_SELECTOR = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])' /** The stop to step over: nothing here focuses the scrollport on purpose. */ const CASCADER_TAB_SKIP_SELECTOR = '[data-slot="scroll-area-viewport"]' /** Out of the tab order whatever they contain; layout is never consulted. */ const CASCADER_TAB_HIDDEN_SELECTOR = "[hidden],[inert],[aria-hidden='true']" /** The panel's real keyboard stops, in DOM order. */ function getCascaderTabStops(panel: HTMLElement): HTMLElement[] { const stops = Array.from( panel.querySelectorAll(CASCADER_TAB_STOP_SELECTOR) ) return stops.filter( (stop) => // Option and trail rows are real `