@allxsmith/bestax-bulma 5.60.0 → 5.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/Carousel.js +3 -43
  4. package/dist/esm/components/Carousel.js.map +1 -1
  5. package/dist/esm/components/Command.js +3 -3
  6. package/dist/esm/components/Navbar.js +3 -1
  7. package/dist/esm/components/Navbar.js.map +1 -1
  8. package/dist/esm/components/Popover.js +7 -27
  9. package/dist/esm/components/Popover.js.map +1 -1
  10. package/dist/esm/components/Sidebar.js +17 -58
  11. package/dist/esm/components/Sidebar.js.map +1 -1
  12. package/dist/esm/components/TreeView.js +14 -10
  13. package/dist/esm/components/TreeView.js.map +1 -1
  14. package/dist/esm/form/ColorPicker.js +4 -4
  15. package/dist/esm/form/ColorPicker.js.map +1 -1
  16. package/dist/esm/form/Combobox.js +11 -16
  17. package/dist/esm/form/Combobox.js.map +1 -1
  18. package/dist/esm/form/Field.js.map +1 -1
  19. package/dist/esm/form/TreeSelect.js +395 -0
  20. package/dist/esm/form/TreeSelect.js.map +1 -0
  21. package/dist/esm/form/_pickerInternals/PickerPopover.js +5 -1
  22. package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
  23. package/dist/esm/form/_selectInternals/DialogSelect.js +208 -0
  24. package/dist/esm/form/_selectInternals/DialogSelect.js.map +1 -0
  25. package/dist/esm/form/_selectInternals/ValueTags.js +28 -0
  26. package/dist/esm/form/_selectInternals/ValueTags.js.map +1 -0
  27. package/dist/esm/helpers/overlayStack.js +33 -1
  28. package/dist/esm/helpers/overlayStack.js.map +1 -1
  29. package/dist/esm/helpers/treeData.js +97 -0
  30. package/dist/esm/helpers/treeData.js.map +1 -0
  31. package/dist/esm/helpers/useBulmaViewport.js +82 -0
  32. package/dist/esm/helpers/useBulmaViewport.js.map +1 -0
  33. package/dist/esm/helpers/useComboboxEngine.js +2 -2
  34. package/dist/esm/helpers/useComboboxEngine.js.map +1 -1
  35. package/dist/esm/helpers/useControlled.js +30 -0
  36. package/dist/esm/helpers/useControlled.js.map +1 -0
  37. package/dist/esm/helpers/useInertSiblings.js +7 -3
  38. package/dist/esm/helpers/useInertSiblings.js.map +1 -1
  39. package/dist/esm/helpers/useOutsidePress.js +53 -0
  40. package/dist/esm/helpers/useOutsidePress.js.map +1 -0
  41. package/dist/esm/helpers/useOverlayFocus.js +74 -0
  42. package/dist/esm/helpers/useOverlayFocus.js.map +1 -0
  43. package/dist/esm/helpers/useTreeView.js +15 -13
  44. package/dist/esm/helpers/useTreeView.js.map +1 -1
  45. package/dist/esm/layout/AppFrame.js +438 -0
  46. package/dist/esm/layout/AppFrame.js.map +1 -0
  47. package/dist/extras.css +1 -1
  48. package/dist/extras.css.map +1 -1
  49. package/dist/index.cjs +1767 -518
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.esm.js +2 -0
  52. package/dist/index.esm.js.map +1 -1
  53. package/dist/types/components/Navbar.d.ts +4 -3
  54. package/dist/types/form/Field.d.ts +1 -1
  55. package/dist/types/form/TreeSelect.d.ts +308 -0
  56. package/dist/types/form/_pickerInternals/PickerPopover.d.ts +8 -0
  57. package/dist/types/form/_selectInternals/DialogSelect.d.ts +86 -0
  58. package/dist/types/form/_selectInternals/ValueTags.d.ts +40 -0
  59. package/dist/types/helpers/overlayStack.d.ts +17 -0
  60. package/dist/types/helpers/treeData.d.ts +48 -0
  61. package/dist/types/helpers/useBulmaViewport.d.ts +34 -0
  62. package/dist/types/helpers/useControlled.d.ts +14 -0
  63. package/dist/types/helpers/useInertSiblings.d.ts +4 -1
  64. package/dist/types/helpers/useOutsidePress.d.ts +24 -0
  65. package/dist/types/helpers/useOverlayFocus.d.ts +44 -0
  66. package/dist/types/helpers/useTreeView.d.ts +30 -0
  67. package/dist/types/index.d.ts +2 -0
  68. package/dist/types/layout/AppFrame.d.ts +306 -0
  69. package/dist/types-cjs/components/Navbar.d.ts +4 -3
  70. package/dist/types-cjs/form/Field.d.ts +1 -1
  71. package/dist/types-cjs/form/TreeSelect.d.ts +308 -0
  72. package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +8 -0
  73. package/dist/types-cjs/form/_selectInternals/DialogSelect.d.ts +86 -0
  74. package/dist/types-cjs/form/_selectInternals/ValueTags.d.ts +40 -0
  75. package/dist/types-cjs/helpers/overlayStack.d.ts +17 -0
  76. package/dist/types-cjs/helpers/treeData.d.ts +48 -0
  77. package/dist/types-cjs/helpers/useBulmaViewport.d.ts +34 -0
  78. package/dist/types-cjs/helpers/useControlled.d.ts +14 -0
  79. package/dist/types-cjs/helpers/useInertSiblings.d.ts +4 -1
  80. package/dist/types-cjs/helpers/useOutsidePress.d.ts +24 -0
  81. package/dist/types-cjs/helpers/useOverlayFocus.d.ts +44 -0
  82. package/dist/types-cjs/helpers/useTreeView.d.ts +30 -0
  83. package/dist/types-cjs/index.d.ts +2 -0
  84. package/dist/types-cjs/layout/AppFrame.d.ts +306 -0
  85. package/dist/versions/bestax-no-dark-mode.css +1 -1
  86. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  87. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  88. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  89. package/dist/versions/bestax-no-helpers.css +1 -1
  90. package/dist/versions/bestax-no-helpers.css.map +1 -1
  91. package/dist/versions/bestax-prefixed.css +1 -1
  92. package/dist/versions/bestax-prefixed.css.map +1 -1
  93. package/package.json +1 -1
  94. package/src/scss/form/_index.scss +1 -0
  95. package/src/scss/form/_picker-popover.scss +6 -0
  96. package/src/scss/form/_treeselect.scss +146 -0
  97. package/src/scss/layout/_appframe.scss +306 -0
  98. package/src/scss/layout/_index.scss +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"useComboboxEngine.js","sources":["../../../src/helpers/useComboboxEngine.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport {\n useCallback,\n useEffect,\n useId,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport type React from 'react';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\nimport { getActiveElementInTree, isEventInside } from './shadowDom';\n\n/** What an item tells the engine about itself. */\nexport interface ComboboxItemState {\n /** What a pick hands back. Unique within the combobox. */\n value: string;\n /** Reachable with the arrows, never picked. */\n disabled: boolean;\n /** The value, or one of them. */\n checked: boolean;\n}\n\n/** An option on the page, with its element. */\ninterface ShownOption extends ComboboxItemState {\n element: HTMLElement;\n}\n\n/**\n * Where the highlight lands once an opened list has rendered, or once typing\n * has changed what's in it:\n *\n * - `first` and `last` (Down and Up Arrow): the checked option, otherwise the\n * first or last enabled one;\n * - `open` (a click, or any other way of opening): the checked option,\n * otherwise the first enabled one with `keepFirst`, otherwise nothing;\n * - `typed`: the first enabled option with `keepFirst`, otherwise nothing;\n * - `none` (Alt+Down Arrow): nothing.\n *\n * Only an enabled option takes it, so a disabled checked one is passed over.\n */\ntype Landing = 'first' | 'last' | 'open' | 'typed' | 'none';\n\n/** Options for `useComboboxEngine`. */\nexport interface ComboboxEngineOptions {\n /** Controlled open state. When set, opening and closing only report through `onOpenChange`. */\n open?: boolean;\n /** Initial open state when uncontrolled. */\n defaultOpen: boolean;\n /** Called with the open state asked for, when it differs from the current one. */\n onOpenChange?: (open: boolean) => void;\n /** Called with an item's `value` when it's picked. */\n onSelect?: (value: string) => void;\n /**\n * Called when Enter goes on to the form with nothing to pick, before the\n * list closes, so an owner that shows something other than the typing can\n * put it back before the form reads the field. An `inline` list doesn't\n * close, so this is the one word it gets.\n */\n onSettle?: () => void;\n /** Highlights the first enabled item whenever the list opens or its items change. */\n keepFirst: boolean;\n /** Several values: a pick keeps the list open. */\n multiple: boolean;\n /** The list is always on screen, so nothing opens or closes it. */\n inline: boolean;\n /**\n * The list's panel is portaled, so it has no server markup and shows only\n * from the commit after hydration. Until it has rendered, an open list\n * isn't taken to be on the page, so no `aria-controls` names it early.\n */\n deferred: boolean;\n}\n\n/** What `useComboboxEngine` hands the parts of a combobox. */\nexport interface ComboboxEngine {\n /** Whether the list is open. Always, with `inline`. */\n open: boolean;\n /**\n * The field's `aria-expanded`: open, with a listbox that has an option in\n * it on the page. Until the open list has rendered, it's taken to have\n * one, so server markup for an open list in place says it's expanded. A\n * `deferred` list isn't in server markup, so it's taken to have none.\n */\n expanded: boolean;\n /** The highlighted option's value, if any. */\n highlighted: string | null;\n /** Whether the user has typed since the list opened. */\n typed: boolean;\n /** The listbox's id. */\n listboxId: string;\n /** An item's id, which follows from its value. */\n itemId: (value: string) => string;\n /** For the element around the field and, unless it's portaled, the list. */\n rootRef: React.MutableRefObject<HTMLDivElement | null>;\n /** For the field. */\n inputRef: React.MutableRefObject<HTMLInputElement | null>;\n /** For the listbox. */\n listboxRef: React.MutableRefObject<HTMLDivElement | null>;\n /**\n * Records the floating panel while it's on screen, which makes it count as\n * inside, and makes an Escape that closes it stop there. Returns the\n * function that forgets it.\n */\n registerPopup: (element: HTMLElement) => () => void;\n /** Records an item's element. Returns the function that forgets it. */\n registerItem: (element: HTMLElement, state: ComboboxItemState) => () => void;\n /** The field's keys, to run after the caller's own handler. */\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;\n /** A change to the field's value: the user typed. */\n onChange: () => void;\n /** A click in the field. */\n onClick: () => void;\n /** Focus leaving the field. */\n onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;\n /** The pointer moved over an enabled option. */\n hover: (value: string) => void;\n /** A click on an enabled option. Focus ends up in the field. */\n pick: (value: string) => void;\n /** For anything in the list: a press there doesn't take focus from the field. */\n keepFocus: (event: React.MouseEvent) => void;\n /**\n * For the floating panel: marks a press as inside, which React reports from\n * content the panel portals elsewhere as well.\n */\n markInside: (event: React.PointerEvent) => void;\n}\n\n/** An IME is composing, so the key belongs to it (`keyCode` 229 is Safari's word for it). */\nconst isComposing = (event: React.KeyboardEvent): boolean =>\n event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;\n\nconst sameValues = (a: string[], b: string[]): boolean =>\n a.length === b.length && a.every((value, i) => value === b[i]);\n\n/**\n * The keys, the open state, the highlight and the ARIA of an editable\n * combobox whose list the app renders and filters (#1083). DOM focus stays\n * in the field the whole time: the highlight is `aria-activedescendant`, as\n * the ARIA Authoring Practices ask of a combobox, where `useMenuKeys` moves\n * real focus, as a menu should.\n *\n * The options are read from the listbox in document order whenever they're\n * needed, so options in groups and options that come and go are found, and\n * the highlight is remembered by value, so it survives a re-render. The text\n * in the field and any value are the app's: a pick only calls\n * `onSelect`.\n *\n * @function useComboboxEngine\n * @param options - The open state, the pick callback and the shape.\n * @returns The state and handlers the parts wire up.\n */\nexport function useComboboxEngine({\n open: controlledOpen,\n defaultOpen,\n onOpenChange,\n onSelect,\n onSettle,\n keepFirst,\n multiple,\n inline,\n deferred,\n}: ComboboxEngineOptions): ComboboxEngine {\n const baseId = useId();\n const listboxId = `${baseId}-listbox`;\n const itemPrefix = `${baseId}-item-`;\n // The value goes into the id encoded, so a value with a space in it still\n // makes one id, not two.\n const itemId = useCallback(\n (value: string) => itemPrefix + encodeURIComponent(value),\n [itemPrefix]\n );\n\n const rootRef = useRef<HTMLDivElement | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n const listboxRef = useRef<HTMLDivElement | null>(null);\n const popupRef = useRef<HTMLElement | null>(null);\n const insidePressRef = useRef<Event | null>(null);\n const optionsRef = useRef(new Map<HTMLElement, ComboboxItemState>());\n const landingRef = useRef<Landing | null>(null);\n const wasOpenRef = useRef(false);\n const shownValuesRef = useRef<string[]>([]);\n // Whether the next highlight change is one to scroll to: a key's or a\n // landing's. The pointer's isn't, since the pointer is already on that\n // option, and scrolling would move the list out from under it.\n const scrollToHighlightRef = useRef(false);\n\n const isControlled = controlledOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = inline || (controlledOpen ?? internalOpen);\n\n const [highlighted, setHighlighted] = useState<string | null>(null);\n const [typed, setTyped] = useState(false);\n // Whether a listbox with an option in it is on the page, which only the\n // page can say, so it's read once the open list has rendered.\n const [listShown, setListShown] = useState<boolean | null>(null);\n // Options register from their own effects, which needn't render the\n // component that holds this hook, so they ask for a render to be read.\n const [, refresh] = useReducer((n: number) => n + 1, 0);\n\n // Closing forgets the highlight, the typing and what was on the page,\n // during render, React's pattern for adjusting state when other state\n // changes.\n if (!open) {\n if (highlighted !== null) setHighlighted(null);\n if (typed) setTyped(false);\n if (listShown !== null) setListShown(null);\n }\n const expanded = open && (listShown ?? !deferred);\n\n /** The options on the page, in document order, groups included. */\n const shownOptions = useCallback((): ShownOption[] => {\n const listbox = listboxRef.current;\n if (!listbox) return [];\n const shown: ShownOption[] = [];\n listbox\n .querySelectorAll<HTMLElement>('[role=\"option\"]')\n .forEach(element => {\n // Only the Combobox's own options: an element given the role by\n // hand isn't one.\n const state = optionsRef.current.get(element);\n if (state) shown.push({ element, ...state });\n });\n return shown;\n }, []);\n\n const requestOpen = useCallback(\n (next: boolean, landing: Landing = 'open') => {\n if (inline || next === open) return;\n landingRef.current = next ? landing : null;\n onOpenChange?.(next);\n if (!isControlled) setInternalOpen(next);\n },\n [inline, open, onOpenChange, isControlled]\n );\n const close = useCallback(() => requestOpen(false), [requestOpen]);\n\n // After every render, read the list: whether it has options, and where\n // the highlight goes now. A highlighted option that has left takes the\n // highlight with it, or hands it to the first enabled one under\n // `keepFirst`, which also moves it there whenever the options change.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose\n useClientLayoutEffect(() => {\n const opened = open && !wasOpenRef.current;\n wasOpenRef.current = open;\n if (!open) {\n landingRef.current = null;\n shownValuesRef.current = [];\n return;\n }\n const options = shownOptions();\n const values = options.map(option => option.value);\n const changed = !sameValues(values, shownValuesRef.current);\n shownValuesRef.current = values;\n setListShown(values.length > 0);\n\n const landing = landingRef.current ?? (opened ? 'open' : null);\n landingRef.current = null;\n const enabled = options.filter(option => !option.disabled);\n const checked = enabled.find(option => option.checked)?.value;\n const first = enabled.length > 0 ? enabled[0].value : null;\n const last = enabled.length > 0 ? enabled[enabled.length - 1].value : null;\n let next = highlighted;\n if (landing === 'first') next = checked ?? first;\n else if (landing === 'last') next = checked ?? last;\n else if (landing === 'open') next = checked ?? (keepFirst ? first : null);\n else if (landing === 'typed') next = keepFirst ? first : null;\n else if (landing === 'none') next = null;\n else if (keepFirst && changed) next = first;\n // Without `keepFirst`, which moved it above, a highlighted option\n // that left takes the highlight with it.\n else if (next !== null && !values.includes(next)) next = null;\n // Only a change is set. StrictMode runs this twice on mount, and the\n // second run, with the landing used up, works out the rendered highlight\n // again, so it leaves the first run's landing in place.\n if (next !== highlighted) {\n scrollToHighlightRef.current = true;\n setHighlighted(next);\n }\n });\n\n // A browser scrolls to what has focus, not to an active descendant, so\n // the highlighted option is brought into view here, when a key or a\n // landing moved it there.\n useClientLayoutEffect(() => {\n if (highlighted === null || !scrollToHighlightRef.current) return;\n scrollToHighlightRef.current = false;\n const option = shownOptions().find(shown => shown.value === highlighted);\n option?.element.scrollIntoView?.({ block: 'nearest' });\n }, [highlighted, shownOptions]);\n\n // A press outside the field and the list closes it. The path is checked\n // rather than the target, so it works in a shadow root.\n useEffect(() => {\n const root = rootRef.current;\n if (!open || inline || !root) return undefined;\n const onPress = (event: PointerEvent) => {\n if (\n event === insidePressRef.current ||\n isEventInside(event, rootRef.current) ||\n isEventInside(event, popupRef.current)\n ) {\n return;\n }\n close();\n };\n // Its document, an iframe's when the tree renders into one.\n const doc = root.ownerDocument;\n doc.addEventListener('pointerdown', onPress);\n return () => doc.removeEventListener('pointerdown', onPress);\n }, [open, inline, close]);\n\n const submit = useCallback(\n (value: string) => {\n onSelect?.(value);\n if (!multiple) close();\n },\n [onSelect, multiple, close]\n );\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n // The caller's handler ran first and can keep a key, and an IME that's\n // composing owns every key.\n if (event.defaultPrevented || isComposing(event)) return;\n const { key, altKey } = event;\n\n if (!open) {\n if (key === 'ArrowDown' || (key === 'ArrowUp' && !altKey)) {\n event.preventDefault();\n requestOpen(\n true,\n altKey ? 'none' : key === 'ArrowDown' ? 'first' : 'last'\n );\n }\n return;\n }\n\n switch (key) {\n case 'ArrowDown':\n case 'ArrowUp': {\n if (altKey) {\n if (key === 'ArrowUp' && !inline) {\n event.preventDefault();\n close();\n }\n return;\n }\n event.preventDefault();\n // Disabled options too, so a screen reader user finds them.\n const options = shownOptions();\n if (options.length === 0) return;\n const at = options.findIndex(option => option.value === highlighted);\n const step = key === 'ArrowDown' ? 1 : -1;\n const index =\n at === -1\n ? step === 1\n ? 0\n : options.length - 1\n : (at + step + options.length) % options.length;\n scrollToHighlightRef.current = true;\n setHighlighted(options[index].value);\n return;\n }\n case 'Enter': {\n const option = shownOptions().find(\n shown => shown.value === highlighted\n );\n if (option) {\n event.preventDefault();\n if (!option.disabled) submit(option.value);\n return;\n }\n // Nothing to pick: the key goes on, so a form submits.\n onSettle?.();\n close();\n return;\n }\n case 'Escape':\n if (inline) return;\n // It stops here only when there was something to close, so a\n // Popover or Modal around it keeps its own Escape otherwise. Nothing\n // stops it going on: a handler further out reads `defaultPrevented`.\n if (popupRef.current) event.preventDefault();\n close();\n return;\n case 'Tab':\n close();\n return;\n case 'Home':\n case 'End':\n case 'ArrowLeft':\n case 'ArrowRight':\n // The field's own keys, which take the field back from the list.\n setHighlighted(null);\n return;\n default:\n }\n };\n\n const onChange = () => {\n setTyped(true);\n if (open) {\n landingRef.current = 'typed';\n setHighlighted(null);\n refresh();\n } else {\n requestOpen(true, 'typed');\n }\n };\n\n const onClick = () => {\n requestOpen(true, 'open');\n };\n\n const onBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const next = event.relatedTarget as Node | null;\n if (\n next &&\n (rootRef.current?.contains(next) || popupRef.current?.contains(next))\n ) {\n return;\n }\n close();\n };\n\n const hover = (value: string) => {\n scrollToHighlightRef.current = false;\n setHighlighted(value);\n };\n\n const pick = (value: string) => {\n submit(value);\n // A press in the list keeps focus in the field, but a browser that\n // ignores that, or a tap, can leave it elsewhere.\n const input = inputRef.current;\n if (input && input.isConnected && getActiveElementInTree(input) !== input) {\n input.focus();\n }\n };\n\n const keepFocus = (event: React.MouseEvent) => event.preventDefault();\n\n const markInside = (event: React.PointerEvent) => {\n insidePressRef.current = event.nativeEvent;\n };\n\n const registerPopup = useCallback((element: HTMLElement) => {\n popupRef.current = element;\n return () => {\n popupRef.current = null;\n };\n }, []);\n\n const registerItem = useCallback(\n (element: HTMLElement, state: ComboboxItemState) => {\n const options = optionsRef.current;\n options.set(element, state);\n refresh();\n return () => {\n options.delete(element);\n refresh();\n };\n },\n []\n );\n\n return {\n open,\n expanded,\n highlighted,\n typed,\n listboxId,\n itemId,\n rootRef,\n inputRef,\n listboxRef,\n registerPopup,\n registerItem,\n onKeyDown,\n onChange,\n onClick,\n onBlur,\n hover,\n pick,\n keepFocus,\n markInside,\n };\n}\n"],"names":["isComposing"],"mappings":"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"}
1
+ {"version":3,"file":"useComboboxEngine.js","sources":["../../../src/helpers/useComboboxEngine.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport {\n useCallback,\n useEffect,\n useId,\n useReducer,\n useRef,\n useState,\n} from 'react';\nimport type React from 'react';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\nimport { getActiveElementInTree, isEventInside } from './shadowDom';\n\n/** What an item tells the engine about itself. */\nexport interface ComboboxItemState {\n /** What a pick hands back. Unique within the combobox. */\n value: string;\n /** Reachable with the arrows, never picked. */\n disabled: boolean;\n /** The value, or one of them. */\n checked: boolean;\n}\n\n/** An option on the page, with its element. */\ninterface ShownOption extends ComboboxItemState {\n element: HTMLElement;\n}\n\n/**\n * Where the highlight lands once an opened list has rendered, or once typing\n * has changed what's in it:\n *\n * - `first` and `last` (Down and Up Arrow): the checked option, otherwise the\n * first or last enabled one;\n * - `open` (a click, or any other way of opening): the checked option,\n * otherwise the first enabled one with `keepFirst`, otherwise nothing;\n * - `typed`: the first enabled option with `keepFirst`, otherwise nothing;\n * - `none` (Alt+Down Arrow): nothing.\n *\n * Only an enabled option takes it, so a disabled checked one is passed over.\n */\ntype Landing = 'first' | 'last' | 'open' | 'typed' | 'none';\n\n/** Options for `useComboboxEngine`. */\nexport interface ComboboxEngineOptions {\n /** Controlled open state. When set, opening and closing only report through `onOpenChange`. */\n open?: boolean;\n /** Initial open state when uncontrolled. */\n defaultOpen: boolean;\n /** Called with the open state asked for, when it differs from the current one. */\n onOpenChange?: (open: boolean) => void;\n /** Called with an item's `value` when it's picked. */\n onSelect?: (value: string) => void;\n /**\n * Called when Enter goes on to the form with nothing to pick, before the\n * list closes, so an owner that shows something other than the typing can\n * put it back before the form reads the field. An `inline` list doesn't\n * close, so this is the one word it gets.\n */\n onSettle?: () => void;\n /** Highlights the first enabled item whenever the list opens or its items change. */\n keepFirst: boolean;\n /** Several values: a pick keeps the list open. */\n multiple: boolean;\n /** The list is always on screen, so nothing opens or closes it. */\n inline: boolean;\n /**\n * The list's panel is portaled, so it has no server markup and shows only\n * from the commit after hydration. Until it has rendered, an open list\n * isn't taken to be on the page, so no `aria-controls` names it early.\n */\n deferred: boolean;\n}\n\n/** What `useComboboxEngine` hands the parts of a combobox. */\nexport interface ComboboxEngine {\n /** Whether the list is open. Always, with `inline`. */\n open: boolean;\n /**\n * The field's `aria-expanded`: open, with a listbox that has an option in\n * it on the page. Until the open list has rendered, it's taken to have\n * one, so server markup for an open list in place says it's expanded. A\n * `deferred` list isn't in server markup, so it's taken to have none.\n */\n expanded: boolean;\n /** The highlighted option's value, if any. */\n highlighted: string | null;\n /** Whether the user has typed since the list opened. */\n typed: boolean;\n /** The listbox's id. */\n listboxId: string;\n /** An item's id, which follows from its value. */\n itemId: (value: string) => string;\n /** For the element around the field and, unless it's portaled, the list. */\n rootRef: React.MutableRefObject<HTMLDivElement | null>;\n /** For the field. */\n inputRef: React.MutableRefObject<HTMLInputElement | null>;\n /** For the listbox. */\n listboxRef: React.MutableRefObject<HTMLDivElement | null>;\n /**\n * Records the floating panel while it's on screen, which makes it count as\n * inside, and makes an Escape that closes it stop there. Returns the\n * function that forgets it.\n */\n registerPopup: (element: HTMLElement) => () => void;\n /** Records an item's element. Returns the function that forgets it. */\n registerItem: (element: HTMLElement, state: ComboboxItemState) => () => void;\n /** The field's keys, to run after the caller's own handler. */\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void;\n /** A change to the field's value: the user typed. */\n onChange: () => void;\n /** A click in the field. */\n onClick: () => void;\n /** Focus leaving the field. */\n onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;\n /** The pointer moved over an enabled option. */\n hover: (value: string) => void;\n /** A click on an enabled option. Focus ends up in the field. */\n pick: (value: string) => void;\n /** For anything in the list: a press there doesn't take focus from the field. */\n keepFocus: (event: React.MouseEvent) => void;\n /**\n * For the floating panel: marks a press as inside, which React reports from\n * content the panel portals elsewhere as well.\n */\n markInside: (event: React.PointerEvent) => void;\n}\n\n/** An IME is composing, so the key belongs to it (`keyCode` 229 is Safari's word for it). */\nconst isComposing = (event: React.KeyboardEvent): boolean =>\n event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;\n\nconst sameValues = (a: string[], b: string[]): boolean =>\n a.length === b.length && a.every((value, i) => value === b[i]);\n\n/**\n * The keys, the open state, the highlight and the ARIA of an editable\n * combobox whose list the app renders and filters (#1083). DOM focus stays\n * in the field the whole time: the highlight is `aria-activedescendant`, as\n * the ARIA Authoring Practices ask of a combobox, where `useMenuKeys` moves\n * real focus, as a menu should.\n *\n * The options are read from the listbox in document order whenever they're\n * needed, so options in groups and options that come and go are found, and\n * the highlight is remembered by value, so it survives a re-render. The text\n * in the field and any value are the app's: a pick only calls\n * `onSelect`.\n *\n * @function useComboboxEngine\n * @param options - The open state, the pick callback and the shape.\n * @returns The state and handlers the parts wire up.\n */\nexport function useComboboxEngine({\n open: controlledOpen,\n defaultOpen,\n onOpenChange,\n onSelect,\n onSettle,\n keepFirst,\n multiple,\n inline,\n deferred,\n}: ComboboxEngineOptions): ComboboxEngine {\n const baseId = useId();\n const listboxId = `${baseId}-listbox`;\n const itemPrefix = `${baseId}-item-`;\n // The value goes into the id encoded, so a value with a space in it still\n // makes one id, not two.\n const itemId = useCallback(\n (value: string) => itemPrefix + encodeURIComponent(value),\n [itemPrefix]\n );\n\n const rootRef = useRef<HTMLDivElement | null>(null);\n const inputRef = useRef<HTMLInputElement | null>(null);\n const listboxRef = useRef<HTMLDivElement | null>(null);\n const popupRef = useRef<HTMLElement | null>(null);\n const insidePressRef = useRef<Event | null>(null);\n const optionsRef = useRef(new Map<HTMLElement, ComboboxItemState>());\n const landingRef = useRef<Landing | null>(null);\n const wasOpenRef = useRef(false);\n const shownValuesRef = useRef<string[]>([]);\n // Whether the next highlight change is one to scroll to: a key's or a\n // landing's. The pointer's isn't, since the pointer is already on that\n // option, and scrolling would move the list out from under it.\n const scrollToHighlightRef = useRef(false);\n\n const isControlled = controlledOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = inline || (controlledOpen ?? internalOpen);\n\n const [highlighted, setHighlighted] = useState<string | null>(null);\n const [typed, setTyped] = useState(false);\n // Whether a listbox with an option in it is on the page, which only the\n // page can say, so it's read once the open list has rendered.\n const [listShown, setListShown] = useState<boolean | null>(null);\n // Options register from their own effects, which needn't render the\n // component that holds this hook, so they ask for a render to be read.\n const [, refresh] = useReducer((n: number) => n + 1, 0);\n\n // Closing forgets the highlight, the typing and what was on the page,\n // during render, React's pattern for adjusting state when other state\n // changes.\n if (!open) {\n if (highlighted !== null) setHighlighted(null);\n if (typed) setTyped(false);\n if (listShown !== null) setListShown(null);\n }\n const expanded = open && (listShown ?? !deferred);\n\n /** The options on the page, in document order, groups included. */\n const shownOptions = useCallback((): ShownOption[] => {\n const listbox = listboxRef.current;\n if (!listbox) return [];\n const shown: ShownOption[] = [];\n listbox\n .querySelectorAll<HTMLElement>('[role=\"option\"]')\n .forEach(element => {\n // Only the Combobox's own options: an element given the role by\n // hand isn't one.\n const state = optionsRef.current.get(element);\n if (state) shown.push({ element, ...state });\n });\n return shown;\n }, []);\n\n const requestOpen = useCallback(\n (next: boolean, landing: Landing = 'open') => {\n if (inline || next === open) return;\n landingRef.current = next ? landing : null;\n onOpenChange?.(next);\n if (!isControlled) setInternalOpen(next);\n },\n [inline, open, onOpenChange, isControlled]\n );\n const close = useCallback(() => requestOpen(false), [requestOpen]);\n\n // After every render, read the list: whether it has options, and where\n // the highlight goes now. A highlighted option that has left takes the\n // highlight with it, or hands it to the first enabled one under\n // `keepFirst`, which also moves it there whenever the options change.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- after every render on purpose\n useClientLayoutEffect(() => {\n const opened = open && !wasOpenRef.current;\n wasOpenRef.current = open;\n if (!open) {\n landingRef.current = null;\n shownValuesRef.current = [];\n return;\n }\n const options = shownOptions();\n const values = options.map(option => option.value);\n const changed = !sameValues(values, shownValuesRef.current);\n shownValuesRef.current = values;\n setListShown(values.length > 0);\n\n const landing = landingRef.current ?? (opened ? 'open' : null);\n landingRef.current = null;\n const enabled = options.filter(option => !option.disabled);\n const checked = enabled.find(option => option.checked)?.value;\n const first = enabled.length > 0 ? enabled[0].value : null;\n const last = enabled.length > 0 ? enabled[enabled.length - 1].value : null;\n let next = highlighted;\n if (landing === 'first') next = checked ?? first;\n else if (landing === 'last') next = checked ?? last;\n else if (landing === 'open') next = checked ?? (keepFirst ? first : null);\n else if (landing === 'typed') next = keepFirst ? first : null;\n else if (landing === 'none') next = null;\n else if (keepFirst && changed) next = first;\n // Without `keepFirst`, which moved it above, a highlighted option\n // that left takes the highlight with it.\n else if (next !== null && !values.includes(next)) next = null;\n // Only a change is set. StrictMode runs this twice on mount, and the\n // second run, with the landing used up, works out the rendered highlight\n // again, so it leaves the first run's landing in place.\n if (next !== highlighted) {\n scrollToHighlightRef.current = true;\n setHighlighted(next);\n }\n });\n\n // A browser scrolls to what has focus, not to an active descendant, so\n // the highlighted option is brought into view here, when a key or a\n // landing moved it there.\n useClientLayoutEffect(() => {\n if (highlighted === null || !scrollToHighlightRef.current) return;\n scrollToHighlightRef.current = false;\n const option = shownOptions().find(shown => shown.value === highlighted);\n option?.element.scrollIntoView?.({ block: 'nearest' });\n }, [highlighted, shownOptions]);\n\n // A press outside the field and the list closes it. The path is checked\n // rather than the target, so it works in a shadow root.\n useEffect(() => {\n const root = rootRef.current;\n if (!open || inline || !root) return undefined;\n const onPress = (event: PointerEvent) => {\n if (\n event === insidePressRef.current ||\n isEventInside(event, rootRef.current) ||\n isEventInside(event, popupRef.current)\n ) {\n return;\n }\n close();\n };\n // Its document, an iframe's when the tree renders into one.\n const doc = root.ownerDocument;\n doc.addEventListener('pointerdown', onPress);\n return () => doc.removeEventListener('pointerdown', onPress);\n }, [open, inline, close]);\n\n const submit = useCallback(\n (value: string) => {\n onSelect?.(value);\n if (!multiple) close();\n },\n [onSelect, multiple, close]\n );\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n // The caller's handler ran first and can keep a key, and an IME that's\n // composing owns every key.\n if (event.defaultPrevented || isComposing(event)) return;\n const { key, altKey } = event;\n\n if (!open) {\n if (key === 'ArrowDown' || (key === 'ArrowUp' && !altKey)) {\n event.preventDefault();\n requestOpen(\n true,\n altKey ? 'none' : key === 'ArrowDown' ? 'first' : 'last'\n );\n }\n return;\n }\n\n switch (key) {\n case 'ArrowDown':\n case 'ArrowUp': {\n if (altKey) {\n if (key === 'ArrowUp' && !inline) {\n event.preventDefault();\n close();\n }\n return;\n }\n event.preventDefault();\n // Disabled options too, so a screen reader user finds them.\n const options = shownOptions();\n if (options.length === 0) return;\n const at = options.findIndex(option => option.value === highlighted);\n const step = key === 'ArrowDown' ? 1 : -1;\n const index =\n at === -1\n ? step === 1\n ? 0\n : options.length - 1\n : (at + step + options.length) % options.length;\n scrollToHighlightRef.current = true;\n setHighlighted(options[index].value);\n return;\n }\n case 'Enter': {\n const option = shownOptions().find(\n shown => shown.value === highlighted\n );\n if (option) {\n event.preventDefault();\n if (!option.disabled) submit(option.value);\n return;\n }\n // Nothing to pick: the key goes on, so a form submits.\n onSettle?.();\n close();\n return;\n }\n case 'Escape':\n if (inline) return;\n // It stops here only when there was something to close, so a\n // Popover or Modal around it keeps its own Escape otherwise. Nothing\n // stops it going on: a handler further out reads `defaultPrevented`.\n if (popupRef.current) event.preventDefault();\n close();\n return;\n case 'Tab':\n close();\n return;\n case 'Home':\n case 'End':\n case 'ArrowLeft':\n case 'ArrowRight':\n // The field's own keys, which take the field back from the list.\n setHighlighted(null);\n return;\n default:\n }\n };\n\n const onChange = () => {\n setTyped(true);\n if (open) {\n landingRef.current = 'typed';\n setHighlighted(null);\n refresh();\n } else {\n requestOpen(true, 'typed');\n }\n };\n\n const onClick = () => {\n requestOpen(true, 'open');\n };\n\n const onBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const next = event.relatedTarget as Node | null;\n if (\n next &&\n (rootRef.current?.contains(next) || popupRef.current?.contains(next))\n ) {\n return;\n }\n close();\n };\n\n const hover = (value: string) => {\n scrollToHighlightRef.current = false;\n setHighlighted(value);\n };\n\n const pick = (value: string) => {\n submit(value);\n // A press in the list keeps focus in the field, but a browser that\n // ignores that, or a tap, can leave it elsewhere.\n const input = inputRef.current;\n if (input && input.isConnected && getActiveElementInTree(input) !== input) {\n input.focus();\n }\n };\n\n const keepFocus = (event: React.MouseEvent) => event.preventDefault();\n\n const markInside = (event: React.PointerEvent) => {\n insidePressRef.current = event.nativeEvent;\n };\n\n const registerPopup = useCallback((element: HTMLElement) => {\n popupRef.current = element;\n return () => {\n popupRef.current = null;\n };\n }, []);\n\n const registerItem = useCallback(\n (element: HTMLElement, state: ComboboxItemState) => {\n const options = optionsRef.current;\n options.set(element, state);\n refresh();\n return () => {\n options.delete(element);\n refresh();\n };\n },\n []\n );\n\n return {\n open,\n expanded,\n highlighted,\n typed,\n listboxId,\n itemId,\n rootRef,\n inputRef,\n listboxRef,\n registerPopup,\n registerItem,\n onKeyDown,\n onChange,\n onClick,\n onBlur,\n hover,\n pick,\n keepFocus,\n markInside,\n };\n}\n"],"names":["isComposing","sameValues"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ 'use client';
3
+ import { useState } from 'react';
4
+
5
+ // INTERNAL: deliberately not exported from src/index.ts.
6
+ /**
7
+ * A value the caller holds, or one this hook holds when the caller doesn't:
8
+ * the `x` / `defaultX` pair of a component's props. The caller holds it
9
+ * whenever `controlled` isn't `undefined`, so `null` is a held value. The
10
+ * setter changes only the value held here, so a held one changes when the
11
+ * caller passes the new value, and it reports nothing: the component calls
12
+ * its own `onXChange`.
13
+ *
14
+ * @function useControlled
15
+ * @param controlled - The caller's value, or `undefined` to hold one here.
16
+ * @param initial - The value held here to begin with.
17
+ * @returns The current value, and the setter for the one held here.
18
+ */
19
+ function useControlled(controlled, initial) {
20
+ const [own, setOwn] = useState(initial);
21
+ const isControlled = controlled !== undefined;
22
+ const set = (next) => {
23
+ if (!isControlled)
24
+ setOwn(next);
25
+ };
26
+ return [isControlled ? controlled : own, set];
27
+ }
28
+
29
+ export { useControlled };
30
+ //# sourceMappingURL=useControlled.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useControlled.js","sources":["../../../src/helpers/useControlled.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useState } from 'react';\n\n/**\n * A value the caller holds, or one this hook holds when the caller doesn't:\n * the `x` / `defaultX` pair of a component's props. The caller holds it\n * whenever `controlled` isn't `undefined`, so `null` is a held value. The\n * setter changes only the value held here, so a held one changes when the\n * caller passes the new value, and it reports nothing: the component calls\n * its own `onXChange`.\n *\n * @function useControlled\n * @param controlled - The caller's value, or `undefined` to hold one here.\n * @param initial - The value held here to begin with.\n * @returns The current value, and the setter for the one held here.\n */\nexport function useControlled<T>(controlled: T | undefined, initial: T) {\n const [own, setOwn] = useState(initial);\n const isControlled = controlled !== undefined;\n const set = (next: T) => {\n if (!isControlled) setOwn(next);\n };\n return [isControlled ? controlled : own, set] as const;\n}\n"],"names":[],"mappings":";;;;AAAA;AAGA;;;;;;;;;;;;AAYG;AACG,SAAU,aAAa,CAAI,UAAyB,EAAE,OAAU,EAAA;IACpE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;AACvC,IAAA,MAAM,YAAY,GAAG,UAAU,KAAK,SAAS;AAC7C,IAAA,MAAM,GAAG,GAAG,CAAC,IAAO,KAAI;AACtB,QAAA,IAAI,CAAC,YAAY;YAAE,MAAM,CAAC,IAAI,CAAC;AACjC,IAAA,CAAC;AACD,IAAA,OAAO,CAAC,YAAY,GAAG,UAAU,GAAG,GAAG,EAAE,GAAG,CAAU;AACxD;;"}
@@ -76,16 +76,20 @@ const above = (el) => el.parentElement ?? el.getRootNode().host;
76
76
  * @param active - Whether what's beside it is inert.
77
77
  * @param wholePage - Whether that goes for the whole page rather than only
78
78
  * the element's siblings.
79
+ * @param except - Ref to an element beside it to leave reachable too, such
80
+ * as the overlay's own backdrop, which has to take the presses that close
81
+ * it rather than let them through to what's under it.
79
82
  */
80
- function useInertSiblings(ref, active, wholePage) {
83
+ function useInertSiblings(ref, active, wholePage, except) {
81
84
  useEffect(() => {
82
85
  const el = ref.current;
83
86
  if (!active || !el)
84
87
  return undefined;
85
88
  const doc = el.ownerDocument;
89
+ const keep = except?.current;
86
90
  const held = [];
87
91
  for (let node = el; node && node !== doc.body; node = wholePage ? above(node) : undefined) {
88
- held.push(...siblingsOf(node).filter(hold));
92
+ held.push(...siblingsOf(node).filter(sibling => sibling !== keep && hold(sibling)));
89
93
  }
90
94
  // Focus this takes away, to hand back later. It's looked up as `el`'s
91
95
  // tree sees it, where focus inside a nested shadow root reads as its
@@ -100,7 +104,7 @@ function useInertSiblings(ref, active, wholePage) {
100
104
  if (doc.activeElement === doc.body)
101
105
  taken?.focus();
102
106
  };
103
- }, [ref, active, wholePage]);
107
+ }, [ref, active, wholePage, except]);
104
108
  }
105
109
 
106
110
  export { useInertSiblings };
@@ -1 +1 @@
1
- {"version":3,"file":"useInertSiblings.js","sources":["../../../src/helpers/useInertSiblings.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useEffect } from 'react';\nimport type { RefObject } from 'react';\nimport { getActiveElementInTree, getDeepestActiveElement } from './shadowDom';\n\n/**\n * How many callers hold each element inert. Shared across callers, the way\n * the scroll lock's count is, so two that cover the same element don't take\n * `inert` off it while the other still needs it. An element that was inert\n * before any caller came isn't counted, so it's left as it was.\n */\nconst holds = new Map<Element, number>();\n\n/**\n * Makes `el` inert for one more caller. Returns `false`, touching nothing,\n * when it was inert already and no caller made it so.\n */\nfunction hold(el: Element): boolean {\n const count = holds.get(el) ?? 0;\n if (count === 0 && el.hasAttribute('inert')) return false;\n holds.set(el, count + 1);\n el.setAttribute('inert', '');\n return true;\n}\n\n/** Lets go of one caller's hold, and takes `inert` off with the last one. */\nfunction release(el: Element): void {\n const count = (holds.get(el) as number) - 1;\n if (count > 0) {\n holds.set(el, count);\n return;\n }\n holds.delete(el);\n el.removeAttribute('inert');\n}\n\n/** The element's siblings, in a shadow root as anywhere else. */\nfunction siblingsOf(el: Element): Element[] {\n const siblings: Element[] = [];\n for (let s = el.previousElementSibling; s; s = s.previousElementSibling) {\n siblings.push(s);\n }\n for (let s = el.nextElementSibling; s; s = s.nextElementSibling) {\n siblings.push(s);\n }\n return siblings;\n}\n\n/**\n * The element above `el` in the page: its parent, or the host of the shadow\n * root it sits at the top of. Nothing above the top of a detached tree.\n */\nconst above = (el: Element): Element | undefined =>\n el.parentElement ?? (el.getRootNode() as Partial<ShadowRoot>).host;\n\n/**\n * Makes the elements beside `ref`'s element inert while `active` is true,\n * and takes it off again when `active` turns false or the caller unmounts.\n * With `wholePage`, it does the same beside each element above it up to\n * `<body>`, so everything on the page but `ref`'s element is inert, the way a\n * modal layer leaves it. The element itself and its own content are never\n * touched.\n *\n * It sets the `inert` attribute through the DOM in an effect, so it works the\n * same on React 18 and 19 and a server render has nothing to show for it. An\n * element that was inert already is left alone, and keeps its `inert` once\n * this lets go. It makes inert what's there when it turns on: an element that\n * mounts beside it afterwards isn't. It follows the DOM, so content a web\n * component renders around a slot this sits in, and text with no element of\n * its own, stays as it is.\n *\n * Focus on an element that turns inert is lost: the browser moves it to\n * `<body>`. So when focus was inside what this made inert, and is still on\n * `<body>` when this lets go, it goes back to the element that had it. Focus\n * this never took isn't handed anywhere, and focus the user or the app has\n * put somewhere else meanwhile stays there.\n *\n * @function useInertSiblings\n * @param ref - Ref to the element that stays reachable.\n * @param active - Whether what's beside it is inert.\n * @param wholePage - Whether that goes for the whole page rather than only\n * the element's siblings.\n */\nexport function useInertSiblings(\n ref: RefObject<Element | null>,\n active: boolean,\n wholePage: boolean\n): void {\n useEffect(() => {\n const el = ref.current;\n if (!active || !el) return undefined;\n const doc = el.ownerDocument;\n\n const held: Element[] = [];\n for (\n let node: Element | undefined = el;\n node && node !== doc.body;\n node = wholePage ? above(node) : undefined\n ) {\n held.push(...siblingsOf(node).filter(hold));\n }\n\n // Focus this takes away, to hand back later. It's looked up as `el`'s\n // tree sees it, where focus inside a nested shadow root reads as its\n // host, so it compares with what's held. What gets focused again is the\n // element that really had focus, inside that shadow root.\n const focused = getActiveElementInTree(el);\n const taken = held.some(other => other.contains(focused))\n ? (getDeepestActiveElement(doc) as HTMLElement | null)\n : null;\n\n return () => {\n held.forEach(release);\n if (doc.activeElement === doc.body) taken?.focus();\n };\n }, [ref, active, wholePage]);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useInertSiblings.js","sources":["../../../src/helpers/useInertSiblings.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useEffect } from 'react';\nimport type { RefObject } from 'react';\nimport { getActiveElementInTree, getDeepestActiveElement } from './shadowDom';\n\n/**\n * How many callers hold each element inert. Shared across callers, the way\n * the scroll lock's count is, so two that cover the same element don't take\n * `inert` off it while the other still needs it. An element that was inert\n * before any caller came isn't counted, so it's left as it was.\n */\nconst holds = new Map<Element, number>();\n\n/**\n * Makes `el` inert for one more caller. Returns `false`, touching nothing,\n * when it was inert already and no caller made it so.\n */\nfunction hold(el: Element): boolean {\n const count = holds.get(el) ?? 0;\n if (count === 0 && el.hasAttribute('inert')) return false;\n holds.set(el, count + 1);\n el.setAttribute('inert', '');\n return true;\n}\n\n/** Lets go of one caller's hold, and takes `inert` off with the last one. */\nfunction release(el: Element): void {\n const count = (holds.get(el) as number) - 1;\n if (count > 0) {\n holds.set(el, count);\n return;\n }\n holds.delete(el);\n el.removeAttribute('inert');\n}\n\n/** The element's siblings, in a shadow root as anywhere else. */\nfunction siblingsOf(el: Element): Element[] {\n const siblings: Element[] = [];\n for (let s = el.previousElementSibling; s; s = s.previousElementSibling) {\n siblings.push(s);\n }\n for (let s = el.nextElementSibling; s; s = s.nextElementSibling) {\n siblings.push(s);\n }\n return siblings;\n}\n\n/**\n * The element above `el` in the page: its parent, or the host of the shadow\n * root it sits at the top of. Nothing above the top of a detached tree.\n */\nconst above = (el: Element): Element | undefined =>\n el.parentElement ?? (el.getRootNode() as Partial<ShadowRoot>).host;\n\n/**\n * Makes the elements beside `ref`'s element inert while `active` is true,\n * and takes it off again when `active` turns false or the caller unmounts.\n * With `wholePage`, it does the same beside each element above it up to\n * `<body>`, so everything on the page but `ref`'s element is inert, the way a\n * modal layer leaves it. The element itself and its own content are never\n * touched.\n *\n * It sets the `inert` attribute through the DOM in an effect, so it works the\n * same on React 18 and 19 and a server render has nothing to show for it. An\n * element that was inert already is left alone, and keeps its `inert` once\n * this lets go. It makes inert what's there when it turns on: an element that\n * mounts beside it afterwards isn't. It follows the DOM, so content a web\n * component renders around a slot this sits in, and text with no element of\n * its own, stays as it is.\n *\n * Focus on an element that turns inert is lost: the browser moves it to\n * `<body>`. So when focus was inside what this made inert, and is still on\n * `<body>` when this lets go, it goes back to the element that had it. Focus\n * this never took isn't handed anywhere, and focus the user or the app has\n * put somewhere else meanwhile stays there.\n *\n * @function useInertSiblings\n * @param ref - Ref to the element that stays reachable.\n * @param active - Whether what's beside it is inert.\n * @param wholePage - Whether that goes for the whole page rather than only\n * the element's siblings.\n * @param except - Ref to an element beside it to leave reachable too, such\n * as the overlay's own backdrop, which has to take the presses that close\n * it rather than let them through to what's under it.\n */\nexport function useInertSiblings(\n ref: RefObject<Element | null>,\n active: boolean,\n wholePage: boolean,\n except?: RefObject<Element | null>\n): void {\n useEffect(() => {\n const el = ref.current;\n if (!active || !el) return undefined;\n const doc = el.ownerDocument;\n const keep = except?.current;\n\n const held: Element[] = [];\n for (\n let node: Element | undefined = el;\n node && node !== doc.body;\n node = wholePage ? above(node) : undefined\n ) {\n held.push(\n ...siblingsOf(node).filter(sibling => sibling !== keep && hold(sibling))\n );\n }\n\n // Focus this takes away, to hand back later. It's looked up as `el`'s\n // tree sees it, where focus inside a nested shadow root reads as its\n // host, so it compares with what's held. What gets focused again is the\n // element that really had focus, inside that shadow root.\n const focused = getActiveElementInTree(el);\n const taken = held.some(other => other.contains(focused))\n ? (getDeepestActiveElement(doc) as HTMLElement | null)\n : null;\n\n return () => {\n held.forEach(release);\n if (doc.activeElement === doc.body) taken?.focus();\n };\n }, [ref, active, wholePage, except]);\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ 'use client';
3
+ import { useRef, useEffect, useCallback } from 'react';
4
+ import { useLatestRef } from './useLatestRef.js';
5
+ import { isEventInside } from './shadowDom.js';
6
+
7
+ // INTERNAL: deliberately not exported from src/index.ts.
8
+ /**
9
+ * Calls `onOutsidePress` for a `pointerdown` outside `ref`'s element while
10
+ * `active`, as a Popover closes on one.
11
+ *
12
+ * A press counts as inside in two ways: on the element's own path in the DOM,
13
+ * and anywhere in a React tree the returned handler is put on, as
14
+ * `onPointerDownCapture`. React's tree includes content portaled out of it,
15
+ * which the DOM path doesn't, and React has seen a press there by the time
16
+ * the document hears it.
17
+ *
18
+ * The listener is on the element's own document, an iframe's when the tree
19
+ * renders into one, and reads the latest `onOutsidePress` as a press
20
+ * arrives, so it stays put while the callback changes identity.
21
+ *
22
+ * @function useOutsidePress
23
+ * @param ref - Ref to the element a press inside of doesn't count.
24
+ * @param active - Whether to listen.
25
+ * @param onOutsidePress - Called with each press outside.
26
+ * @returns The `onPointerDownCapture` handler that marks a press in its tree
27
+ * as inside.
28
+ */
29
+ function useOutsidePress(ref, active, onOutsidePress) {
30
+ // The last pointerdown React saw inside.
31
+ const insidePressRef = useRef(null);
32
+ const onOutsidePressRef = useLatestRef(onOutsidePress);
33
+ useEffect(() => {
34
+ const element = ref.current;
35
+ if (!active || !element)
36
+ return undefined;
37
+ const handlePointerDown = (e) => {
38
+ if (e === insidePressRef.current || isEventInside(e, ref.current)) {
39
+ return;
40
+ }
41
+ onOutsidePressRef.current(e);
42
+ };
43
+ const doc = element.ownerDocument;
44
+ doc.addEventListener('pointerdown', handlePointerDown);
45
+ return () => doc.removeEventListener('pointerdown', handlePointerDown);
46
+ }, [ref, active, onOutsidePressRef]);
47
+ return useCallback((event) => {
48
+ insidePressRef.current = event.nativeEvent;
49
+ }, []);
50
+ }
51
+
52
+ export { useOutsidePress };
53
+ //# sourceMappingURL=useOutsidePress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOutsidePress.js","sources":["../../../src/helpers/useOutsidePress.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useCallback, useEffect, useRef } from 'react';\nimport type React from 'react';\nimport type { RefObject } from 'react';\nimport { useLatestRef } from './useLatestRef';\nimport { isEventInside } from './shadowDom';\n\n/**\n * Calls `onOutsidePress` for a `pointerdown` outside `ref`'s element while\n * `active`, as a Popover closes on one.\n *\n * A press counts as inside in two ways: on the element's own path in the DOM,\n * and anywhere in a React tree the returned handler is put on, as\n * `onPointerDownCapture`. React's tree includes content portaled out of it,\n * which the DOM path doesn't, and React has seen a press there by the time\n * the document hears it.\n *\n * The listener is on the element's own document, an iframe's when the tree\n * renders into one, and reads the latest `onOutsidePress` as a press\n * arrives, so it stays put while the callback changes identity.\n *\n * @function useOutsidePress\n * @param ref - Ref to the element a press inside of doesn't count.\n * @param active - Whether to listen.\n * @param onOutsidePress - Called with each press outside.\n * @returns The `onPointerDownCapture` handler that marks a press in its tree\n * as inside.\n */\nexport function useOutsidePress(\n ref: RefObject<Element | null>,\n active: boolean,\n onOutsidePress: (event: PointerEvent) => void\n): (event: React.PointerEvent) => void {\n // The last pointerdown React saw inside.\n const insidePressRef = useRef<Event | null>(null);\n const onOutsidePressRef = useLatestRef(onOutsidePress);\n\n useEffect(() => {\n const element = ref.current;\n if (!active || !element) return undefined;\n const handlePointerDown = (e: PointerEvent) => {\n if (e === insidePressRef.current || isEventInside(e, ref.current)) {\n return;\n }\n onOutsidePressRef.current(e);\n };\n const doc = element.ownerDocument;\n doc.addEventListener('pointerdown', handlePointerDown);\n return () => doc.removeEventListener('pointerdown', handlePointerDown);\n }, [ref, active, onOutsidePressRef]);\n\n return useCallback((event: React.PointerEvent) => {\n insidePressRef.current = event.nativeEvent;\n }, []);\n}\n"],"names":[],"mappings":";;;;;;AAAA;AAOA;;;;;;;;;;;;;;;;;;;;AAoBG;SACa,eAAe,CAC7B,GAA8B,EAC9B,MAAe,EACf,cAA6C,EAAA;;AAG7C,IAAA,MAAM,cAAc,GAAG,MAAM,CAAe,IAAI,CAAC;AACjD,IAAA,MAAM,iBAAiB,GAAG,YAAY,CAAC,cAAc,CAAC;IAEtD,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO;AAC3B,QAAA,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,SAAS;AACzC,QAAA,MAAM,iBAAiB,GAAG,CAAC,CAAe,KAAI;AAC5C,YAAA,IAAI,CAAC,KAAK,cAAc,CAAC,OAAO,IAAI,aAAa,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE;gBACjE;YACF;AACA,YAAA,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC9B,QAAA,CAAC;AACD,QAAA,MAAM,GAAG,GAAG,OAAO,CAAC,aAAa;AACjC,QAAA,GAAG,CAAC,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,CAAC;QACtD,OAAO,MAAM,GAAG,CAAC,mBAAmB,CAAC,aAAa,EAAE,iBAAiB,CAAC;IACxE,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAEpC,IAAA,OAAO,WAAW,CAAC,CAAC,KAAyB,KAAI;AAC/C,QAAA,cAAc,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW;IAC5C,CAAC,EAAE,EAAE,CAAC;AACR;;"}
@@ -0,0 +1,74 @@
1
+ /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
+ 'use client';
3
+ import { useRef, useEffect } from 'react';
4
+ import { useClientLayoutEffect } from './useClientLayoutEffect.js';
5
+ import { useLatestRef } from './useLatestRef.js';
6
+ import { isModalOverlayOpen } from './overlayStack.js';
7
+ import { focusTabStop } from './useFocusTrap.js';
8
+ import { getActiveElementInTree, getDeepestActiveElement } from './shadowDom.js';
9
+
10
+ // INTERNAL: deliberately not exported from src/index.ts.
11
+ /**
12
+ * Moves focus into an overlay as it opens, to the first stop Tab wraps to
13
+ * inside `ref`'s element or the element itself, and hands it back to
14
+ * whatever had it as it closes, as `Modal` does. Focus goes back only while
15
+ * the overlay still holds it, or it's been dropped to `<body>`: closing one
16
+ * under another overlay mustn't pull focus out of the one on top, and focus
17
+ * moved out to the page stays there. The opener is found wherever it sits,
18
+ * inside a shadow root too, in the overlay's own document, an iframe's when
19
+ * it renders into one. It's read in a layout effect as the overlay opens, so
20
+ * before the commit's passive effects, such as one that makes the page
21
+ * inert. Content inside the overlay with a layout effect of its own, or an
22
+ * `autoFocus`, can still move focus first, and then there's no opener to go
23
+ * back to: focus is never handed back into the overlay that's closing.
24
+ *
25
+ * @function useOverlayFocus
26
+ * @param ref - Ref to the overlay's element.
27
+ * @param open - Whether the overlay is open.
28
+ * @param options - Whether it's modal, what remounts it, and whether a
29
+ * close hands focus back.
30
+ */
31
+ function useOverlayFocus(ref, open, { modal, remountKey, restore }) {
32
+ const openerRef = useRef(null);
33
+ const restoreRef = useLatestRef(restore);
34
+ // The element that had focus, wherever it sits: a portaled overlay renders
35
+ // under `document.body` while its opener can be inside a shadow root.
36
+ useClientLayoutEffect(() => {
37
+ if (!open)
38
+ return;
39
+ const element = ref.current;
40
+ /* istanbul ignore if: as below, the ref is set while the overlay is open */
41
+ if (!element)
42
+ return;
43
+ // Focus already inside, where an `autoFocus` in the overlay puts it, has
44
+ // no opener to go back to.
45
+ openerRef.current = element.contains(getActiveElementInTree(element))
46
+ ? null
47
+ : getDeepestActiveElement(element.ownerDocument);
48
+ }, [open, ref]);
49
+ useEffect(() => {
50
+ if (!open)
51
+ return undefined;
52
+ const element = ref.current;
53
+ /* istanbul ignore if: the overlay renders whenever it's open, so its ref is set before this runs */
54
+ if (!element)
55
+ return undefined;
56
+ if (modal || !isModalOverlayOpen()) {
57
+ focusTabStop(element);
58
+ }
59
+ return () => {
60
+ // A removed subtree leaves focus on <body>, which still counts as held.
61
+ const active = getActiveElementInTree(element);
62
+ const held = active === element.ownerDocument.body || element.contains(active);
63
+ // Asked now rather than as it opened: it's about this close.
64
+ // eslint-disable-next-line react-hooks/exhaustive-deps
65
+ if (held && (restoreRef.current?.() ?? true)) {
66
+ openerRef.current?.focus();
67
+ }
68
+ };
69
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- `modal` is read as the overlay opens, on purpose
70
+ }, [open, remountKey, ref, restoreRef]);
71
+ }
72
+
73
+ export { useOverlayFocus };
74
+ //# sourceMappingURL=useOverlayFocus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOverlayFocus.js","sources":["../../../src/helpers/useOverlayFocus.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useEffect, useRef } from 'react';\nimport type { RefObject } from 'react';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\nimport { useLatestRef } from './useLatestRef';\nimport { isModalOverlayOpen } from './overlayStack';\nimport { focusTabStop } from './useFocusTrap';\nimport { getActiveElementInTree, getDeepestActiveElement } from './shadowDom';\n\n/** Options for `useOverlayFocus`. */\nexport interface OverlayFocusOptions {\n /**\n * Whether the overlay is modal. One that isn't, opened while a modal\n * overlay is open under it, leaves focus where it is: that overlay keeps\n * Tab, so a Tab from this one's controls would only go back to it. It's\n * read as the overlay opens, so a change while it's open moves nothing.\n */\n modal: boolean;\n /**\n * A value that remounts the overlay's nodes while it's open when it\n * changes, such as a new portal target. Focus is handed back from the old\n * nodes and moved into the new ones.\n */\n remountKey?: unknown;\n /**\n * Asked as the overlay closes, while it still holds focus: `false` leaves\n * focus where it is, for a close that shouldn't send it back, such as one\n * whose overlay stays on the page as something else.\n */\n restore?: () => boolean;\n}\n\n/**\n * Moves focus into an overlay as it opens, to the first stop Tab wraps to\n * inside `ref`'s element or the element itself, and hands it back to\n * whatever had it as it closes, as `Modal` does. Focus goes back only while\n * the overlay still holds it, or it's been dropped to `<body>`: closing one\n * under another overlay mustn't pull focus out of the one on top, and focus\n * moved out to the page stays there. The opener is found wherever it sits,\n * inside a shadow root too, in the overlay's own document, an iframe's when\n * it renders into one. It's read in a layout effect as the overlay opens, so\n * before the commit's passive effects, such as one that makes the page\n * inert. Content inside the overlay with a layout effect of its own, or an\n * `autoFocus`, can still move focus first, and then there's no opener to go\n * back to: focus is never handed back into the overlay that's closing.\n *\n * @function useOverlayFocus\n * @param ref - Ref to the overlay's element.\n * @param open - Whether the overlay is open.\n * @param options - Whether it's modal, what remounts it, and whether a\n * close hands focus back.\n */\nexport function useOverlayFocus(\n ref: RefObject<HTMLElement | null>,\n open: boolean,\n { modal, remountKey, restore }: OverlayFocusOptions\n): void {\n const openerRef = useRef<HTMLElement | null>(null);\n const restoreRef = useLatestRef(restore);\n\n // The element that had focus, wherever it sits: a portaled overlay renders\n // under `document.body` while its opener can be inside a shadow root.\n useClientLayoutEffect(() => {\n if (!open) return;\n const element = ref.current;\n /* istanbul ignore if: as below, the ref is set while the overlay is open */\n if (!element) return;\n // Focus already inside, where an `autoFocus` in the overlay puts it, has\n // no opener to go back to.\n openerRef.current = element.contains(getActiveElementInTree(element))\n ? null\n : (getDeepestActiveElement(element.ownerDocument) as HTMLElement | null);\n }, [open, ref]);\n\n useEffect(() => {\n if (!open) return undefined;\n const element = ref.current;\n /* istanbul ignore if: the overlay renders whenever it's open, so its ref is set before this runs */\n if (!element) return undefined;\n\n if (modal || !isModalOverlayOpen()) {\n focusTabStop(element);\n }\n\n return () => {\n // A removed subtree leaves focus on <body>, which still counts as held.\n const active = getActiveElementInTree(element);\n const held =\n active === element.ownerDocument.body || element.contains(active);\n // Asked now rather than as it opened: it's about this close.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n if (held && (restoreRef.current?.() ?? true)) {\n openerRef.current?.focus();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `modal` is read as the overlay opens, on purpose\n }, [open, remountKey, ref, restoreRef]);\n}\n"],"names":[],"mappings":";;;;;;;;;AAAA;AAgCA;;;;;;;;;;;;;;;;;;;AAmBG;AACG,SAAU,eAAe,CAC7B,GAAkC,EAClC,IAAa,EACb,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAuB,EAAA;AAEnD,IAAA,MAAM,SAAS,GAAG,MAAM,CAAqB,IAAI,CAAC;AAClD,IAAA,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC;;;IAIxC,qBAAqB,CAAC,MAAK;AACzB,QAAA,IAAI,CAAC,IAAI;YAAE;AACX,QAAA,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO;;AAE3B,QAAA,IAAI,CAAC,OAAO;YAAE;;;QAGd,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC,OAAO,CAAC;AAClE,cAAE;AACF,cAAG,uBAAuB,CAAC,OAAO,CAAC,aAAa,CAAwB;AAC5E,IAAA,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAEf,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,SAAS;AAC3B,QAAA,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO;;AAE3B,QAAA,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,SAAS;AAE9B,QAAA,IAAI,KAAK,IAAI,CAAC,kBAAkB,EAAE,EAAE;YAClC,YAAY,CAAC,OAAO,CAAC;QACvB;AAEA,QAAA,OAAO,MAAK;;AAEV,YAAA,MAAM,MAAM,GAAG,sBAAsB,CAAC,OAAO,CAAC;AAC9C,YAAA,MAAM,IAAI,GACR,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;;;AAGnE,YAAA,IAAI,IAAI,KAAK,UAAU,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,EAAE;AAC5C,gBAAA,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE;YAC5B;AACF,QAAA,CAAC;;IAEH,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;AACzC;;"}
@@ -1,7 +1,8 @@
1
1
  /* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
2
2
  'use client';
3
- import { useRef, useReducer, useState, useEffect, useCallback } from 'react';
3
+ import { createContext, useRef, useReducer, useState, useEffect, useCallback } from 'react';
4
4
  import { useClientLayoutEffect } from './useClientLayoutEffect.js';
5
+ import { useControlled } from './useControlled.js';
5
6
  import { getActiveElementInTree } from './shadowDom.js';
6
7
  import { useTypeahead, typeaheadMatch, typeaheadText } from './typeahead.js';
7
8
 
@@ -78,6 +79,14 @@ function pathOf(element, { values }) {
78
79
  }
79
80
  return path;
80
81
  }
82
+ /**
83
+ * The values of the items a tree hides while they stay in it, as TreeSelect's
84
+ * search hides the items that don't match. A hidden item renders as one under
85
+ * a closed parent does: nothing on the page, and in a `checkbox` tree it and
86
+ * its children still register, so a check still reaches them. Internal, for
87
+ * the components that put a TreeView inside them.
88
+ */
89
+ const TreeViewHiddenContext = createContext(new Set());
81
90
  /**
82
91
  * Every known item's check state, by the rules in #1096:
83
92
  *
@@ -153,16 +162,6 @@ function toggledList(records, list, value) {
153
162
  ...list.filter(item => !records.has(item)),
154
163
  ];
155
164
  }
156
- /** A value the caller holds, or one this hook holds when the caller doesn't. */
157
- function useControlled(controlled, initial) {
158
- const [own, setOwn] = useState(initial);
159
- const isControlled = controlled !== undefined;
160
- const set = (next) => {
161
- if (!isControlled)
162
- setOwn(next);
163
- };
164
- return [isControlled ? controlled : own, set];
165
- }
166
165
  /**
167
166
  * The list with `value` added at the end, or, when it's `present`, with
168
167
  * every copy of it taken out. Everything else stays as it was, in its order.
@@ -430,12 +429,15 @@ function useTreeView(options) {
430
429
  press(item, actions.space);
431
430
  break;
432
431
  case '*': {
432
+ // The siblings on the page: not the ones a component around the
433
+ // tree hides, which stay registered.
433
434
  const closed = [];
434
435
  registry.records.forEach((record, value) => {
435
436
  if (record.parent === item.parent &&
436
437
  record.isParent &&
437
438
  !record.disabled &&
438
- !expanded.includes(value)) {
439
+ !expanded.includes(value) &&
440
+ registry.elements.has(value)) {
439
441
  closed.push(value);
440
442
  }
441
443
  });
@@ -476,5 +478,5 @@ function useTreeView(options) {
476
478
  };
477
479
  }
478
480
 
479
- export { useTreeView };
481
+ export { TreeViewHiddenContext, readChecks, toggledList, useTreeView };
480
482
  //# sourceMappingURL=useTreeView.js.map