@mortenbrudvik/waveui 0.15.0 → 0.16.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 (155) hide show
  1. package/README.md +274 -24
  2. package/dist/components/data-display/List.cjs +1 -1
  3. package/dist/components/data-display/List.cjs.map +1 -1
  4. package/dist/components/data-display/List.mjs +405 -219
  5. package/dist/components/data-display/List.mjs.map +1 -1
  6. package/dist/components/data-display/List.registry.cjs +1 -1
  7. package/dist/components/data-display/List.registry.cjs.map +1 -1
  8. package/dist/components/data-display/List.registry.mjs +12 -6
  9. package/dist/components/data-display/List.registry.mjs.map +1 -1
  10. package/dist/components/input/Combobox.cjs +1 -1
  11. package/dist/components/input/Combobox.cjs.map +1 -1
  12. package/dist/components/input/Combobox.mjs +163 -148
  13. package/dist/components/input/Combobox.mjs.map +1 -1
  14. package/dist/components/input/Dropdown.cjs +1 -1
  15. package/dist/components/input/Dropdown.cjs.map +1 -1
  16. package/dist/components/input/Dropdown.mjs +132 -117
  17. package/dist/components/input/Dropdown.mjs.map +1 -1
  18. package/dist/components/input/Listbox.cjs +1 -1
  19. package/dist/components/input/Listbox.cjs.map +1 -1
  20. package/dist/components/input/Listbox.mjs +90 -78
  21. package/dist/components/input/Listbox.mjs.map +1 -1
  22. package/dist/components/input/Option.cjs +1 -1
  23. package/dist/components/input/Option.cjs.map +1 -1
  24. package/dist/components/input/Option.data.cjs +2 -0
  25. package/dist/components/input/Option.data.cjs.map +1 -0
  26. package/dist/components/input/Option.data.mjs +114 -0
  27. package/dist/components/input/Option.data.mjs.map +1 -0
  28. package/dist/components/input/Option.mjs +21 -17
  29. package/dist/components/input/Option.mjs.map +1 -1
  30. package/dist/components/layout/FlatTree.cjs +2 -0
  31. package/dist/components/layout/FlatTree.cjs.map +1 -0
  32. package/dist/components/layout/FlatTree.context.cjs +2 -0
  33. package/dist/components/layout/FlatTree.context.cjs.map +1 -0
  34. package/dist/components/layout/FlatTree.context.mjs +20 -0
  35. package/dist/components/layout/FlatTree.context.mjs.map +1 -0
  36. package/dist/components/layout/FlatTree.item.cjs +2 -0
  37. package/dist/components/layout/FlatTree.item.cjs.map +1 -0
  38. package/dist/components/layout/FlatTree.item.mjs +90 -0
  39. package/dist/components/layout/FlatTree.item.mjs.map +1 -0
  40. package/dist/components/layout/FlatTree.mjs +276 -0
  41. package/dist/components/layout/FlatTree.mjs.map +1 -0
  42. package/dist/components/layout/Tree.core.cjs +1 -1
  43. package/dist/components/layout/Tree.core.cjs.map +1 -1
  44. package/dist/components/layout/Tree.core.mjs +166 -165
  45. package/dist/components/layout/Tree.core.mjs.map +1 -1
  46. package/dist/components/layout/Tree.selection.cjs +1 -1
  47. package/dist/components/layout/Tree.selection.cjs.map +1 -1
  48. package/dist/components/layout/Tree.selection.mjs +11 -57
  49. package/dist/components/layout/Tree.selection.mjs.map +1 -1
  50. package/dist/components/layout/useFlatTree.cjs +2 -0
  51. package/dist/components/layout/useFlatTree.cjs.map +1 -0
  52. package/dist/components/layout/useFlatTree.mjs +83 -0
  53. package/dist/components/layout/useFlatTree.mjs.map +1 -0
  54. package/dist/components/table/DataGrid.cjs +1 -1
  55. package/dist/components/table/DataGrid.cjs.map +1 -1
  56. package/dist/components/table/DataGrid.context.cjs +2 -0
  57. package/dist/components/table/DataGrid.context.cjs.map +1 -0
  58. package/dist/components/table/DataGrid.context.mjs +74 -0
  59. package/dist/components/table/DataGrid.context.mjs.map +1 -0
  60. package/dist/components/table/DataGrid.header.cjs +2 -0
  61. package/dist/components/table/DataGrid.header.cjs.map +1 -0
  62. package/dist/components/table/DataGrid.header.mjs +168 -0
  63. package/dist/components/table/DataGrid.header.mjs.map +1 -0
  64. package/dist/components/table/DataGrid.mjs +488 -591
  65. package/dist/components/table/DataGrid.mjs.map +1 -1
  66. package/dist/components/table/DataGrid.sizing.cjs +2 -0
  67. package/dist/components/table/DataGrid.sizing.cjs.map +1 -0
  68. package/dist/components/table/DataGrid.sizing.mjs +187 -0
  69. package/dist/components/table/DataGrid.sizing.mjs.map +1 -0
  70. package/dist/components/table/DataGrid.store.cjs +2 -0
  71. package/dist/components/table/DataGrid.store.cjs.map +1 -0
  72. package/dist/components/table/DataGrid.store.mjs +108 -0
  73. package/dist/components/table/DataGrid.store.mjs.map +1 -0
  74. package/dist/components/table/DataGrid.window.cjs +2 -0
  75. package/dist/components/table/DataGrid.window.cjs.map +1 -0
  76. package/dist/components/table/DataGrid.window.mjs +165 -0
  77. package/dist/components/table/DataGrid.window.mjs.map +1 -0
  78. package/dist/components/table/Table.cjs +1 -1
  79. package/dist/components/table/Table.cjs.map +1 -1
  80. package/dist/components/table/Table.mjs +74 -72
  81. package/dist/components/table/Table.mjs.map +1 -1
  82. package/dist/components/table/Table.resize.cjs +2 -0
  83. package/dist/components/table/Table.resize.cjs.map +1 -0
  84. package/dist/components/table/Table.resize.mjs +98 -0
  85. package/dist/components/table/Table.resize.mjs.map +1 -0
  86. package/dist/components/table/useGridNavigation.cjs +1 -1
  87. package/dist/components/table/useGridNavigation.cjs.map +1 -1
  88. package/dist/components/table/useGridNavigation.mjs +343 -140
  89. package/dist/components/table/useGridNavigation.mjs.map +1 -1
  90. package/dist/components/table/useTableColumnSizing.cjs +2 -0
  91. package/dist/components/table/useTableColumnSizing.cjs.map +1 -0
  92. package/dist/components/table/useTableColumnSizing.mjs +76 -0
  93. package/dist/components/table/useTableColumnSizing.mjs.map +1 -0
  94. package/dist/components/table/useTableFeatures.cjs +2 -0
  95. package/dist/components/table/useTableFeatures.cjs.map +1 -0
  96. package/dist/components/table/useTableFeatures.mjs +110 -0
  97. package/dist/components/table/useTableFeatures.mjs.map +1 -0
  98. package/dist/components/table/useTableSelection.cjs +2 -0
  99. package/dist/components/table/useTableSelection.cjs.map +1 -0
  100. package/dist/components/table/useTableSelection.mjs +39 -0
  101. package/dist/components/table/useTableSelection.mjs.map +1 -0
  102. package/dist/components/table/useTableSort.cjs +2 -0
  103. package/dist/components/table/useTableSort.cjs.map +1 -0
  104. package/dist/components/table/useTableSort.mjs +58 -0
  105. package/dist/components/table/useTableSort.mjs.map +1 -0
  106. package/dist/hooks/useActiveDescendant.cjs +1 -1
  107. package/dist/hooks/useActiveDescendant.cjs.map +1 -1
  108. package/dist/hooks/useActiveDescendant.mjs +33 -25
  109. package/dist/hooks/useActiveDescendant.mjs.map +1 -1
  110. package/dist/hooks/useListbox.cjs +1 -1
  111. package/dist/hooks/useListbox.cjs.map +1 -1
  112. package/dist/hooks/useListbox.mjs +140 -130
  113. package/dist/hooks/useListbox.mjs.map +1 -1
  114. package/dist/hooks/useRovingTabIndex.cjs +1 -1
  115. package/dist/hooks/useRovingTabIndex.cjs.map +1 -1
  116. package/dist/hooks/useRovingTabIndex.mjs +1 -1
  117. package/dist/hooks/useRovingTabIndex.mjs.map +1 -1
  118. package/dist/hooks/useSelection.cjs +2 -0
  119. package/dist/hooks/useSelection.cjs.map +1 -0
  120. package/dist/hooks/useSelection.mjs +57 -0
  121. package/dist/hooks/useSelection.mjs.map +1 -0
  122. package/dist/hooks/useVirtualWindow.cjs +2 -0
  123. package/dist/hooks/useVirtualWindow.cjs.map +1 -0
  124. package/dist/hooks/useVirtualWindow.mjs +102 -0
  125. package/dist/hooks/useVirtualWindow.mjs.map +1 -0
  126. package/dist/index.cjs +1 -1
  127. package/dist/index.d.cts +1542 -53
  128. package/dist/index.d.ts +1542 -53
  129. package/dist/index.mjs +147 -136
  130. package/dist/lib/classNames.cjs +1 -1
  131. package/dist/lib/classNames.cjs.map +1 -1
  132. package/dist/lib/classNames.mjs +68 -49
  133. package/dist/lib/classNames.mjs.map +1 -1
  134. package/dist/lib/columnSizing.cjs +2 -0
  135. package/dist/lib/columnSizing.cjs.map +1 -0
  136. package/dist/lib/columnSizing.mjs +27 -0
  137. package/dist/lib/columnSizing.mjs.map +1 -0
  138. package/dist/lib/flatTree.cjs +2 -0
  139. package/dist/lib/flatTree.cjs.map +1 -0
  140. package/dist/lib/flatTree.mjs +118 -0
  141. package/dist/lib/flatTree.mjs.map +1 -0
  142. package/dist/lib/selection.cjs +2 -0
  143. package/dist/lib/selection.cjs.map +1 -0
  144. package/dist/lib/selection.mjs +28 -0
  145. package/dist/lib/selection.mjs.map +1 -0
  146. package/dist/lib/treeCascade.cjs +2 -0
  147. package/dist/lib/treeCascade.cjs.map +1 -0
  148. package/dist/lib/treeCascade.mjs +52 -0
  149. package/dist/lib/treeCascade.mjs.map +1 -0
  150. package/dist/lib/virtual.cjs +2 -0
  151. package/dist/lib/virtual.cjs.map +1 -0
  152. package/dist/lib/virtual.mjs +54 -0
  153. package/dist/lib/virtual.mjs.map +1 -0
  154. package/dist/styles.css +1 -1
  155. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useRovingTabIndex.mjs","names":["React","groupIntoRows","rowElements","container","ordered","row","item","columns","rows","start","gridTargets","vertical","homeEnd","current","rowIndex","scope","target","column","delta","r","best","bestDistance","distance","index","DEFAULT_ITEM_SELECTOR","COMPOSITE_ROLES","ARROW_KEY_OWNER_ROLES","NON_TEXT_INPUT_TYPES","OBSERVED_ATTRIBUTES","isNestedComposite","el","roleComposites","role","isInNestedComposite","node","STAMPED_SELECTOR","matchesSelector","selector","rewrittenTabIndex","last","record","records","rewritten","isFocusableSelf","getFocusTarget","element","ownStop","tabbable","getFirstTabbable","isDisabledElement","marker","isNativelyDisabled","ownsArrowKeys","tag","editable","canHoldTabStop","isHiddenByCss","view","hidden","cache","parent","child","visibility","hasFocus","active","isValidSelector","toEntry","resolveTabStop","rank","entries","values","value","tabStop","lastFocused","activeValue","orderByItems","items","resolved","byValue","RovingStore","notify","previous","options","candidates","itemSelector","result","seen","cssHidden","candidate","composite","nested","manageTabIndex","isHiddenInput","stop","tabIndex","original","own","generated","parseSnapshot","snapshot","textOf","isInContainer","findOwningItem","byElement","startsOnItem","useRovingTabIndex","legacyRef","maybeOptions","refOrOptions","store","focusedValue","setFocusedValue","focusedStop","setFocusedStop","isDev","warnOnce","orientation","domItems","stopEntries","ranks","present","tabStopValue","getTabIndex","containerRef","setRef","typeaheadItemsRef","typeaheadMatchesRef","useTypeahead","matches","focusItem","focusFirstOf","handleKeys","useEventCallback","gridCtrlHomeEnd","shortcut","isAltGraphCharacter","enabled","handled","targets","spaceOnly","gridMove","intent","getArrowIntent","dir","getDirection","step","count","i","loop","homeEndKeys","typeahead","onTypeahead","next","handleKeyDown","handleKeyDownCapture","isSearching","handleFocus","focusValue","focusFirst","focusLast"],"sources":["../../src/hooks/useRovingTabIndex.ts"],"sourcesContent":["import * as React from 'react';\nimport type { WaveDir } from '../components/provider/WaveProvider';\nimport { isDev, warnOnce } from '../lib/dev';\nimport { getArrowIntent, getDirection } from '../lib/direction';\nimport { getFirstTabbable, isHiddenInput } from '../lib/focus';\nimport { setRef } from '../lib/mergeRefs';\nimport { useEventCallback } from './useEventCallback';\nimport { isAltGraphCharacter, useTypeahead, type TypeaheadItem } from './useTypeahead';\n\n/**\n * Configuration options for the {@link useRovingTabIndex} hook. Disabled items are skipped; items\n * marked `data-disabled-focusable` (the `disabledFocusable` prop of WaveUI buttons, links and\n * choice controls) stay reachable and may hold the tab stop: APG allows focusable disabled items.\n */\nexport interface UseRovingTabIndexOptions {\n /**\n * The selected value. It may be absent, `''` or `null` (nothing selected) and does not have to be\n * one of the items.\n */\n activeValue?: string | null;\n /**\n * Explicit item order (0.4 call shape). Omitted ⇒ the DOM order of the elements matching\n * `itemSelector`. Disabled items are skipped either way, and so are listed values with no\n * element in the container (not rendered, or `hidden`/`inert`): they never hold the tab stop.\n */\n items?: readonly string[];\n /**\n * Arrow key axis: `'horizontal'` uses Left/Right (mirrored in RTL), `'vertical'` uses Up/Down,\n * `'both'` uses all four, in the DOM order of the items.\n *\n * `'grid'` is a two-dimensional grid: Left/Right move through the cells in reading order across\n * rows (mirrored in RTL); Up/Down go to the cell at the same index in the previous or next row,\n * else to the nearest enabled cell of that row (the earlier one on a tie), skipping rows without\n * an enabled cell, and stop at the first and last row; Home/End go to the first and last enabled\n * cell of the row, Ctrl+Home/End of the grid (whatever `homeEndKeys` says). Rows are the\n * `role=\"row\"` elements inside the container, else groups of `columns` items in DOM order. Without\n * either, `'grid'` acts as `'both'` and a development warning says so.\n * @default 'horizontal'\n */\n orientation?: 'horizontal' | 'vertical' | 'both' | 'grid';\n /**\n * With `orientation: 'grid'` and no `role=\"row\"` elements: the number of cells per row. The last\n * row may be shorter.\n */\n columns?: number;\n /**\n * Whether arrow keys wrap around at the ends.\n * @default true\n */\n loop?: boolean;\n /** Text direction. Defaults to the direction of the container at key time (`getDirection`). */\n dir?: WaveDir;\n /**\n * Selector for the item elements inside the container. A selector the browser rejects matches\n * nothing (no item is navigable or tabbable) and logs a development warning.\n * @default '[data-roving-value]'\n */\n itemSelector?: string;\n /**\n * Printable characters move focus to the next item whose text (`data-roving-text`, else its\n * text content) starts with them. A Space typed within 500 ms of another character continues\n * the search instead of activating the focused item: `containerProps.onKeyDownCapture` calls\n * `preventDefault()` on it before an item's own keydown handler runs, so that handler must skip\n * default-prevented events (`composeEventHandlers` does). Keys typed in content nested inside an\n * item (a row's action button) are not typeahead. A character typed with AltGr (which Windows\n * reports as Ctrl+Alt, e.g. Polish `ł`) is typeahead text; other Ctrl, Alt or Meta\n * combinations, Ctrl+Alt+Space included, are shortcuts and ignored. A match whose `focus()`\n * leaves focus where it was is passed over for the next item that matches.\n * @default false\n */\n typeahead?: boolean;\n /**\n * Whether Home/End move to the first/last enabled item.\n * @default true\n */\n homeEndKeys?: boolean;\n /** Which item holds the tab stop. 'active' (default): the enabled activeValue item, else the first\n * enabled item — APG Tabs/Radio/Listbox/Tree. 'last-focused': the last focused enabled item, else\n * 'active' — APG Toolbar (and the static Menu). Either way a nested composite or a control that\n * uses the arrow keys itself (text field, select, slider, spin button, editable combobox) holds\n * it only when no other item can: arrows pressed there never lead back to the other items, so\n * focusing one keeps the tab stop on the last focused other item. 'none': no item holds it\n * (every item gets tabIndex -1; a nested composite keeps its own tab stop, and one marked\n * `data-roving-transparent` joins the group's arrow order), for a composite reached only by\n * the arrow keys of an enclosing widget or by a script (a tree row's actions); the keys,\n * `focusValue`, `focusFirst` and `focusLast` keep working. */\n tabStop?: 'active' | 'last-focused' | 'none';\n /** true ⇒ the hook writes tabIndex 0/-1 onto item elements itself and assigns\n * data-roving-value=\"auto-<n>\" to items that lack one. For containers that do not render their\n * items (Toolbar children, Menu items, Tree rows). Default false (items call getTabIndex).\n * Matches that cannot take focus are no items: `input[type=hidden]` is skipped, and a control\n * hidden by CSS inside the container (`display: none` or `visibility: hidden` on it or on an\n * ancestor inside the container) is treated as disabled (skipped, never the tab stop, stamped\n * -1). A container hidden as a whole from outside keeps its items and tab stop.\n * When the container stops being one (its ref detaches, as when `containerProps` are no longer\n * spread, or moves to another element) or `manageTabIndex` turns false, every element the hook\n * stamped gets its own `tabindex` back (the attribute it had at its first stamp, or none; a\n * `tabIndex` its author rendered since, the commit that detaches the container included, stays)\n * and loses a `data-roving-value` the hook added (one React rendered stays). An element that\n * stops being an item (`itemSelector`, or a changed selector, no longer matches it) is handed\n * back the same way. The last focused item and the generated values are kept, so a container\n * that attaches again starts where it was. */\n manageTabIndex?: boolean;\n /** Called after an arrow, Home/End or typeahead key moved focus to `value`. */\n onFocusMove?: (value: string, event: React.KeyboardEvent) => void;\n}\n\n/** Props to spread onto the container element. */\nexport interface RovingContainerProps {\n ref: React.RefCallback<HTMLElement>;\n 'data-roving-container': '';\n onKeyDown: React.KeyboardEventHandler;\n /** With `typeahead`: default-prevents a Space that continues a search, before item handlers. */\n onKeyDownCapture: React.KeyboardEventHandler;\n onFocus: React.FocusEventHandler;\n}\n\n/** Result of {@link useRovingTabIndex}. */\nexport interface UseRovingTabIndexResult {\n /** Spread onto the container: ref, `data-roving-container` marker and the key/focus handlers. */\n containerProps: RovingContainerProps;\n /** The container's keydown handler (same function as `containerProps.onKeyDown`). */\n handleKeyDown: (e: React.KeyboardEvent) => void;\n /**\n * The container's capture-phase keydown handler (same function as\n * `containerProps.onKeyDownCapture`). With `typeahead`, it extends the search with a Space that\n * continues one and calls `preventDefault()`, before an item's own keydown handler runs: an item\n * that activates on Space must skip default-prevented events (`composeEventHandlers` does).\n */\n handleKeyDownCapture: (e: React.KeyboardEvent) => void;\n /** The container's focus handler (same function as `containerProps.onFocus`). */\n handleFocus: (e: React.FocusEvent) => void;\n /** `0` for the item that holds the tab stop, `-1` for every other item. */\n getTabIndex: (value: string) => 0 | -1;\n /** The value of the item that last received focus, or `null`. */\n focusedValue: string | null;\n /** Focuses the item with this value (ignored when it is disabled or unknown). */\n focusValue: (value: string) => void;\n /** Focuses the first enabled item. */\n focusFirst: () => void;\n /** Focuses the last enabled item. */\n focusLast: () => void;\n}\n\n/** The rows of a grid: the items of the `role=\"row\"` elements, else groups of `columns` items. */\nfunction groupIntoRows(\n ordered: readonly ResolvedItem[],\n container: HTMLElement,\n columns: number | undefined,\n): ResolvedItem[][] | null {\n const rowElements = container.querySelectorAll<HTMLElement>('[role=\"row\"]');\n if (rowElements.length > 0) {\n return Array.from(rowElements).map((row) =>\n ordered.filter((item) => row.contains(item.element)),\n );\n }\n if (columns !== undefined && columns > 0) {\n const rows: ResolvedItem[][] = [];\n for (let start = 0; start < ordered.length; start += columns) {\n rows.push(ordered.slice(start, start + columns));\n }\n return rows;\n }\n return null;\n}\n\n/**\n * Where a grid key (Up/Down, Home/End, Ctrl+Home/End) goes from `current`: the cells to try, in\n * order; `'stay'` when the key is the grid's but nothing is further that way; `undefined` when it is\n * not a grid key, or focus is not on a cell of a row (the container's own handling applies).\n */\nfunction gridTargets(\n e: React.KeyboardEvent,\n rows: readonly ResolvedItem[][],\n current: ResolvedItem | null | undefined,\n): ResolvedItem[] | 'stay' | undefined {\n const vertical = e.key === 'ArrowUp' || e.key === 'ArrowDown';\n const homeEnd = e.key === 'Home' || e.key === 'End';\n if (!current || (!vertical && !homeEnd)) return undefined;\n const rowIndex = rows.findIndex((row) => row.includes(current));\n if (rowIndex < 0) return undefined;\n if (homeEnd) {\n const scope = (e.ctrlKey ? rows.flat() : rows[rowIndex]).filter((item) => !item.disabled);\n const target = e.key === 'Home' ? scope[0] : scope[scope.length - 1];\n return target && target !== current ? [target] : 'stay';\n }\n // The column is the index in the row, counting every cell (disabled ones included).\n const column = rows[rowIndex].indexOf(current);\n const delta = e.key === 'ArrowDown' ? 1 : -1;\n for (let r = rowIndex + delta; r >= 0 && r < rows.length; r += delta) {\n let best: ResolvedItem | undefined;\n let bestDistance = Infinity;\n rows[r].forEach((item, index) => {\n if (item.disabled) return;\n const distance = Math.abs(index - column);\n if (distance < bestDistance) {\n best = item;\n bestDistance = distance;\n }\n });\n if (best) return [best];\n }\n return 'stay';\n}\n\ninterface ResolvedItem {\n value: string;\n /** The item element (for a nested composite: its root). */\n element: HTMLElement;\n /** A nested composite (own roving container or composite role) counts as one item. */\n nested: boolean;\n /**\n * Skipped by the keys and never the tab stop: disabled, a nested composite with nothing to\n * focus, or (`manageTabIndex`) hidden by CSS.\n */\n disabled: boolean;\n}\n\ninterface StoreOptions {\n itemSelector: string;\n manageTabIndex: boolean;\n items: readonly string[] | undefined;\n activeValue: string | null;\n tabStop: 'active' | 'last-focused' | 'none';\n /**\n * Whether an element with a composite role but no roving container of its own is a nested\n * composite (one item). Off in the 0.4 call shape and with explicit `items`, where such an element\n * is usually the widget's own root inside a wrapper that carries the container ref.\n */\n roleComposites: boolean;\n}\n\nconst DEFAULT_ITEM_SELECTOR = '[data-roving-value]';\n\n/** Roles of widgets that manage their own children: an element with one of them is one item. */\nconst COMPOSITE_ROLES = new Set([\n 'radiogroup',\n 'listbox',\n 'grid',\n 'treegrid',\n 'tablist',\n 'menu',\n 'menubar',\n 'tree',\n 'spinbutton',\n]);\n\n/**\n * Roles whose widgets use the arrow keys themselves. A combobox does only when it is editable (an\n * `<input>` or contenteditable, caught by the native checks): a select-only combobox such as\n * Dropdown's `<button role=\"combobox\">` uses Up/Down, Home and End, never Left/Right.\n */\nconst ARROW_KEY_OWNER_ROLES = new Set(['slider', 'spinbutton', 'textbox', 'searchbox']);\n\n/** `<input>` types that do not use the arrow keys for text editing. */\nconst NON_TEXT_INPUT_TYPES = new Set([\n 'button',\n 'checkbox',\n 'color',\n 'file',\n 'hidden',\n 'image',\n 'radio',\n 'reset',\n 'submit',\n]);\n\nconst OBSERVED_ATTRIBUTES = [\n 'disabled',\n 'aria-disabled',\n 'data-disabled-focusable',\n 'data-roving-disabled',\n 'data-roving-value',\n 'data-roving-container',\n 'data-roving-transparent',\n 'tabindex',\n 'hidden',\n 'inert',\n 'role',\n // Whether an item uses the arrow keys itself (text input types, contenteditable).\n 'type',\n 'contenteditable',\n];\n\n/**\n * Whether `el` is a nested composite: it has its own roving container, or (with `roleComposites`)\n * a composite role. An element marked `data-roving-transparent` is never one by its role (a\n * `role=\"radiogroup\"` whose radios are items of the enclosing container, `Toolbar.RadioGroup`);\n * its own `data-roving-container` still makes it one.\n */\nfunction isNestedComposite(el: Element, roleComposites: boolean): boolean {\n if (el.hasAttribute('data-roving-container')) return true;\n if (!roleComposites || el.hasAttribute('data-roving-transparent')) return false;\n const role = el.getAttribute('role');\n return role !== null && COMPOSITE_ROLES.has(role);\n}\n\n/** Whether `el` is a nested composite or inside one, below `container`. */\nfunction isInNestedComposite(el: Element, container: Element, roleComposites: boolean): boolean {\n for (let node: Element | null = el; node && node !== container; node = node.parentElement) {\n if (isNestedComposite(node, roleComposites)) return true;\n }\n return false;\n}\n\n/** What the hook knows of an element it stamped, to hand it back to its author. */\ninterface StampedOriginal {\n /**\n * The author's own `tabindex` attribute, or `null`: read at the first stamp, and replaced by the\n * one the author writes while the element is stamped (React rendering another `tabIndex`; the\n * next stamp overwrites it in the DOM).\n */\n tabIndex: string | null;\n /** The `tabindex` the hook left on the element at its last stamp. */\n stamped: string;\n /** The `data-roving-value` the hook added (a generated value), or `null` when it added none. */\n addedValue: string | null;\n}\n\n/**\n * Every element the hook stamps carries both attributes, whatever `itemSelector` matched it: the\n * hand-back finds its elements through them, also one the selector no longer matches.\n */\nconst STAMPED_SELECTOR = '[tabindex], [data-roving-value]';\n\nfunction matchesSelector(el: Element, selector: string): boolean {\n try {\n return el.matches(selector);\n } catch {\n // A selector the browser rejects matches nothing.\n return false;\n }\n}\n\n/**\n * The elements whose last `tabindex` write in `records` left the value it found: the author\n * (through React) wrote the value the stamp had left. The hook only writes to change a value, so\n * such a write is never its own. Needs records with `attributeOldValue`.\n */\nfunction rewrittenTabIndex(records: readonly MutationRecord[]): Set<Node> {\n const last = new Map<Node, MutationRecord>();\n for (const record of records) {\n if (record.type === 'attributes' && record.attributeName === 'tabindex') {\n last.set(record.target, record);\n }\n }\n const rewritten = new Set<Node>();\n for (const [target, record] of last) {\n if (record.oldValue === (target as Element).getAttribute('tabindex')) rewritten.add(target);\n }\n return rewritten;\n}\n\nfunction isFocusableSelf(el: HTMLElement): boolean {\n return el.tabIndex >= 0 && !el.hasAttribute('disabled');\n}\n\n/** The element to focus for an item: the item itself, or a nested composite's own tab stop. */\nfunction getFocusTarget(item: ResolvedItem): HTMLElement | null {\n const { element } = item;\n if (!item.nested || isFocusableSelf(element)) return element;\n const ownStop = element.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (ownStop) return ownStop;\n const tabbable = getFirstTabbable(element);\n return tabbable instanceof HTMLElement ? tabbable : null;\n}\n\n/**\n * Whether the keys skip an item, in this order: the composite's own `data-roving-disabled` marker\n * (not `\"false\"`) and native `disabled` skip it; `data-disabled-focusable` keeps it (a\n * `disabledFocusable` control: APG allows focusable disabled items); `aria-disabled=\"true\"` skips\n * it.\n */\nfunction isDisabledElement(el: HTMLElement): boolean {\n const marker = el.getAttribute('data-roving-disabled');\n if (marker !== null && marker !== 'false') return true;\n if (isNativelyDisabled(el)) return true;\n if (el.hasAttribute('data-disabled-focusable')) return false;\n return el.getAttribute('aria-disabled') === 'true';\n}\n\nfunction isNativelyDisabled(el: HTMLElement): boolean {\n if ((el as { disabled?: unknown }).disabled === true) return true;\n try {\n return el.matches(':disabled');\n } catch {\n return false;\n }\n}\n\n/** Text-entry fields and widgets that use the arrow keys themselves (APG Toolbar). */\nfunction ownsArrowKeys(target: EventTarget | null): boolean {\n if (!target || (target as Node).nodeType !== 1) return false;\n const el = target as HTMLElement;\n const role = el.getAttribute('role');\n if (role !== null && ARROW_KEY_OWNER_ROLES.has(role)) return true;\n const tag = el.localName;\n if (tag === 'textarea' || tag === 'select') return true;\n if (tag === 'input') return !NON_TEXT_INPUT_TYPES.has((el as HTMLInputElement).type);\n const editable = el.closest('[contenteditable]');\n return editable !== null && editable.getAttribute('contenteditable') !== 'false';\n}\n\n/**\n * Whether an item can hold the container's tab stop: not a nested composite (it keeps a tab stop\n * of its own) and not a control that uses the arrow keys itself. From either, the arrow keys never\n * lead to the other items, so a tab stop there would leave them reachable by keyboard only through\n * it (or not at all).\n */\nfunction canHoldTabStop(item: ResolvedItem): boolean {\n return !item.nested && !ownsArrowKeys(item.element);\n}\n\n/**\n * Whether CSS inside `container` keeps `el` from taking focus (browsers ignore `focus()` on it):\n * `display: none` on it or on an ancestor inside `container`, `visibility: hidden`/`collapse` set\n * inside `container`, or a place inside a closed `<details>` other than its summary. Hiding from\n * outside the container (a surface that stays invisible until it is positioned, a consumer panel)\n * hides the whole item set and does not count: showing it again changes nothing the hook observes,\n * so nothing would re-stamp. `cache` holds, for one resolution, whether an element hides its whole\n * subtree (the `display` and `<details>` checks).\n *\n * Internal: shared with Tree's treegrid navigation; not exported from the package.\n */\nexport function isHiddenByCss(\n el: Element,\n container: Element,\n cache: Map<Element, boolean>,\n): boolean {\n const view = el.ownerDocument.defaultView;\n if (!view) return false;\n for (let node: Element | null = el; node && node !== container; node = node.parentElement) {\n let hidden = cache.get(node);\n if (hidden === undefined) {\n const parent = node.parentElement;\n hidden =\n view.getComputedStyle(node).display === 'none' ||\n (parent !== null &&\n parent.localName === 'details' &&\n !parent.hasAttribute('open') &&\n Array.from(parent.children).find((child) => child.localName === 'summary') !== node);\n cache.set(node, hidden);\n }\n if (hidden) return true;\n }\n const { visibility } = view.getComputedStyle(el);\n if (visibility !== 'hidden' && visibility !== 'collapse') return false;\n // Visibility is inherited, so `el` also reads hidden when an ancestor of the container hides the\n // whole container. It counts only when the container itself is visible, i.e. when the hiding is\n // set inside it (on `el` or on a wrapper between them).\n return view.getComputedStyle(container).visibility === 'visible';\n}\n\n/** Whether focus is on `el` or inside it (in its document or shadow root). */\nfunction hasFocus(el: HTMLElement): boolean {\n const active = (el.getRootNode() as Node & { activeElement?: Element | null }).activeElement;\n return !!active && el.contains(active);\n}\n\nfunction isValidSelector(selector: string): boolean {\n try {\n document.createDocumentFragment().querySelector(selector);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * How an item takes part in the tab stop: `0` never (disabled), `1` can hold it, `2` holds it only\n * when no item can (a nested composite, or a control that uses the arrow keys itself).\n */\ntype TabStopRank = 0 | 1 | 2;\n\n/** One item of the published DOM snapshot, and the input of {@link resolveTabStop}. */\ntype SnapshotEntry = [value: string, rank: TabStopRank];\n\nfunction toEntry(item: ResolvedItem): SnapshotEntry {\n return [item.value, item.disabled ? 0 : canHoldTabStop(item) ? 1 : 2];\n}\n\n/**\n * The value that holds the tab stop, among the items of the best rank present (`1`, else `2`):\n * with 'last-focused' the last focused one, else the `activeValue` item, else the first one.\n */\nfunction resolveTabStop(\n entries: readonly SnapshotEntry[],\n activeValue: string | null,\n tabStop: 'active' | 'last-focused',\n lastFocused: string | null,\n): string | null {\n const rank = entries.some(([, r]) => r === 1) ? 1 : 2;\n const values = entries.filter(([, r]) => r === rank).map(([value]) => value);\n if (tabStop === 'last-focused' && lastFocused !== null && values.includes(lastFocused)) {\n return lastFocused;\n }\n if (activeValue !== null && activeValue !== '' && values.includes(activeValue)) {\n return activeValue;\n }\n return values[0] ?? null;\n}\n\nfunction orderByItems(\n resolved: ResolvedItem[],\n items: readonly string[] | undefined,\n): ResolvedItem[] {\n if (!items) return resolved;\n const byValue = new Map(resolved.map((item) => [item.value, item]));\n const ordered: ResolvedItem[] = [];\n for (const value of items) {\n const item = byValue.get(value);\n if (item) ordered.push(item);\n }\n return ordered;\n}\n\n/**\n * Per-hook store: resolves items from the DOM, publishes the enabled set to React through\n * `useSyncExternalStore` (a MutationObserver on the container notifies it), and stamps tabIndex in\n * `manageTabIndex` mode.\n */\nclass RovingStore {\n container: HTMLElement | null = null;\n lastFocused: string | null = null;\n options: StoreOptions = {\n itemSelector: DEFAULT_ITEM_SELECTOR,\n manageTabIndex: false,\n items: undefined,\n activeValue: null,\n tabStop: 'active',\n roleComposites: true,\n };\n\n private readonly stamped = new WeakMap<Element, StampedOriginal>();\n private readonly autoValues = new WeakMap<Element, string>();\n private autoCounter = 0;\n private notify: (() => void) | null = null;\n private observer: MutationObserver | null = null;\n private dirty = true;\n private snapshot = '';\n\n subscribe = (notify: () => void): (() => void) => {\n this.notify = notify;\n this.observe();\n return () => {\n this.notify = null;\n this.disconnect();\n };\n };\n\n getSnapshot = (): string => {\n if (!this.container) return '';\n if (this.dirty) {\n this.dirty = false;\n // DOM facts only (order and tab stop rank); `items` is applied during render.\n this.snapshot = JSON.stringify(this.resolveOwn().map(toEntry));\n }\n return this.snapshot;\n };\n\n getServerSnapshot = (): string => '';\n\n /**\n * Attaches the store to `container` (`null` detaches it). A container that stops being one (its\n * ref detached, or the store moved to another element) first hands every element it stamped back\n * to its author, with the writes its observer has not delivered yet (the `tabIndex` React\n * rendered in the commit that detaches it). Only a store that manages tabIndex has stamped\n * anything: `setOptions` hands the stamps back the moment `manageTabIndex` turns off.\n */\n setContainer(container: HTMLElement | null): void {\n if (container === this.container) return;\n // Disconnecting drops the pending records anyway; the hand-back reads them first.\n const records = this.takeRecords();\n this.disconnect();\n if (this.container && this.options.manageTabIndex) {\n this.release(this.container, 'all', records);\n }\n this.container = container;\n this.dirty = true;\n if (this.notify) {\n this.observe();\n this.notify();\n }\n }\n\n /**\n * Records a focused item for the 'last-focused' tab stop. An item that cannot hold the tab stop\n * (a nested composite, a control that uses the arrow keys itself) leaves the last one in place.\n */\n recordFocus(item: ResolvedItem): void {\n if (canHoldTabStop(item)) this.lastFocused = item.value;\n }\n\n setOptions(options: StoreOptions): void {\n const previous = this.options;\n this.options = options;\n // `manageTabIndex` turned off: the elements stamped so far go back to their author. The records\n // taken here are not delivered; the notification below stands in for them. (A changed\n // `itemSelector` hands back what it no longer matches at the next stamp.)\n if (previous.manageTabIndex && !options.manageTabIndex && this.container) {\n this.release(this.container, 'all', this.takeRecords());\n }\n if (\n previous.itemSelector !== options.itemSelector ||\n previous.manageTabIndex !== options.manageTabIndex ||\n previous.roleComposites !== options.roleComposites\n ) {\n this.dirty = true;\n this.notify?.();\n }\n }\n\n /** Own items in navigation order: the explicit `items` order when given, else DOM order. */\n resolve(container: HTMLElement | null = this.container): ResolvedItem[] {\n return orderByItems(this.resolveOwn(container), this.options.items);\n }\n\n /** Own items of the container in DOM order (items of nested composites excluded). */\n resolveOwn(container: HTMLElement | null = this.container): ResolvedItem[] {\n if (!container) return [];\n const { itemSelector, manageTabIndex, roleComposites } = this.options;\n let candidates: HTMLElement[];\n try {\n candidates = Array.from(container.querySelectorAll<HTMLElement>(itemSelector));\n } catch {\n // An invalid selector (warned from an effect in development): this runs during render\n // (getSnapshot), where throwing would take the whole tree down.\n return [];\n }\n\n const result: ResolvedItem[] = [];\n const seen = new Set<Element>();\n const cssHidden = new Map<Element, boolean>();\n for (const candidate of candidates) {\n let composite: HTMLElement | null = null;\n let hidden = false;\n for (\n let node: HTMLElement | null = candidate;\n node && node !== container;\n node = node.parentElement\n ) {\n if (isNestedComposite(node, roleComposites)) composite = node;\n if (node.hidden || node.hasAttribute('inert')) hidden = true;\n }\n if (hidden) continue;\n\n const element = composite ?? candidate;\n if (seen.has(element)) continue;\n const nested = composite !== null;\n if (!nested && manageTabIndex) {\n // An author tabindex=\"-1\" (a SpinButton stepper, SplitButton internals) opts out.\n if (candidate.getAttribute('tabindex') === '-1' && !this.stamped.has(candidate)) continue;\n // A hidden form input (HiddenInput's `type=\"hidden\"`) is never focusable.\n if (isHiddenInput(candidate)) continue;\n }\n seen.add(element);\n\n const item: ResolvedItem = {\n value: this.valueOf(element, nested),\n element,\n nested,\n disabled: isDisabledElement(element),\n };\n if (nested && !item.disabled && !getFocusTarget(item)) item.disabled = true;\n // Consumer children hidden by CSS (a responsive `hidden md:inline-flex` control). Treated as\n // disabled rather than dropped, so it is stamped -1 and is no extra Tab stop once the CSS\n // shows it again (a CSS change triggers no mutation, hence no re-stamp).\n if (manageTabIndex && !item.disabled && isHiddenByCss(element, container, cssHidden)) {\n item.disabled = true;\n }\n result.push(item);\n }\n return result;\n }\n\n /**\n * manageTabIndex: writes tabIndex 0 on the tab stop and -1 on every other own item. An element\n * it stamped that is no longer one of its items (it now belongs to a nested composite, as when a\n * `data-roving-transparent` marker is removed, or `itemSelector` no longer matches it) is handed\n * back first. A `tabindex` the author rendered since the last stamp becomes the element's own.\n */\n stamp(): void {\n if (!this.options.manageTabIndex || !this.container) return;\n this.release(this.container, 'stale');\n const resolved = this.resolve();\n const stop =\n this.options.tabStop === 'none'\n ? null\n : resolveTabStop(\n resolved.map(toEntry),\n this.options.activeValue,\n this.options.tabStop,\n this.lastFocused,\n );\n for (const item of resolved) {\n if (item.nested) continue;\n const { element } = item;\n const tabIndex = item.value === stop ? '0' : '-1';\n const current = element.getAttribute('tabindex');\n let original = this.stamped.get(element);\n if (!original) {\n original = { tabIndex: current, stamped: tabIndex, addedValue: null };\n this.stamped.set(element, original);\n } else {\n // Another value than the last stamp left: the author rendered it, so it is the element's own.\n if (current !== original.stamped) original.tabIndex = current;\n original.stamped = tabIndex;\n }\n if (current !== tabIndex) element.setAttribute('tabindex', tabIndex);\n if (!element.hasAttribute('data-roving-value')) {\n element.setAttribute('data-roving-value', item.value);\n original.addedValue = item.value;\n }\n }\n }\n\n /**\n * Hands elements it stamped inside `container` back to their author and drops their records, so\n * a later stamp reads them afresh. The `tabindex` the record holds as the author's is put back\n * (removed when there was none), unless the author wrote one since the last stamp, which stays:\n * another value than the stamp's, or the stamp's value written again (seen in the pending\n * `records`). A `data-roving-value` the hook added is removed while it still holds the generated\n * value (one React renders stays). The generated values and the last focused item are kept: a\n * container that attaches again starts where it was. `'stale'`: only the elements that are no\n * longer its items, because they belong to a nested composite or `itemSelector` no longer matches\n * them (every stamp); `'all'`: every element (the container detaches or is replaced, or\n * `manageTabIndex` turns off).\n */\n private release(\n container: HTMLElement,\n scope: 'stale' | 'all',\n records: readonly MutationRecord[] = [],\n ): void {\n const rewritten = rewrittenTabIndex(records);\n const { itemSelector, roleComposites } = this.options;\n for (const element of Array.from(container.querySelectorAll<HTMLElement>(STAMPED_SELECTOR))) {\n const original = this.stamped.get(element);\n if (!original) continue;\n if (\n scope === 'stale' &&\n matchesSelector(element, itemSelector) &&\n !isInNestedComposite(element, container, roleComposites)\n ) {\n continue;\n }\n this.stamped.delete(element);\n const current = element.getAttribute('tabindex');\n const authorWrote = current !== original.stamped || rewritten.has(element);\n // Written only to change the value: the hook's own writes never keep one (`rewrittenTabIndex`).\n if (!authorWrote && current !== original.tabIndex) {\n if (original.tabIndex === null) element.removeAttribute('tabindex');\n else element.setAttribute('tabindex', original.tabIndex);\n }\n if (\n original.addedValue !== null &&\n element.getAttribute('data-roving-value') === original.addedValue\n ) {\n element.removeAttribute('data-roving-value');\n }\n }\n }\n\n /**\n * Takes over, as the element's own, a `tabindex` the author wrote with the value the stamp had\n * left (`rewrittenTabIndex`): the next stamp cannot tell it from its own. Another value is taken\n * over by the next stamp.\n */\n private adopt(records: readonly MutationRecord[]): void {\n for (const target of rewrittenTabIndex(records)) {\n const original = this.stamped.get(target as Element);\n if (original) original.tabIndex = (target as Element).getAttribute('tabindex');\n }\n }\n\n /** The records the observer has not delivered yet; it delivers none of them afterwards. */\n private takeRecords(): MutationRecord[] {\n return this.observer?.takeRecords() ?? [];\n }\n\n private valueOf(element: HTMLElement, nested: boolean): string {\n const own = nested ? null : element.getAttribute('data-roving-value');\n if (own !== null) return own;\n let generated = this.autoValues.get(element);\n if (generated === undefined) {\n this.autoCounter += 1;\n generated = `auto-${this.autoCounter}`;\n this.autoValues.set(element, generated);\n }\n return generated;\n }\n\n private observe(): void {\n if (!this.container || this.observer || typeof MutationObserver === 'undefined') return;\n this.observer = new MutationObserver((records) => {\n this.adopt(records);\n this.stamp();\n this.dirty = true;\n this.notify?.();\n });\n this.observer.observe(this.container, {\n subtree: true,\n childList: true,\n attributes: true,\n // With the old value, a write that keeps a value shows as the author's (`rewrittenTabIndex`).\n attributeOldValue: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n this.dirty = true;\n }\n\n private disconnect(): void {\n this.observer?.disconnect();\n this.observer = null;\n }\n}\n\nfunction parseSnapshot(snapshot: string): SnapshotEntry[] | null {\n if (snapshot === '') return null;\n try {\n return JSON.parse(snapshot) as SnapshotEntry[];\n } catch {\n return null;\n }\n}\n\nfunction textOf(element: HTMLElement): string {\n return element.getAttribute('data-roving-text') ?? element.textContent ?? '';\n}\n\n/**\n * Whether an event target is the container or inside its DOM. React bubbles events from a portal\n * (a Menu.Popover or Popover.Content a Toolbar item renders) through the container although their\n * target is elsewhere in the document; those events are not the container's to handle.\n */\nfunction isInContainer(container: Node, target: EventTarget | null): boolean {\n if (!target || typeof (target as Node).nodeType !== 'number') return false;\n return target === container || container.contains(target as Node);\n}\n\n/**\n * The item an event started in: the innermost item element that is the target or one of its\n * ancestors (up to the container). Items can nest (an APG treeitem contains the group of its child\n * treeitems), so the first containing item in DOM order would be the outermost ancestor.\n */\nfunction findOwningItem(\n items: readonly ResolvedItem[],\n target: EventTarget | null,\n container: Node,\n): ResolvedItem | null {\n if (items.length === 0 || !target || typeof (target as Node).nodeType !== 'number') return null;\n const byElement = new Map<Node, ResolvedItem>(items.map((item) => [item.element, item]));\n for (let node: Node | null = target as Node; node && node !== container; node = node.parentNode) {\n const item = byElement.get(node);\n if (item) return item;\n }\n return null;\n}\n\n/**\n * Whether a key started on the item itself (on the container when it started on no item), not on\n * content nested inside the item, such as a row's action button.\n */\nfunction startsOnItem(item: ResolvedItem | null, target: EventTarget | null, container: Node) {\n return item ? target === getFocusTarget(item) : target === container;\n}\n\n/**\n * Implements the WAI-ARIA roving tabindex pattern for composite widgets: one item holds the tab\n * stop (`tabIndex=0`, all others `-1`), arrow keys move focus between items, Home/End jump to the\n * ends, and (optionally) typeahead jumps by text. Used by RadioGroup, Rating, SwatchPicker, List,\n * TabList, Tree, Menu and Toolbar.\n *\n * - **Items** come from the DOM at event time: elements matching `itemSelector` (default\n * `[data-roving-value]`) whose nearest roving container is this one. A nested composite (an\n * element with its own `data-roving-container`, or a radiogroup/listbox/grid/tablist/menu/tree/\n * spinbutton role) counts as one item whose focus target is its own tab stop; the hook never\n * writes tabindex inside it, so it keeps its own Tab stop. An element marked\n * `data-roving-transparent` is never a nested composite by its role: its items are items of this\n * container (the radios of `Toolbar.RadioGroup`); it must not carry a `data-roving-container`.\n * Pass `items` to fix the order instead.\n * In the 0.4 call shape and with explicit `items`, only elements with their own roving container\n * are nested composites: a role-only `radiogroup`/`tablist`/… element between the container and\n * the items (the widget's root inside a wrapper that holds the ref) does not swallow them.\n * - **Disabled items** (`data-roving-disabled`, `disabled`, `aria-disabled=\"true\"`) are skipped and\n * never hold the tab stop. Items marked `data-disabled-focusable` (the `disabledFocusable` prop\n * of WaveUI buttons, links and choice controls) stay reachable and may hold the tab stop: APG\n * allows focusable disabled items. The attribute only overrides `aria-disabled`: an item that is\n * natively disabled or marked `data-roving-disabled` is still skipped. The enabled set is\n * tracked with a MutationObserver, so an item that disables itself moves the tab stop without\n * the owner re-rendering. With `manageTabIndex`, a control hidden by CSS counts as disabled and\n * `input[type=hidden]` is no item.\n * - **Hand-back** (`manageTabIndex`): a container that stops being one (its ref detaches or moves\n * to another element) or that stops managing tabIndex gives every element it stamped its own\n * `tabindex` back (the one it had, or the one its author rendered since) and removes the\n * `data-roving-value` it added, so each control is its own Tab stop again; attached again, it\n * stamps again from the last focused item. An element `itemSelector` no longer matches is handed\n * back at once.\n * - **Tab stop** (`tabStop`): `'active'` — the enabled `activeValue` item, else the first enabled\n * item; `'last-focused'` — the last focused enabled item, else the `'active'` rule. A nested\n * composite or a control that uses the arrow keys itself holds it only when no other item can;\n * `'none'` — no item: every item is `tabIndex=-1` (a nested composite keeps its own tab stop;\n * mark it `data-roving-transparent` to join the group's arrow order), and focus reaches the\n * items only through the keys or a script.\n * - **Keys** are ignored when another handler already called `preventDefault()`; with Meta; with\n * Alt or Ctrl, except a single character typed with AltGr (Ctrl+Alt on Windows), which is\n * typeahead text (arrows, Home, End and Space with Ctrl+Alt stay ignored); and when they start\n * in a text field, select, contenteditable, slider, spinbutton or editable combobox (Left/Right\n * keep moving the caret; a select-only combobox such as Dropdown's button does not keep them).\n * Left/Right are mirrored in RTL (`dir`, else the direction of the container at key time).\n * Arrows and typeahead move from the item the key started in — the innermost one when items\n * nest, as treeitems do inside their parent's group; when it started on no item (focus on the\n * container itself) next goes to the first enabled item and prev to the last.\n * An item whose `focus()` leaves focus where it was (the browser ignores it on an element CSS\n * hides) is passed over for the next one, and is never recorded or reported to `onFocusMove`.\n * Handled keys call `preventDefault()`. Keys and focus from outside the container's DOM — a\n * popup an item renders through a portal, whose events React bubbles through the container —\n * are ignored, so focus never leaves an open menu or popover for the container's items.\n *\n * @example\n * const { containerProps, getTabIndex } = useRovingTabIndex({ activeValue: value, orientation: 'both' });\n * <div role=\"radiogroup\" {...containerProps}>\n * <button role=\"radio\" data-roving-value=\"a\" tabIndex={getTabIndex('a')} />\n * </div>\n *\n * The 0.4 call shape `useRovingTabIndex(containerRef, options)` still works: pass the container's\n * ref object and attach `handleKeyDown` (and ideally `handleFocus`, and with `typeahead`\n * `handleKeyDownCapture` as `onKeyDownCapture`) yourself.\n *\n * @param options - See {@link UseRovingTabIndexOptions}.\n * @returns See {@link UseRovingTabIndexResult}.\n */\nexport function useRovingTabIndex(options: UseRovingTabIndexOptions): UseRovingTabIndexResult;\nexport function useRovingTabIndex(\n containerRef: React.RefObject<HTMLElement | null>,\n options: UseRovingTabIndexOptions,\n): UseRovingTabIndexResult;\nexport function useRovingTabIndex(\n refOrOptions: React.RefObject<HTMLElement | null> | UseRovingTabIndexOptions,\n maybeOptions?: UseRovingTabIndexOptions,\n): UseRovingTabIndexResult {\n const legacyRef =\n maybeOptions !== undefined ? (refOrOptions as React.RefObject<HTMLElement | null>) : undefined;\n const options = (maybeOptions ?? refOrOptions) as UseRovingTabIndexOptions;\n const {\n activeValue = null,\n items,\n orientation = 'horizontal',\n columns,\n loop = true,\n dir,\n itemSelector = DEFAULT_ITEM_SELECTOR,\n typeahead = false,\n homeEndKeys = true,\n tabStop = 'active',\n manageTabIndex = false,\n onFocusMove,\n } = options;\n\n const [store] = React.useState(() => new RovingStore());\n const snapshot = React.useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n );\n const [focusedValue, setFocusedValue] = React.useState<string | null>(null);\n // The last focused item that can hold the tab stop ('last-focused' during render).\n const [focusedStop, setFocusedStop] = React.useState<string | null>(null);\n\n // 0.4 call shape or explicit `items`: only elements with their own roving container are nested\n // composites (a role=radiogroup/tablist/… around the items is the widget's own root).\n const roleComposites = legacyRef === undefined && items === undefined;\n\n React.useLayoutEffect(() => {\n store.setOptions({ itemSelector, manageTabIndex, items, activeValue, tabStop, roleComposites });\n if (legacyRef) store.setContainer(legacyRef.current);\n store.stamp();\n });\n\n // C-DEV: `resolveOwn` treats a selector the browser rejects as \"no items\" (it runs during render,\n // where it must not throw), so the developer hears about it here.\n React.useEffect(() => {\n if (isDev && !isValidSelector(itemSelector)) {\n warnOnce(\n `useRovingTabIndex:itemSelector:${itemSelector}`,\n `useRovingTabIndex: itemSelector \"${itemSelector}\" is not a valid CSS selector in this browser; no item is navigable or tabbable.`,\n );\n }\n }, [itemSelector]);\n\n // C-DEV: a grid needs rows, from role=\"row\" elements or a `columns` count.\n React.useEffect(() => {\n if (!isDev || orientation !== 'grid' || columns) return;\n const container = store.container;\n if (container && container.querySelector('[role=\"row\"]') === null) {\n warnOnce(\n 'useRovingTabIndex:grid-rows',\n 'useRovingTabIndex: orientation \"grid\" needs role=\"row\" elements around the items or a `columns` count; without either it acts as \"both\".',\n );\n }\n });\n\n // Tab stop during render, from the DOM snapshot (null before the container is known).\n const domItems = React.useMemo(() => parseSnapshot(snapshot), [snapshot]);\n const stopEntries = React.useMemo<readonly SnapshotEntry[] | null>(() => {\n if (!items) return domItems;\n // Explicit `items`: the listed values the DOM has, in the listed order. Every listed value\n // before the container is known, or while the DOM has none of them yet (items rendered in the\n // commit the snapshot has not caught up with).\n if (domItems) {\n const ranks = new Map(domItems);\n const present = items.flatMap((value): SnapshotEntry[] => {\n const rank = ranks.get(value);\n return rank === undefined ? [] : [[value, rank]];\n });\n if (present.length > 0) return present;\n }\n return items.map((value): SnapshotEntry => [value, 1]);\n }, [items, domItems]);\n const tabStopValue =\n tabStop === 'none'\n ? null\n : stopEntries\n ? resolveTabStop(stopEntries, activeValue, tabStop, focusedStop)\n : activeValue || null;\n\n const getTabIndex = React.useCallback(\n (value: string): 0 | -1 => (value === tabStopValue ? 0 : -1),\n [tabStopValue],\n );\n\n const containerRef = React.useCallback(\n (node: HTMLElement | null) => {\n store.setContainer(node);\n if (legacyRef) setRef(legacyRef, node);\n },\n [store, legacyRef],\n );\n\n const typeaheadItemsRef = React.useRef<TypeaheadItem[]>([]);\n // Every item matching the typed characters, in search order (the first is the match).\n const typeaheadMatchesRef = React.useRef<readonly string[]>([]);\n const { onTypeahead, isSearching } = useTypeahead({\n getItems: () => typeaheadItemsRef.current,\n onMatch: (_value, matches) => {\n typeaheadMatchesRef.current = matches;\n },\n });\n\n /** Focuses an item; `true` only when focus actually moved there (then it is recorded). */\n const focusItem = (item: ResolvedItem | undefined): boolean => {\n if (!item || item.disabled) return false;\n const target = getFocusTarget(item);\n if (!target) return false;\n target.focus();\n // Browsers ignore focus() on an element CSS hides (display: none, visibility: hidden).\n if (!hasFocus(target)) return false;\n store.recordFocus(item);\n return true;\n };\n\n /** Focuses the first item of `candidates` that takes focus. */\n const focusFirstOf = (candidates: readonly ResolvedItem[]): void => {\n for (const item of candidates) {\n if (focusItem(item)) return;\n }\n };\n\n // `spaceOnly` (the capture phase) handles nothing but a Space that continues a typeahead search.\n const handleKeys = useEventCallback((e: React.KeyboardEvent, spaceOnly: boolean) => {\n // Ctrl, Alt and Meta combinations are shortcuts, except a single character typed with AltGr\n // (Ctrl+Alt on Windows), which is typeahead text: arrows, Home, End and Space with it stay\n // shortcuts.\n const gridCtrlHomeEnd =\n orientation === 'grid' && e.ctrlKey && !e.altKey && (e.key === 'Home' || e.key === 'End');\n const shortcut =\n e.metaKey || ((e.altKey || e.ctrlKey) && !isAltGraphCharacter(e) && !gridCtrlHomeEnd);\n if (e.defaultPrevented || shortcut) return;\n if (ownsArrowKeys(e.target)) return;\n const container = store.container ?? (e.currentTarget as HTMLElement);\n // A key from a portaled popup is the popup's: never pull focus back out of it.\n if (!isInContainer(container, e.target)) return;\n const ordered = store.resolve(container);\n const enabled = ordered.filter((item) => !item.disabled);\n if (enabled.length === 0) return;\n\n // The (innermost) item the key started in. None (the container itself has focus): next → first,\n // prev → last.\n const current = findOwningItem(ordered, e.target, container);\n\n let handled = false;\n // Where focus goes, in order of preference: an item whose focus() does not move focus (hidden\n // by CSS) passes the key on to the next one.\n let targets: ResolvedItem[] = [];\n const rows =\n orientation === 'grid' && !spaceOnly ? groupIntoRows(ordered, container, columns) : null;\n const gridMove = rows ? gridTargets(e, rows, current) : undefined;\n const intent =\n spaceOnly || gridMove\n ? null\n : getArrowIntent(e.key, {\n orientation: orientation === 'grid' ? (rows ? 'horizontal' : 'both') : orientation,\n dir: dir ?? getDirection(e.currentTarget as Element),\n });\n if (gridMove) {\n handled = true;\n targets = gridMove === 'stay' ? [] : gridMove;\n } else if (intent) {\n handled = true;\n if (!current) {\n targets = intent === 'next' ? enabled : [...enabled].reverse();\n } else {\n const step = intent === 'next' ? 1 : -1;\n const count = ordered.length;\n let index = ordered.indexOf(current);\n for (let i = 1; i < count; i++) {\n index += step;\n if (index < 0 || index >= count) {\n if (!loop) break;\n index = (index + count) % count;\n }\n if (!ordered[index].disabled) targets.push(ordered[index]);\n }\n }\n } else if (!spaceOnly && homeEndKeys && (e.key === 'Home' || e.key === 'End')) {\n handled = true;\n targets = e.key === 'Home' ? enabled : [...enabled].reverse();\n } else if (typeahead && startsOnItem(current, e.target, container)) {\n typeaheadItemsRef.current = ordered.map((item) => ({\n value: item.value,\n text: textOf(item.element),\n disabled: item.disabled,\n }));\n typeaheadMatchesRef.current = [];\n if (onTypeahead(e, current?.value ?? null)) {\n handled = true;\n // The match first, then the next ones, for a match that does not take focus.\n targets = typeaheadMatchesRef.current.flatMap((value) =>\n enabled.filter((item) => item.value === value),\n );\n }\n }\n\n if (!handled) return;\n e.preventDefault();\n for (const next of targets) {\n // Back at the item the key started on (Home on the first item): focus stays.\n if (next === current) return;\n if (focusItem(next)) {\n onFocusMove?.(next.value, e);\n return;\n }\n }\n });\n\n const handleKeyDown = useEventCallback((e: React.KeyboardEvent) => handleKeys(e, false));\n\n // A Space that continues a typeahead search belongs to the search, not to the focused item: it is\n // handled and default-prevented in the capture phase, so the item's own keydown handler, which\n // skips default-prevented events, does not activate the item.\n const handleKeyDownCapture = useEventCallback((e: React.KeyboardEvent) => {\n if (typeahead && e.key === ' ' && isSearching()) handleKeys(e, true);\n });\n\n const handleFocus = useEventCallback((e: React.FocusEvent) => {\n const container = store.container ?? (e.currentTarget as HTMLElement);\n if (!isInContainer(container, e.target)) return;\n const item = findOwningItem(store.resolve(container), e.target, container);\n if (!item) return;\n store.recordFocus(item);\n setFocusedValue(item.value);\n if (canHoldTabStop(item)) setFocusedStop(item.value);\n store.stamp();\n });\n\n const focusValue = useEventCallback((value: string) => {\n focusItem(store.resolve().find((item) => item.value === value));\n });\n const focusFirst = useEventCallback(() => {\n focusFirstOf(store.resolve().filter((item) => !item.disabled));\n });\n const focusLast = useEventCallback(() => {\n focusFirstOf(\n store\n .resolve()\n .filter((item) => !item.disabled)\n .reverse(),\n );\n });\n\n const containerProps = React.useMemo<RovingContainerProps>(\n () => ({\n ref: containerRef,\n 'data-roving-container': '',\n onKeyDown: handleKeyDown,\n onKeyDownCapture: handleKeyDownCapture,\n onFocus: handleFocus,\n }),\n [containerRef, handleKeyDown, handleKeyDownCapture, handleFocus],\n );\n\n return {\n containerProps,\n handleKeyDown,\n handleKeyDownCapture,\n handleFocus,\n getTabIndex,\n focusedValue,\n focusValue,\n focusFirst,\n focusLast,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useRovingTabIndex.mjs","names":["React","groupIntoRows","rowElements","container","ordered","row","item","columns","rows","start","gridTargets","vertical","homeEnd","current","rowIndex","scope","target","column","delta","r","best","bestDistance","distance","index","DEFAULT_ITEM_SELECTOR","COMPOSITE_ROLES","ARROW_KEY_OWNER_ROLES","NON_TEXT_INPUT_TYPES","OBSERVED_ATTRIBUTES","isNestedComposite","el","roleComposites","role","isInNestedComposite","node","STAMPED_SELECTOR","matchesSelector","selector","rewrittenTabIndex","last","record","records","rewritten","isFocusableSelf","getFocusTarget","element","ownStop","tabbable","getFirstTabbable","isDisabledElement","marker","isNativelyDisabled","ownsArrowKeys","tag","editable","canHoldTabStop","isHiddenByCss","view","hidden","cache","parent","child","visibility","hasFocus","active","isValidSelector","toEntry","resolveTabStop","rank","entries","values","value","tabStop","lastFocused","activeValue","orderByItems","items","resolved","byValue","RovingStore","notify","previous","options","candidates","itemSelector","result","seen","cssHidden","candidate","composite","nested","manageTabIndex","isHiddenInput","stop","tabIndex","original","own","generated","parseSnapshot","snapshot","textOf","isInContainer","findOwningItem","byElement","startsOnItem","useRovingTabIndex","legacyRef","maybeOptions","refOrOptions","store","focusedValue","setFocusedValue","focusedStop","setFocusedStop","isDev","warnOnce","orientation","domItems","stopEntries","ranks","present","tabStopValue","getTabIndex","containerRef","setRef","typeaheadItemsRef","typeaheadMatchesRef","useTypeahead","matches","focusItem","focusFirstOf","handleKeys","useEventCallback","gridCtrlHomeEnd","shortcut","isAltGraphCharacter","enabled","handled","targets","spaceOnly","gridMove","intent","getArrowIntent","dir","getDirection","step","count","i","loop","homeEndKeys","typeahead","onTypeahead","next","handleKeyDown","handleKeyDownCapture","isSearching","handleFocus","focusValue","focusFirst","focusLast"],"sources":["../../src/hooks/useRovingTabIndex.ts"],"sourcesContent":["import * as React from 'react';\nimport type { WaveDir } from '../components/provider/WaveProvider';\nimport { isDev, warnOnce } from '../lib/dev';\nimport { getArrowIntent, getDirection } from '../lib/direction';\nimport { getFirstTabbable, isHiddenInput } from '../lib/focus';\nimport { setRef } from '../lib/mergeRefs';\nimport { useEventCallback } from './useEventCallback';\nimport { isAltGraphCharacter, useTypeahead, type TypeaheadItem } from './useTypeahead';\n\n/**\n * Configuration options for the {@link useRovingTabIndex} hook. Disabled items are skipped; items\n * marked `data-disabled-focusable` (the `disabledFocusable` prop of WaveUI buttons, links and\n * choice controls) stay reachable and may hold the tab stop: APG allows focusable disabled items.\n */\nexport interface UseRovingTabIndexOptions {\n /**\n * The selected value. It may be absent, `''` or `null` (nothing selected) and does not have to be\n * one of the items.\n */\n activeValue?: string | null;\n /**\n * Explicit item order (0.4 call shape). Omitted ⇒ the DOM order of the elements matching\n * `itemSelector`. Disabled items are skipped either way, and so are listed values with no\n * element in the container (not rendered, or `hidden`/`inert`): they never hold the tab stop.\n */\n items?: readonly string[];\n /**\n * Arrow key axis: `'horizontal'` uses Left/Right (mirrored in RTL), `'vertical'` uses Up/Down,\n * `'both'` uses all four, in the DOM order of the items.\n *\n * `'grid'` is a two-dimensional grid: Left/Right move through the cells in reading order across\n * rows (mirrored in RTL); Up/Down go to the cell at the same index in the previous or next row,\n * else to the nearest enabled cell of that row (the earlier one on a tie), skipping rows without\n * an enabled cell, and stop at the first and last row; Home/End go to the first and last enabled\n * cell of the row, Ctrl+Home/End of the grid (whatever `homeEndKeys` says). Rows are the\n * `role=\"row\"` elements inside the container, else groups of `columns` items in DOM order. Without\n * either, `'grid'` acts as `'both'` and a development warning says so.\n * @default 'horizontal'\n */\n orientation?: 'horizontal' | 'vertical' | 'both' | 'grid';\n /**\n * With `orientation: 'grid'` and no `role=\"row\"` elements: the number of cells per row. The last\n * row may be shorter.\n */\n columns?: number;\n /**\n * Whether arrow keys wrap around at the ends.\n * @default true\n */\n loop?: boolean;\n /** Text direction. Defaults to the direction of the container at key time (`getDirection`). */\n dir?: WaveDir;\n /**\n * Selector for the item elements inside the container. A selector the browser rejects matches\n * nothing (no item is navigable or tabbable) and logs a development warning.\n * @default '[data-roving-value]'\n */\n itemSelector?: string;\n /**\n * Printable characters move focus to the next item whose text (`data-roving-text`, else its\n * text content) starts with them. A Space typed within 500 ms of another character continues\n * the search instead of activating the focused item: `containerProps.onKeyDownCapture` calls\n * `preventDefault()` on it before an item's own keydown handler runs, so that handler must skip\n * default-prevented events (`composeEventHandlers` does). Keys typed in content nested inside an\n * item (a row's action button) are not typeahead. A character typed with AltGr (which Windows\n * reports as Ctrl+Alt, e.g. Polish `ł`) is typeahead text; other Ctrl, Alt or Meta\n * combinations, Ctrl+Alt+Space included, are shortcuts and ignored. A match whose `focus()`\n * leaves focus where it was is passed over for the next item that matches.\n * @default false\n */\n typeahead?: boolean;\n /**\n * Whether Home/End move to the first/last enabled item.\n * @default true\n */\n homeEndKeys?: boolean;\n /** Which item holds the tab stop. 'active' (default): the enabled activeValue item, else the first\n * enabled item — APG Tabs/Radio/Listbox/Tree. 'last-focused': the last focused enabled item, else\n * 'active' — APG Toolbar (and the static Menu). Either way a nested composite or a control that\n * uses the arrow keys itself (text field, select, slider, spin button, editable combobox) holds\n * it only when no other item can: arrows pressed there never lead back to the other items, so\n * focusing one keeps the tab stop on the last focused other item. 'none': no item holds it\n * (every item gets tabIndex -1; a nested composite keeps its own tab stop, and one marked\n * `data-roving-transparent` joins the group's arrow order), for a composite reached only by\n * the arrow keys of an enclosing widget or by a script (a tree row's actions); the keys,\n * `focusValue`, `focusFirst` and `focusLast` keep working. */\n tabStop?: 'active' | 'last-focused' | 'none';\n /** true ⇒ the hook writes tabIndex 0/-1 onto item elements itself and assigns\n * data-roving-value=\"auto-<n>\" to items that lack one. For containers that do not render their\n * items (Toolbar children, Menu items, Tree rows). Default false (items call getTabIndex).\n * Matches that cannot take focus are no items: `input[type=hidden]` is skipped, and a control\n * hidden by CSS inside the container (`display: none` or `visibility: hidden` on it or on an\n * ancestor inside the container) is treated as disabled (skipped, never the tab stop, stamped\n * -1). A container hidden as a whole from outside keeps its items and tab stop.\n * When the container stops being one (its ref detaches, as when `containerProps` are no longer\n * spread, or moves to another element) or `manageTabIndex` turns false, every element the hook\n * stamped gets its own `tabindex` back (the attribute it had at its first stamp, or none; a\n * `tabIndex` its author rendered since, the commit that detaches the container included, stays)\n * and loses a `data-roving-value` the hook added (one React rendered stays). An element that\n * stops being an item (`itemSelector`, or a changed selector, no longer matches it) is handed\n * back the same way. The last focused item and the generated values are kept, so a container\n * that attaches again starts where it was. */\n manageTabIndex?: boolean;\n /** Called after an arrow, Home/End or typeahead key moved focus to `value`. */\n onFocusMove?: (value: string, event: React.KeyboardEvent) => void;\n}\n\n/** Props to spread onto the container element. */\nexport interface RovingContainerProps {\n ref: React.RefCallback<HTMLElement>;\n 'data-roving-container': '';\n onKeyDown: React.KeyboardEventHandler;\n /** With `typeahead`: default-prevents a Space that continues a search, before item handlers. */\n onKeyDownCapture: React.KeyboardEventHandler;\n onFocus: React.FocusEventHandler;\n}\n\n/** Result of {@link useRovingTabIndex}. */\nexport interface UseRovingTabIndexResult {\n /** Spread onto the container: ref, `data-roving-container` marker and the key/focus handlers. */\n containerProps: RovingContainerProps;\n /** The container's keydown handler (same function as `containerProps.onKeyDown`). */\n handleKeyDown: (e: React.KeyboardEvent) => void;\n /**\n * The container's capture-phase keydown handler (same function as\n * `containerProps.onKeyDownCapture`). With `typeahead`, it extends the search with a Space that\n * continues one and calls `preventDefault()`, before an item's own keydown handler runs: an item\n * that activates on Space must skip default-prevented events (`composeEventHandlers` does).\n */\n handleKeyDownCapture: (e: React.KeyboardEvent) => void;\n /** The container's focus handler (same function as `containerProps.onFocus`). */\n handleFocus: (e: React.FocusEvent) => void;\n /** `0` for the item that holds the tab stop, `-1` for every other item. */\n getTabIndex: (value: string) => 0 | -1;\n /** The value of the item that last received focus, or `null`. */\n focusedValue: string | null;\n /** Focuses the item with this value (ignored when it is disabled or unknown). */\n focusValue: (value: string) => void;\n /** Focuses the first enabled item. */\n focusFirst: () => void;\n /** Focuses the last enabled item. */\n focusLast: () => void;\n}\n\n/** The rows of a grid: the items of the `role=\"row\"` elements, else groups of `columns` items. */\nfunction groupIntoRows(\n ordered: readonly ResolvedItem[],\n container: HTMLElement,\n columns: number | undefined,\n): ResolvedItem[][] | null {\n const rowElements = container.querySelectorAll<HTMLElement>('[role=\"row\"]');\n if (rowElements.length > 0) {\n return Array.from(rowElements).map((row) =>\n ordered.filter((item) => row.contains(item.element)),\n );\n }\n if (columns !== undefined && columns > 0) {\n const rows: ResolvedItem[][] = [];\n for (let start = 0; start < ordered.length; start += columns) {\n rows.push(ordered.slice(start, start + columns));\n }\n return rows;\n }\n return null;\n}\n\n/**\n * Where a grid key (Up/Down, Home/End, Ctrl+Home/End) goes from `current`: the cells to try, in\n * order; `'stay'` when the key is the grid's but nothing is further that way; `undefined` when it is\n * not a grid key, or focus is not on a cell of a row (the container's own handling applies).\n */\nfunction gridTargets(\n e: React.KeyboardEvent,\n rows: readonly ResolvedItem[][],\n current: ResolvedItem | null | undefined,\n): ResolvedItem[] | 'stay' | undefined {\n const vertical = e.key === 'ArrowUp' || e.key === 'ArrowDown';\n const homeEnd = e.key === 'Home' || e.key === 'End';\n if (!current || (!vertical && !homeEnd)) return undefined;\n const rowIndex = rows.findIndex((row) => row.includes(current));\n if (rowIndex < 0) return undefined;\n if (homeEnd) {\n const scope = (e.ctrlKey ? rows.flat() : rows[rowIndex]).filter((item) => !item.disabled);\n const target = e.key === 'Home' ? scope[0] : scope[scope.length - 1];\n return target && target !== current ? [target] : 'stay';\n }\n // The column is the index in the row, counting every cell (disabled ones included).\n const column = rows[rowIndex].indexOf(current);\n const delta = e.key === 'ArrowDown' ? 1 : -1;\n for (let r = rowIndex + delta; r >= 0 && r < rows.length; r += delta) {\n let best: ResolvedItem | undefined;\n let bestDistance = Infinity;\n rows[r].forEach((item, index) => {\n if (item.disabled) return;\n const distance = Math.abs(index - column);\n if (distance < bestDistance) {\n best = item;\n bestDistance = distance;\n }\n });\n if (best) return [best];\n }\n return 'stay';\n}\n\ninterface ResolvedItem {\n value: string;\n /** The item element (for a nested composite: its root). */\n element: HTMLElement;\n /** A nested composite (own roving container or composite role) counts as one item. */\n nested: boolean;\n /**\n * Skipped by the keys and never the tab stop: disabled, a nested composite with nothing to\n * focus, or (`manageTabIndex`) hidden by CSS.\n */\n disabled: boolean;\n}\n\ninterface StoreOptions {\n itemSelector: string;\n manageTabIndex: boolean;\n items: readonly string[] | undefined;\n activeValue: string | null;\n tabStop: 'active' | 'last-focused' | 'none';\n /**\n * Whether an element with a composite role but no roving container of its own is a nested\n * composite (one item). Off in the 0.4 call shape and with explicit `items`, where such an element\n * is usually the widget's own root inside a wrapper that carries the container ref.\n */\n roleComposites: boolean;\n}\n\nconst DEFAULT_ITEM_SELECTOR = '[data-roving-value]';\n\n/** Roles of widgets that manage their own children: an element with one of them is one item. */\nconst COMPOSITE_ROLES = new Set([\n 'radiogroup',\n 'listbox',\n 'grid',\n 'treegrid',\n 'tablist',\n 'menu',\n 'menubar',\n 'tree',\n 'spinbutton',\n]);\n\n/**\n * Roles whose widgets use the arrow keys themselves. A combobox does only when it is editable (an\n * `<input>` or contenteditable, caught by the native checks): a select-only combobox such as\n * Dropdown's `<button role=\"combobox\">` uses Up/Down, Home and End, never Left/Right.\n */\nconst ARROW_KEY_OWNER_ROLES = new Set(['slider', 'spinbutton', 'textbox', 'searchbox']);\n\n/** `<input>` types that do not use the arrow keys for text editing. */\nconst NON_TEXT_INPUT_TYPES = new Set([\n 'button',\n 'checkbox',\n 'color',\n 'file',\n 'hidden',\n 'image',\n 'radio',\n 'reset',\n 'submit',\n]);\n\nconst OBSERVED_ATTRIBUTES = [\n 'disabled',\n 'aria-disabled',\n 'data-disabled-focusable',\n 'data-roving-disabled',\n 'data-roving-value',\n 'data-roving-container',\n 'data-roving-transparent',\n 'tabindex',\n 'hidden',\n 'inert',\n 'role',\n // Whether an item uses the arrow keys itself (text input types, contenteditable).\n 'type',\n 'contenteditable',\n];\n\n/**\n * Whether `el` is a nested composite: it has its own roving container, or (with `roleComposites`)\n * a composite role. An element marked `data-roving-transparent` is never one by its role (a\n * `role=\"radiogroup\"` whose radios are items of the enclosing container, `Toolbar.RadioGroup`);\n * its own `data-roving-container` still makes it one.\n */\nfunction isNestedComposite(el: Element, roleComposites: boolean): boolean {\n if (el.hasAttribute('data-roving-container')) return true;\n if (!roleComposites || el.hasAttribute('data-roving-transparent')) return false;\n const role = el.getAttribute('role');\n return role !== null && COMPOSITE_ROLES.has(role);\n}\n\n/** Whether `el` is a nested composite or inside one, below `container`. */\nfunction isInNestedComposite(el: Element, container: Element, roleComposites: boolean): boolean {\n for (let node: Element | null = el; node && node !== container; node = node.parentElement) {\n if (isNestedComposite(node, roleComposites)) return true;\n }\n return false;\n}\n\n/** What the hook knows of an element it stamped, to hand it back to its author. */\ninterface StampedOriginal {\n /**\n * The author's own `tabindex` attribute, or `null`: read at the first stamp, and replaced by the\n * one the author writes while the element is stamped (React rendering another `tabIndex`; the\n * next stamp overwrites it in the DOM).\n */\n tabIndex: string | null;\n /** The `tabindex` the hook left on the element at its last stamp. */\n stamped: string;\n /** The `data-roving-value` the hook added (a generated value), or `null` when it added none. */\n addedValue: string | null;\n}\n\n/**\n * Every element the hook stamps carries both attributes, whatever `itemSelector` matched it: the\n * hand-back finds its elements through them, also one the selector no longer matches.\n */\nconst STAMPED_SELECTOR = '[tabindex], [data-roving-value]';\n\nfunction matchesSelector(el: Element, selector: string): boolean {\n try {\n return el.matches(selector);\n } catch {\n // A selector the browser rejects matches nothing.\n return false;\n }\n}\n\n/**\n * The elements whose last `tabindex` write in `records` left the value it found: the author\n * (through React) wrote the value the stamp had left. The hook only writes to change a value, so\n * such a write is never its own. Needs records with `attributeOldValue`.\n */\nfunction rewrittenTabIndex(records: readonly MutationRecord[]): Set<Node> {\n const last = new Map<Node, MutationRecord>();\n for (const record of records) {\n if (record.type === 'attributes' && record.attributeName === 'tabindex') {\n last.set(record.target, record);\n }\n }\n const rewritten = new Set<Node>();\n for (const [target, record] of last) {\n if (record.oldValue === (target as Element).getAttribute('tabindex')) rewritten.add(target);\n }\n return rewritten;\n}\n\nfunction isFocusableSelf(el: HTMLElement): boolean {\n return el.tabIndex >= 0 && !el.hasAttribute('disabled');\n}\n\n/** The element to focus for an item: the item itself, or a nested composite's own tab stop. */\nfunction getFocusTarget(item: ResolvedItem): HTMLElement | null {\n const { element } = item;\n if (!item.nested || isFocusableSelf(element)) return element;\n const ownStop = element.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (ownStop) return ownStop;\n const tabbable = getFirstTabbable(element);\n return tabbable instanceof HTMLElement ? tabbable : null;\n}\n\n/**\n * Whether the keys skip an item, in this order: the composite's own `data-roving-disabled` marker\n * (not `\"false\"`) and native `disabled` skip it; `data-disabled-focusable` keeps it (a\n * `disabledFocusable` control: APG allows focusable disabled items); `aria-disabled=\"true\"` skips\n * it.\n */\nfunction isDisabledElement(el: HTMLElement): boolean {\n const marker = el.getAttribute('data-roving-disabled');\n if (marker !== null && marker !== 'false') return true;\n if (isNativelyDisabled(el)) return true;\n if (el.hasAttribute('data-disabled-focusable')) return false;\n return el.getAttribute('aria-disabled') === 'true';\n}\n\nfunction isNativelyDisabled(el: HTMLElement): boolean {\n if ((el as { disabled?: unknown }).disabled === true) return true;\n try {\n return el.matches(':disabled');\n } catch {\n return false;\n }\n}\n\n/**\n * Text-entry fields and widgets that use the arrow keys themselves (APG Toolbar).\n *\n * Internal: shared with the windowed List's keys, which leave the same keys alone; not exported\n * from the package.\n */\nexport function ownsArrowKeys(target: EventTarget | null): boolean {\n if (!target || (target as Node).nodeType !== 1) return false;\n const el = target as HTMLElement;\n const role = el.getAttribute('role');\n if (role !== null && ARROW_KEY_OWNER_ROLES.has(role)) return true;\n const tag = el.localName;\n if (tag === 'textarea' || tag === 'select') return true;\n if (tag === 'input') return !NON_TEXT_INPUT_TYPES.has((el as HTMLInputElement).type);\n const editable = el.closest('[contenteditable]');\n return editable !== null && editable.getAttribute('contenteditable') !== 'false';\n}\n\n/**\n * Whether an item can hold the container's tab stop: not a nested composite (it keeps a tab stop\n * of its own) and not a control that uses the arrow keys itself. From either, the arrow keys never\n * lead to the other items, so a tab stop there would leave them reachable by keyboard only through\n * it (or not at all).\n */\nfunction canHoldTabStop(item: ResolvedItem): boolean {\n return !item.nested && !ownsArrowKeys(item.element);\n}\n\n/**\n * Whether CSS inside `container` keeps `el` from taking focus (browsers ignore `focus()` on it):\n * `display: none` on it or on an ancestor inside `container`, `visibility: hidden`/`collapse` set\n * inside `container`, or a place inside a closed `<details>` other than its summary. Hiding from\n * outside the container (a surface that stays invisible until it is positioned, a consumer panel)\n * hides the whole item set and does not count: showing it again changes nothing the hook observes,\n * so nothing would re-stamp. `cache` holds, for one resolution, whether an element hides its whole\n * subtree (the `display` and `<details>` checks).\n *\n * Internal: shared with Tree's treegrid navigation; not exported from the package.\n */\nexport function isHiddenByCss(\n el: Element,\n container: Element,\n cache: Map<Element, boolean>,\n): boolean {\n const view = el.ownerDocument.defaultView;\n if (!view) return false;\n for (let node: Element | null = el; node && node !== container; node = node.parentElement) {\n let hidden = cache.get(node);\n if (hidden === undefined) {\n const parent = node.parentElement;\n hidden =\n view.getComputedStyle(node).display === 'none' ||\n (parent !== null &&\n parent.localName === 'details' &&\n !parent.hasAttribute('open') &&\n Array.from(parent.children).find((child) => child.localName === 'summary') !== node);\n cache.set(node, hidden);\n }\n if (hidden) return true;\n }\n const { visibility } = view.getComputedStyle(el);\n if (visibility !== 'hidden' && visibility !== 'collapse') return false;\n // Visibility is inherited, so `el` also reads hidden when an ancestor of the container hides the\n // whole container. It counts only when the container itself is visible, i.e. when the hiding is\n // set inside it (on `el` or on a wrapper between them).\n return view.getComputedStyle(container).visibility === 'visible';\n}\n\n/** Whether focus is on `el` or inside it (in its document or shadow root). */\nfunction hasFocus(el: HTMLElement): boolean {\n const active = (el.getRootNode() as Node & { activeElement?: Element | null }).activeElement;\n return !!active && el.contains(active);\n}\n\nfunction isValidSelector(selector: string): boolean {\n try {\n document.createDocumentFragment().querySelector(selector);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * How an item takes part in the tab stop: `0` never (disabled), `1` can hold it, `2` holds it only\n * when no item can (a nested composite, or a control that uses the arrow keys itself).\n */\ntype TabStopRank = 0 | 1 | 2;\n\n/** One item of the published DOM snapshot, and the input of {@link resolveTabStop}. */\ntype SnapshotEntry = [value: string, rank: TabStopRank];\n\nfunction toEntry(item: ResolvedItem): SnapshotEntry {\n return [item.value, item.disabled ? 0 : canHoldTabStop(item) ? 1 : 2];\n}\n\n/**\n * The value that holds the tab stop, among the items of the best rank present (`1`, else `2`):\n * with 'last-focused' the last focused one, else the `activeValue` item, else the first one.\n */\nfunction resolveTabStop(\n entries: readonly SnapshotEntry[],\n activeValue: string | null,\n tabStop: 'active' | 'last-focused',\n lastFocused: string | null,\n): string | null {\n const rank = entries.some(([, r]) => r === 1) ? 1 : 2;\n const values = entries.filter(([, r]) => r === rank).map(([value]) => value);\n if (tabStop === 'last-focused' && lastFocused !== null && values.includes(lastFocused)) {\n return lastFocused;\n }\n if (activeValue !== null && activeValue !== '' && values.includes(activeValue)) {\n return activeValue;\n }\n return values[0] ?? null;\n}\n\nfunction orderByItems(\n resolved: ResolvedItem[],\n items: readonly string[] | undefined,\n): ResolvedItem[] {\n if (!items) return resolved;\n const byValue = new Map(resolved.map((item) => [item.value, item]));\n const ordered: ResolvedItem[] = [];\n for (const value of items) {\n const item = byValue.get(value);\n if (item) ordered.push(item);\n }\n return ordered;\n}\n\n/**\n * Per-hook store: resolves items from the DOM, publishes the enabled set to React through\n * `useSyncExternalStore` (a MutationObserver on the container notifies it), and stamps tabIndex in\n * `manageTabIndex` mode.\n */\nclass RovingStore {\n container: HTMLElement | null = null;\n lastFocused: string | null = null;\n options: StoreOptions = {\n itemSelector: DEFAULT_ITEM_SELECTOR,\n manageTabIndex: false,\n items: undefined,\n activeValue: null,\n tabStop: 'active',\n roleComposites: true,\n };\n\n private readonly stamped = new WeakMap<Element, StampedOriginal>();\n private readonly autoValues = new WeakMap<Element, string>();\n private autoCounter = 0;\n private notify: (() => void) | null = null;\n private observer: MutationObserver | null = null;\n private dirty = true;\n private snapshot = '';\n\n subscribe = (notify: () => void): (() => void) => {\n this.notify = notify;\n this.observe();\n return () => {\n this.notify = null;\n this.disconnect();\n };\n };\n\n getSnapshot = (): string => {\n if (!this.container) return '';\n if (this.dirty) {\n this.dirty = false;\n // DOM facts only (order and tab stop rank); `items` is applied during render.\n this.snapshot = JSON.stringify(this.resolveOwn().map(toEntry));\n }\n return this.snapshot;\n };\n\n getServerSnapshot = (): string => '';\n\n /**\n * Attaches the store to `container` (`null` detaches it). A container that stops being one (its\n * ref detached, or the store moved to another element) first hands every element it stamped back\n * to its author, with the writes its observer has not delivered yet (the `tabIndex` React\n * rendered in the commit that detaches it). Only a store that manages tabIndex has stamped\n * anything: `setOptions` hands the stamps back the moment `manageTabIndex` turns off.\n */\n setContainer(container: HTMLElement | null): void {\n if (container === this.container) return;\n // Disconnecting drops the pending records anyway; the hand-back reads them first.\n const records = this.takeRecords();\n this.disconnect();\n if (this.container && this.options.manageTabIndex) {\n this.release(this.container, 'all', records);\n }\n this.container = container;\n this.dirty = true;\n if (this.notify) {\n this.observe();\n this.notify();\n }\n }\n\n /**\n * Records a focused item for the 'last-focused' tab stop. An item that cannot hold the tab stop\n * (a nested composite, a control that uses the arrow keys itself) leaves the last one in place.\n */\n recordFocus(item: ResolvedItem): void {\n if (canHoldTabStop(item)) this.lastFocused = item.value;\n }\n\n setOptions(options: StoreOptions): void {\n const previous = this.options;\n this.options = options;\n // `manageTabIndex` turned off: the elements stamped so far go back to their author. The records\n // taken here are not delivered; the notification below stands in for them. (A changed\n // `itemSelector` hands back what it no longer matches at the next stamp.)\n if (previous.manageTabIndex && !options.manageTabIndex && this.container) {\n this.release(this.container, 'all', this.takeRecords());\n }\n if (\n previous.itemSelector !== options.itemSelector ||\n previous.manageTabIndex !== options.manageTabIndex ||\n previous.roleComposites !== options.roleComposites\n ) {\n this.dirty = true;\n this.notify?.();\n }\n }\n\n /** Own items in navigation order: the explicit `items` order when given, else DOM order. */\n resolve(container: HTMLElement | null = this.container): ResolvedItem[] {\n return orderByItems(this.resolveOwn(container), this.options.items);\n }\n\n /** Own items of the container in DOM order (items of nested composites excluded). */\n resolveOwn(container: HTMLElement | null = this.container): ResolvedItem[] {\n if (!container) return [];\n const { itemSelector, manageTabIndex, roleComposites } = this.options;\n let candidates: HTMLElement[];\n try {\n candidates = Array.from(container.querySelectorAll<HTMLElement>(itemSelector));\n } catch {\n // An invalid selector (warned from an effect in development): this runs during render\n // (getSnapshot), where throwing would take the whole tree down.\n return [];\n }\n\n const result: ResolvedItem[] = [];\n const seen = new Set<Element>();\n const cssHidden = new Map<Element, boolean>();\n for (const candidate of candidates) {\n let composite: HTMLElement | null = null;\n let hidden = false;\n for (\n let node: HTMLElement | null = candidate;\n node && node !== container;\n node = node.parentElement\n ) {\n if (isNestedComposite(node, roleComposites)) composite = node;\n if (node.hidden || node.hasAttribute('inert')) hidden = true;\n }\n if (hidden) continue;\n\n const element = composite ?? candidate;\n if (seen.has(element)) continue;\n const nested = composite !== null;\n if (!nested && manageTabIndex) {\n // An author tabindex=\"-1\" (a SpinButton stepper, SplitButton internals) opts out.\n if (candidate.getAttribute('tabindex') === '-1' && !this.stamped.has(candidate)) continue;\n // A hidden form input (HiddenInput's `type=\"hidden\"`) is never focusable.\n if (isHiddenInput(candidate)) continue;\n }\n seen.add(element);\n\n const item: ResolvedItem = {\n value: this.valueOf(element, nested),\n element,\n nested,\n disabled: isDisabledElement(element),\n };\n if (nested && !item.disabled && !getFocusTarget(item)) item.disabled = true;\n // Consumer children hidden by CSS (a responsive `hidden md:inline-flex` control). Treated as\n // disabled rather than dropped, so it is stamped -1 and is no extra Tab stop once the CSS\n // shows it again (a CSS change triggers no mutation, hence no re-stamp).\n if (manageTabIndex && !item.disabled && isHiddenByCss(element, container, cssHidden)) {\n item.disabled = true;\n }\n result.push(item);\n }\n return result;\n }\n\n /**\n * manageTabIndex: writes tabIndex 0 on the tab stop and -1 on every other own item. An element\n * it stamped that is no longer one of its items (it now belongs to a nested composite, as when a\n * `data-roving-transparent` marker is removed, or `itemSelector` no longer matches it) is handed\n * back first. A `tabindex` the author rendered since the last stamp becomes the element's own.\n */\n stamp(): void {\n if (!this.options.manageTabIndex || !this.container) return;\n this.release(this.container, 'stale');\n const resolved = this.resolve();\n const stop =\n this.options.tabStop === 'none'\n ? null\n : resolveTabStop(\n resolved.map(toEntry),\n this.options.activeValue,\n this.options.tabStop,\n this.lastFocused,\n );\n for (const item of resolved) {\n if (item.nested) continue;\n const { element } = item;\n const tabIndex = item.value === stop ? '0' : '-1';\n const current = element.getAttribute('tabindex');\n let original = this.stamped.get(element);\n if (!original) {\n original = { tabIndex: current, stamped: tabIndex, addedValue: null };\n this.stamped.set(element, original);\n } else {\n // Another value than the last stamp left: the author rendered it, so it is the element's own.\n if (current !== original.stamped) original.tabIndex = current;\n original.stamped = tabIndex;\n }\n if (current !== tabIndex) element.setAttribute('tabindex', tabIndex);\n if (!element.hasAttribute('data-roving-value')) {\n element.setAttribute('data-roving-value', item.value);\n original.addedValue = item.value;\n }\n }\n }\n\n /**\n * Hands elements it stamped inside `container` back to their author and drops their records, so\n * a later stamp reads them afresh. The `tabindex` the record holds as the author's is put back\n * (removed when there was none), unless the author wrote one since the last stamp, which stays:\n * another value than the stamp's, or the stamp's value written again (seen in the pending\n * `records`). A `data-roving-value` the hook added is removed while it still holds the generated\n * value (one React renders stays). The generated values and the last focused item are kept: a\n * container that attaches again starts where it was. `'stale'`: only the elements that are no\n * longer its items, because they belong to a nested composite or `itemSelector` no longer matches\n * them (every stamp); `'all'`: every element (the container detaches or is replaced, or\n * `manageTabIndex` turns off).\n */\n private release(\n container: HTMLElement,\n scope: 'stale' | 'all',\n records: readonly MutationRecord[] = [],\n ): void {\n const rewritten = rewrittenTabIndex(records);\n const { itemSelector, roleComposites } = this.options;\n for (const element of Array.from(container.querySelectorAll<HTMLElement>(STAMPED_SELECTOR))) {\n const original = this.stamped.get(element);\n if (!original) continue;\n if (\n scope === 'stale' &&\n matchesSelector(element, itemSelector) &&\n !isInNestedComposite(element, container, roleComposites)\n ) {\n continue;\n }\n this.stamped.delete(element);\n const current = element.getAttribute('tabindex');\n const authorWrote = current !== original.stamped || rewritten.has(element);\n // Written only to change the value: the hook's own writes never keep one (`rewrittenTabIndex`).\n if (!authorWrote && current !== original.tabIndex) {\n if (original.tabIndex === null) element.removeAttribute('tabindex');\n else element.setAttribute('tabindex', original.tabIndex);\n }\n if (\n original.addedValue !== null &&\n element.getAttribute('data-roving-value') === original.addedValue\n ) {\n element.removeAttribute('data-roving-value');\n }\n }\n }\n\n /**\n * Takes over, as the element's own, a `tabindex` the author wrote with the value the stamp had\n * left (`rewrittenTabIndex`): the next stamp cannot tell it from its own. Another value is taken\n * over by the next stamp.\n */\n private adopt(records: readonly MutationRecord[]): void {\n for (const target of rewrittenTabIndex(records)) {\n const original = this.stamped.get(target as Element);\n if (original) original.tabIndex = (target as Element).getAttribute('tabindex');\n }\n }\n\n /** The records the observer has not delivered yet; it delivers none of them afterwards. */\n private takeRecords(): MutationRecord[] {\n return this.observer?.takeRecords() ?? [];\n }\n\n private valueOf(element: HTMLElement, nested: boolean): string {\n const own = nested ? null : element.getAttribute('data-roving-value');\n if (own !== null) return own;\n let generated = this.autoValues.get(element);\n if (generated === undefined) {\n this.autoCounter += 1;\n generated = `auto-${this.autoCounter}`;\n this.autoValues.set(element, generated);\n }\n return generated;\n }\n\n private observe(): void {\n if (!this.container || this.observer || typeof MutationObserver === 'undefined') return;\n this.observer = new MutationObserver((records) => {\n this.adopt(records);\n this.stamp();\n this.dirty = true;\n this.notify?.();\n });\n this.observer.observe(this.container, {\n subtree: true,\n childList: true,\n attributes: true,\n // With the old value, a write that keeps a value shows as the author's (`rewrittenTabIndex`).\n attributeOldValue: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n this.dirty = true;\n }\n\n private disconnect(): void {\n this.observer?.disconnect();\n this.observer = null;\n }\n}\n\nfunction parseSnapshot(snapshot: string): SnapshotEntry[] | null {\n if (snapshot === '') return null;\n try {\n return JSON.parse(snapshot) as SnapshotEntry[];\n } catch {\n return null;\n }\n}\n\nfunction textOf(element: HTMLElement): string {\n return element.getAttribute('data-roving-text') ?? element.textContent ?? '';\n}\n\n/**\n * Whether an event target is the container or inside its DOM. React bubbles events from a portal\n * (a Menu.Popover or Popover.Content a Toolbar item renders) through the container although their\n * target is elsewhere in the document; those events are not the container's to handle.\n */\nfunction isInContainer(container: Node, target: EventTarget | null): boolean {\n if (!target || typeof (target as Node).nodeType !== 'number') return false;\n return target === container || container.contains(target as Node);\n}\n\n/**\n * The item an event started in: the innermost item element that is the target or one of its\n * ancestors (up to the container). Items can nest (an APG treeitem contains the group of its child\n * treeitems), so the first containing item in DOM order would be the outermost ancestor.\n */\nfunction findOwningItem(\n items: readonly ResolvedItem[],\n target: EventTarget | null,\n container: Node,\n): ResolvedItem | null {\n if (items.length === 0 || !target || typeof (target as Node).nodeType !== 'number') return null;\n const byElement = new Map<Node, ResolvedItem>(items.map((item) => [item.element, item]));\n for (let node: Node | null = target as Node; node && node !== container; node = node.parentNode) {\n const item = byElement.get(node);\n if (item) return item;\n }\n return null;\n}\n\n/**\n * Whether a key started on the item itself (on the container when it started on no item), not on\n * content nested inside the item, such as a row's action button.\n */\nfunction startsOnItem(item: ResolvedItem | null, target: EventTarget | null, container: Node) {\n return item ? target === getFocusTarget(item) : target === container;\n}\n\n/**\n * Implements the WAI-ARIA roving tabindex pattern for composite widgets: one item holds the tab\n * stop (`tabIndex=0`, all others `-1`), arrow keys move focus between items, Home/End jump to the\n * ends, and (optionally) typeahead jumps by text. Used by RadioGroup, Rating, SwatchPicker, List,\n * TabList, Tree, Menu and Toolbar.\n *\n * - **Items** come from the DOM at event time: elements matching `itemSelector` (default\n * `[data-roving-value]`) whose nearest roving container is this one. A nested composite (an\n * element with its own `data-roving-container`, or a radiogroup/listbox/grid/tablist/menu/tree/\n * spinbutton role) counts as one item whose focus target is its own tab stop; the hook never\n * writes tabindex inside it, so it keeps its own Tab stop. An element marked\n * `data-roving-transparent` is never a nested composite by its role: its items are items of this\n * container (the radios of `Toolbar.RadioGroup`); it must not carry a `data-roving-container`.\n * Pass `items` to fix the order instead.\n * In the 0.4 call shape and with explicit `items`, only elements with their own roving container\n * are nested composites: a role-only `radiogroup`/`tablist`/… element between the container and\n * the items (the widget's root inside a wrapper that holds the ref) does not swallow them.\n * - **Disabled items** (`data-roving-disabled`, `disabled`, `aria-disabled=\"true\"`) are skipped and\n * never hold the tab stop. Items marked `data-disabled-focusable` (the `disabledFocusable` prop\n * of WaveUI buttons, links and choice controls) stay reachable and may hold the tab stop: APG\n * allows focusable disabled items. The attribute only overrides `aria-disabled`: an item that is\n * natively disabled or marked `data-roving-disabled` is still skipped. The enabled set is\n * tracked with a MutationObserver, so an item that disables itself moves the tab stop without\n * the owner re-rendering. With `manageTabIndex`, a control hidden by CSS counts as disabled and\n * `input[type=hidden]` is no item.\n * - **Hand-back** (`manageTabIndex`): a container that stops being one (its ref detaches or moves\n * to another element) or that stops managing tabIndex gives every element it stamped its own\n * `tabindex` back (the one it had, or the one its author rendered since) and removes the\n * `data-roving-value` it added, so each control is its own Tab stop again; attached again, it\n * stamps again from the last focused item. An element `itemSelector` no longer matches is handed\n * back at once.\n * - **Tab stop** (`tabStop`): `'active'` — the enabled `activeValue` item, else the first enabled\n * item; `'last-focused'` — the last focused enabled item, else the `'active'` rule. A nested\n * composite or a control that uses the arrow keys itself holds it only when no other item can;\n * `'none'` — no item: every item is `tabIndex=-1` (a nested composite keeps its own tab stop;\n * mark it `data-roving-transparent` to join the group's arrow order), and focus reaches the\n * items only through the keys or a script.\n * - **Keys** are ignored when another handler already called `preventDefault()`; with Meta; with\n * Alt or Ctrl, except a single character typed with AltGr (Ctrl+Alt on Windows), which is\n * typeahead text (arrows, Home, End and Space with Ctrl+Alt stay ignored); and when they start\n * in a text field, select, contenteditable, slider, spinbutton or editable combobox (Left/Right\n * keep moving the caret; a select-only combobox such as Dropdown's button does not keep them).\n * Left/Right are mirrored in RTL (`dir`, else the direction of the container at key time).\n * Arrows and typeahead move from the item the key started in — the innermost one when items\n * nest, as treeitems do inside their parent's group; when it started on no item (focus on the\n * container itself) next goes to the first enabled item and prev to the last.\n * An item whose `focus()` leaves focus where it was (the browser ignores it on an element CSS\n * hides) is passed over for the next one, and is never recorded or reported to `onFocusMove`.\n * Handled keys call `preventDefault()`. Keys and focus from outside the container's DOM — a\n * popup an item renders through a portal, whose events React bubbles through the container —\n * are ignored, so focus never leaves an open menu or popover for the container's items.\n *\n * @example\n * const { containerProps, getTabIndex } = useRovingTabIndex({ activeValue: value, orientation: 'both' });\n * <div role=\"radiogroup\" {...containerProps}>\n * <button role=\"radio\" data-roving-value=\"a\" tabIndex={getTabIndex('a')} />\n * </div>\n *\n * The 0.4 call shape `useRovingTabIndex(containerRef, options)` still works: pass the container's\n * ref object and attach `handleKeyDown` (and ideally `handleFocus`, and with `typeahead`\n * `handleKeyDownCapture` as `onKeyDownCapture`) yourself.\n *\n * @param options - See {@link UseRovingTabIndexOptions}.\n * @returns See {@link UseRovingTabIndexResult}.\n */\nexport function useRovingTabIndex(options: UseRovingTabIndexOptions): UseRovingTabIndexResult;\nexport function useRovingTabIndex(\n containerRef: React.RefObject<HTMLElement | null>,\n options: UseRovingTabIndexOptions,\n): UseRovingTabIndexResult;\nexport function useRovingTabIndex(\n refOrOptions: React.RefObject<HTMLElement | null> | UseRovingTabIndexOptions,\n maybeOptions?: UseRovingTabIndexOptions,\n): UseRovingTabIndexResult {\n const legacyRef =\n maybeOptions !== undefined ? (refOrOptions as React.RefObject<HTMLElement | null>) : undefined;\n const options = (maybeOptions ?? refOrOptions) as UseRovingTabIndexOptions;\n const {\n activeValue = null,\n items,\n orientation = 'horizontal',\n columns,\n loop = true,\n dir,\n itemSelector = DEFAULT_ITEM_SELECTOR,\n typeahead = false,\n homeEndKeys = true,\n tabStop = 'active',\n manageTabIndex = false,\n onFocusMove,\n } = options;\n\n const [store] = React.useState(() => new RovingStore());\n const snapshot = React.useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n );\n const [focusedValue, setFocusedValue] = React.useState<string | null>(null);\n // The last focused item that can hold the tab stop ('last-focused' during render).\n const [focusedStop, setFocusedStop] = React.useState<string | null>(null);\n\n // 0.4 call shape or explicit `items`: only elements with their own roving container are nested\n // composites (a role=radiogroup/tablist/… around the items is the widget's own root).\n const roleComposites = legacyRef === undefined && items === undefined;\n\n React.useLayoutEffect(() => {\n store.setOptions({ itemSelector, manageTabIndex, items, activeValue, tabStop, roleComposites });\n if (legacyRef) store.setContainer(legacyRef.current);\n store.stamp();\n });\n\n // C-DEV: `resolveOwn` treats a selector the browser rejects as \"no items\" (it runs during render,\n // where it must not throw), so the developer hears about it here.\n React.useEffect(() => {\n if (isDev && !isValidSelector(itemSelector)) {\n warnOnce(\n `useRovingTabIndex:itemSelector:${itemSelector}`,\n `useRovingTabIndex: itemSelector \"${itemSelector}\" is not a valid CSS selector in this browser; no item is navigable or tabbable.`,\n );\n }\n }, [itemSelector]);\n\n // C-DEV: a grid needs rows, from role=\"row\" elements or a `columns` count.\n React.useEffect(() => {\n if (!isDev || orientation !== 'grid' || columns) return;\n const container = store.container;\n if (container && container.querySelector('[role=\"row\"]') === null) {\n warnOnce(\n 'useRovingTabIndex:grid-rows',\n 'useRovingTabIndex: orientation \"grid\" needs role=\"row\" elements around the items or a `columns` count; without either it acts as \"both\".',\n );\n }\n });\n\n // Tab stop during render, from the DOM snapshot (null before the container is known).\n const domItems = React.useMemo(() => parseSnapshot(snapshot), [snapshot]);\n const stopEntries = React.useMemo<readonly SnapshotEntry[] | null>(() => {\n if (!items) return domItems;\n // Explicit `items`: the listed values the DOM has, in the listed order. Every listed value\n // before the container is known, or while the DOM has none of them yet (items rendered in the\n // commit the snapshot has not caught up with).\n if (domItems) {\n const ranks = new Map(domItems);\n const present = items.flatMap((value): SnapshotEntry[] => {\n const rank = ranks.get(value);\n return rank === undefined ? [] : [[value, rank]];\n });\n if (present.length > 0) return present;\n }\n return items.map((value): SnapshotEntry => [value, 1]);\n }, [items, domItems]);\n const tabStopValue =\n tabStop === 'none'\n ? null\n : stopEntries\n ? resolveTabStop(stopEntries, activeValue, tabStop, focusedStop)\n : activeValue || null;\n\n const getTabIndex = React.useCallback(\n (value: string): 0 | -1 => (value === tabStopValue ? 0 : -1),\n [tabStopValue],\n );\n\n const containerRef = React.useCallback(\n (node: HTMLElement | null) => {\n store.setContainer(node);\n if (legacyRef) setRef(legacyRef, node);\n },\n [store, legacyRef],\n );\n\n const typeaheadItemsRef = React.useRef<TypeaheadItem[]>([]);\n // Every item matching the typed characters, in search order (the first is the match).\n const typeaheadMatchesRef = React.useRef<readonly string[]>([]);\n const { onTypeahead, isSearching } = useTypeahead({\n getItems: () => typeaheadItemsRef.current,\n onMatch: (_value, matches) => {\n typeaheadMatchesRef.current = matches;\n },\n });\n\n /** Focuses an item; `true` only when focus actually moved there (then it is recorded). */\n const focusItem = (item: ResolvedItem | undefined): boolean => {\n if (!item || item.disabled) return false;\n const target = getFocusTarget(item);\n if (!target) return false;\n target.focus();\n // Browsers ignore focus() on an element CSS hides (display: none, visibility: hidden).\n if (!hasFocus(target)) return false;\n store.recordFocus(item);\n return true;\n };\n\n /** Focuses the first item of `candidates` that takes focus. */\n const focusFirstOf = (candidates: readonly ResolvedItem[]): void => {\n for (const item of candidates) {\n if (focusItem(item)) return;\n }\n };\n\n // `spaceOnly` (the capture phase) handles nothing but a Space that continues a typeahead search.\n const handleKeys = useEventCallback((e: React.KeyboardEvent, spaceOnly: boolean) => {\n // Ctrl, Alt and Meta combinations are shortcuts, except a single character typed with AltGr\n // (Ctrl+Alt on Windows), which is typeahead text: arrows, Home, End and Space with it stay\n // shortcuts.\n const gridCtrlHomeEnd =\n orientation === 'grid' && e.ctrlKey && !e.altKey && (e.key === 'Home' || e.key === 'End');\n const shortcut =\n e.metaKey || ((e.altKey || e.ctrlKey) && !isAltGraphCharacter(e) && !gridCtrlHomeEnd);\n if (e.defaultPrevented || shortcut) return;\n if (ownsArrowKeys(e.target)) return;\n const container = store.container ?? (e.currentTarget as HTMLElement);\n // A key from a portaled popup is the popup's: never pull focus back out of it.\n if (!isInContainer(container, e.target)) return;\n const ordered = store.resolve(container);\n const enabled = ordered.filter((item) => !item.disabled);\n if (enabled.length === 0) return;\n\n // The (innermost) item the key started in. None (the container itself has focus): next → first,\n // prev → last.\n const current = findOwningItem(ordered, e.target, container);\n\n let handled = false;\n // Where focus goes, in order of preference: an item whose focus() does not move focus (hidden\n // by CSS) passes the key on to the next one.\n let targets: ResolvedItem[] = [];\n const rows =\n orientation === 'grid' && !spaceOnly ? groupIntoRows(ordered, container, columns) : null;\n const gridMove = rows ? gridTargets(e, rows, current) : undefined;\n const intent =\n spaceOnly || gridMove\n ? null\n : getArrowIntent(e.key, {\n orientation: orientation === 'grid' ? (rows ? 'horizontal' : 'both') : orientation,\n dir: dir ?? getDirection(e.currentTarget as Element),\n });\n if (gridMove) {\n handled = true;\n targets = gridMove === 'stay' ? [] : gridMove;\n } else if (intent) {\n handled = true;\n if (!current) {\n targets = intent === 'next' ? enabled : [...enabled].reverse();\n } else {\n const step = intent === 'next' ? 1 : -1;\n const count = ordered.length;\n let index = ordered.indexOf(current);\n for (let i = 1; i < count; i++) {\n index += step;\n if (index < 0 || index >= count) {\n if (!loop) break;\n index = (index + count) % count;\n }\n if (!ordered[index].disabled) targets.push(ordered[index]);\n }\n }\n } else if (!spaceOnly && homeEndKeys && (e.key === 'Home' || e.key === 'End')) {\n handled = true;\n targets = e.key === 'Home' ? enabled : [...enabled].reverse();\n } else if (typeahead && startsOnItem(current, e.target, container)) {\n typeaheadItemsRef.current = ordered.map((item) => ({\n value: item.value,\n text: textOf(item.element),\n disabled: item.disabled,\n }));\n typeaheadMatchesRef.current = [];\n if (onTypeahead(e, current?.value ?? null)) {\n handled = true;\n // The match first, then the next ones, for a match that does not take focus.\n targets = typeaheadMatchesRef.current.flatMap((value) =>\n enabled.filter((item) => item.value === value),\n );\n }\n }\n\n if (!handled) return;\n e.preventDefault();\n for (const next of targets) {\n // Back at the item the key started on (Home on the first item): focus stays.\n if (next === current) return;\n if (focusItem(next)) {\n onFocusMove?.(next.value, e);\n return;\n }\n }\n });\n\n const handleKeyDown = useEventCallback((e: React.KeyboardEvent) => handleKeys(e, false));\n\n // A Space that continues a typeahead search belongs to the search, not to the focused item: it is\n // handled and default-prevented in the capture phase, so the item's own keydown handler, which\n // skips default-prevented events, does not activate the item.\n const handleKeyDownCapture = useEventCallback((e: React.KeyboardEvent) => {\n if (typeahead && e.key === ' ' && isSearching()) handleKeys(e, true);\n });\n\n const handleFocus = useEventCallback((e: React.FocusEvent) => {\n const container = store.container ?? (e.currentTarget as HTMLElement);\n if (!isInContainer(container, e.target)) return;\n const item = findOwningItem(store.resolve(container), e.target, container);\n if (!item) return;\n store.recordFocus(item);\n setFocusedValue(item.value);\n if (canHoldTabStop(item)) setFocusedStop(item.value);\n store.stamp();\n });\n\n const focusValue = useEventCallback((value: string) => {\n focusItem(store.resolve().find((item) => item.value === value));\n });\n const focusFirst = useEventCallback(() => {\n focusFirstOf(store.resolve().filter((item) => !item.disabled));\n });\n const focusLast = useEventCallback(() => {\n focusFirstOf(\n store\n .resolve()\n .filter((item) => !item.disabled)\n .reverse(),\n );\n });\n\n const containerProps = React.useMemo<RovingContainerProps>(\n () => ({\n ref: containerRef,\n 'data-roving-container': '',\n onKeyDown: handleKeyDown,\n onKeyDownCapture: handleKeyDownCapture,\n onFocus: handleFocus,\n }),\n [containerRef, handleKeyDown, handleKeyDownCapture, handleFocus],\n );\n\n return {\n containerProps,\n handleKeyDown,\n handleKeyDownCapture,\n handleFocus,\n getTabIndex,\n focusedValue,\n focusValue,\n focusFirst,\n focusLast,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use client";const e=require("./useControllable.cjs"),t=require("../lib/selection.cjs");let n=require("react");var r=[];function i(e,t){return e===t||e.length===t.length&&e.every((e,n)=>e===t[n])}function a({selectionMode:a,selectedItems:o,defaultSelectedItems:s,onSelectedItemsChange:c}){let l=(0,n.useRef)(void 0),[u,d]=e.useControllable(o,s??r,e=>c?.([...e],l.current)),[f,p]=(0,n.useState)(()=>({ids:u,set:new Set(u)})),m=f.set;i(f.ids,u)||(m=new Set(u),p({ids:u,set:m}));let h=(0,n.useCallback)((e,t)=>{l.current=t,d(t=>{let n=[...e(new Set(t))];return i(t,n)?t:n}),l.current=void 0},[d]);return(0,n.useMemo)(()=>({selectionMode:a,selectedItems:m,isSelected:e=>m.has(e),toggle:(e,n)=>h(n=>t.toggleId(n,e,a),{event:n,id:e}),select:(e,t)=>h(t=>a===`single`?new Set([e]):new Set(t).add(e),{event:t,id:e}),deselect:(e,t)=>h(t=>{let n=new Set(t);return n.delete(e),n},{event:t,id:e}),toggleAll:(e,n)=>{a!==`single`&&h(n=>t.summarizeSelection(n,e).all?t.clearIds(n,e):t.selectAllIds(n,e),{event:n})},clear:e=>h(()=>new Set,{event:e})}),[a,m,h])}exports.useSelection=a;
2
+ //# sourceMappingURL=useSelection.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSelection.cjs","names":["useRef","useControllable","useState","useCallback","useMemo","toggleId","summarizeSelection","clearIds","selectAllIds","require_useControllable","require_selection","react","NO_IDS","sameIds","a","b","id","index","useSelection","selectedItemsProp","detailsRef","ids","setIds","defaultSelectedItems","onSelectedItemsChange","next","cache","setCache","selectedItems","change","details","update","prev","selectionMode","universe"],"sources":["../../src/hooks/useSelection.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\nimport { useControllable } from './useControllable';\nimport { clearIds, selectAllIds, summarizeSelection, toggleId } from '../lib/selection';\nimport type { SelectionMode } from '../lib/types';\n\n/** What {@link UseSelectionOptions.onSelectedItemsChange} receives as its second argument. */\nexport interface UseSelectionChangeDetails {\n /** The event that caused the change, when the caller passed one. */\n event?: Event;\n /** The id that was toggled, selected or deselected. Absent for `toggleAll` and `clear`. */\n id?: string;\n}\n\n/** Options of {@link useSelection}. */\nexport interface UseSelectionOptions {\n /**\n * `single` keeps at most one id selected and never deselects through `toggle`; `multiple` toggles\n * ids independently.\n */\n selectionMode: SelectionMode;\n /** The selected ids (controlled). Pair it with `onSelectedItemsChange`. */\n selectedItems?: readonly string[];\n /**\n * The ids selected at first render (uncontrolled).\n * @default []\n */\n defaultSelectedItems?: readonly string[];\n /**\n * Called with a new array of the selected ids whenever the selection changes. A call that leaves\n * the selection as it is (selecting a selected id, toggling the selected id in `single` mode)\n * does not call it.\n */\n onSelectedItemsChange?: (selectedItems: string[], details?: UseSelectionChangeDetails) => void;\n}\n\n/** What {@link useSelection} returns. */\nexport interface UseSelectionResult {\n /** The mode the hook was given. */\n selectionMode: SelectionMode;\n /**\n * The selected ids. The same set object is returned while the ids stay the same, so it can key a\n * memo. It holds ids whose items are not rendered.\n */\n selectedItems: ReadonlySet<string>;\n /** Whether `id` is selected. */\n isSelected(id: string): boolean;\n /** Toggles `id`: in `multiple` mode adds or removes it, in `single` mode selects it. */\n toggle(id: string, event?: Event): void;\n /** Selects `id`; in `single` mode it replaces the selection. */\n select(id: string, event?: Event): void;\n /** Removes `id` from the selection. */\n deselect(id: string, event?: Event): void;\n /**\n * Selects every id of `universe` when not all of them are selected, else removes only the ids of\n * `universe`: ids outside it stay selected. It does nothing in `single` mode.\n */\n toggleAll(universe: readonly string[], event?: Event): void;\n /** Empties the selection, ids outside any universe included. */\n clear(event?: Event): void;\n}\n\nconst NO_IDS: readonly string[] = [];\n\nfunction sameIds(a: readonly string[], b: readonly string[]): boolean {\n return a === b || (a.length === b.length && a.every((id, index) => id === b[index]));\n}\n\n/**\n * Selection by id, held outside the DOM, for lists, tables and trees whose items are not all\n * rendered. State is controlled with `selectedItems` or uncontrolled with `defaultSelectedItems`;\n * `onSelectedItemsChange` fires once per change, never for a no-op.\n *\n * @example\n * const selection = useSelection({ selectionMode: 'multiple' });\n * selection.toggleAll(rows.map((row) => row.id));\n */\nexport function useSelection({\n selectionMode,\n selectedItems: selectedItemsProp,\n defaultSelectedItems,\n onSelectedItemsChange,\n}: UseSelectionOptions): UseSelectionResult {\n // The details of the change in flight: set by the action just before it asks for the change, read\n // by the change callback, which `useControllable` calls synchronously from the same action.\n const detailsRef = useRef<UseSelectionChangeDetails | undefined>(undefined);\n const [ids, setIds] = useControllable<readonly string[]>(\n selectedItemsProp,\n defaultSelectedItems ?? NO_IDS,\n (next) => onSelectedItemsChange?.([...next], detailsRef.current),\n );\n\n // The set is rebuilt only when the ids differ, by content: a controlled parent that passes a new\n // array of the same ids on every render keeps the same set.\n const [cache, setCache] = useState(() => ({ ids, set: new Set(ids) as ReadonlySet<string> }));\n let selectedItems = cache.set;\n if (!sameIds(cache.ids, ids)) {\n selectedItems = new Set(ids);\n setCache({ ids, set: selectedItems });\n }\n\n const change = useCallback(\n (\n update: (prev: ReadonlySet<string>) => ReadonlySet<string>,\n details: UseSelectionChangeDetails,\n ) => {\n detailsRef.current = details;\n setIds((prev) => {\n const next = [...update(new Set(prev))];\n return sameIds(prev, next) ? prev : next;\n });\n detailsRef.current = undefined;\n },\n [setIds],\n );\n\n return useMemo<UseSelectionResult>(\n () => ({\n selectionMode,\n selectedItems,\n isSelected: (id) => selectedItems.has(id),\n toggle: (id, event) => change((prev) => toggleId(prev, id, selectionMode), { event, id }),\n select: (id, event) =>\n change((prev) => (selectionMode === 'single' ? new Set([id]) : new Set(prev).add(id)), {\n event,\n id,\n }),\n deselect: (id, event) =>\n change(\n (prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n },\n { event, id },\n ),\n toggleAll: (universe, event) => {\n if (selectionMode === 'single') return;\n change(\n (prev) =>\n summarizeSelection(prev, universe).all\n ? clearIds(prev, universe)\n : selectAllIds(prev, universe),\n { event },\n );\n },\n clear: (event) => change(() => new Set(), { event }),\n }),\n [selectionMode, selectedItems, change],\n );\n}\n"],"mappings":"+GA6DA,IAAMY,EAA4B,CAAC,EAEnC,SAASC,EAAQ,EAAsB,EAA+B,CACpE,OAAOC,IAAMC,GAAMD,EAAE,SAAWC,EAAE,QAAUD,EAAE,OAAO,EAAI,IAAUE,IAAOD,EAAEE,EAAM,CACpF,CAWA,SAAgBC,EAAa,CAC3B,gBACA,cAAeC,EACf,uBACA,yBAC0C,CAG1C,IAAMC,GAAA,EAAapB,EAAAA,OAAAA,CAA8C,IAAA,EAAS,EACpE,CAACqB,EAAKC,GAAUrB,EAAAA,gBACpBkB,EACAI,GAAwBX,EACvB,GAASY,IAAwB,CAAC,GAAGC,CAAI,EAAGL,EAAW,OAAO,CACjE,EAIM,CAACM,EAAOC,IAAA,EAAYzB,EAAAA,SAAAA,MAAgB,CAAE,MAAK,IAAK,IAAI,IAAImB,CAAG,CAAyB,EAAE,EACxFO,EAAgBF,EAAM,IACrBb,EAAQa,EAAM,IAAKL,CAAG,IACzB,EAAgB,IAAI,IAAIA,CAAG,EAC3BM,EAAS,CAAE,MAAK,IAAKC,CAAc,CAAC,GAGtC,IAAMC,GAAA,EAAS1B,EAAAA,YAAAA,EAEX,EACA,IACG,CACH,EAAW,QAAU2B,EACrBR,EAAQ,GAAS,CACf,IAAMG,EAAO,CAAC,GAAGM,EAAO,IAAI,IAAIC,CAAI,CAAC,CAAC,EACtC,OAAOnB,EAAQmB,EAAMP,CAAI,EAAIO,EAAOP,CACtC,CAAC,EACD,EAAW,QAAU,IAAA,EACvB,EACA,CAACH,CAAM,CACT,EAEA,OAAA,EAAOlB,EAAAA,QAAAA,MACE,CACL,gBACA,gBACA,WAAa,GAAOwB,EAAc,IAAIZ,CAAE,EACxC,QAAS,EAAI,IAAUa,EAAQ,GAASxB,EAAAA,SAAS2B,EAAMhB,EAAIiB,CAAa,EAAG,CAAE,QAAO,IAAG,CAAC,EACxF,QAAS,EAAI,IACXJ,EAAQ,GAAUI,IAAkB,SAAW,IAAI,IAAI,CAACjB,CAAE,CAAC,EAAI,IAAI,IAAIgB,CAAI,CAAC,CAAC,IAAIhB,CAAE,EAAI,CACrF,QACA,IACF,CAAC,EACH,UAAW,EAAI,IACba,EACG,GAAS,CACR,IAAMJ,EAAO,IAAI,IAAIO,CAAI,EAEzB,OADAP,EAAK,OAAOT,CAAE,EACPS,CACT,EACA,CAAE,QAAO,IAAG,CACd,EACF,WAAY,EAAU,IAAU,CAC1BQ,IAAkB,UACtBJ,EACG,GACCvB,EAAAA,mBAAmB0B,EAAME,CAAQ,CAAC,CAAC,IAC/B3B,EAAAA,SAASyB,EAAME,CAAQ,EACvB1B,EAAAA,aAAawB,EAAME,CAAQ,EACjC,CAAE,OAAM,CACV,CACF,EACA,MAAQ,GAAUL,MAAa,IAAI,IAAO,CAAE,OAAM,CAAC,CACrD,GACA,CAACI,EAAeL,EAAeC,CAAM,CACvC,CACF"}
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { useControllable as e } from "./useControllable.mjs";
3
+ import { clearIds as t, selectAllIds as n, summarizeSelection as r, toggleId as i } from "../lib/selection.mjs";
4
+ import { useCallback as a, useMemo as o, useRef as s, useState as c } from "react";
5
+ //#region src/hooks/useSelection.ts
6
+ var l = [];
7
+ function u(e, t) {
8
+ return e === t || e.length === t.length && e.every((e, n) => e === t[n]);
9
+ }
10
+ function d({ selectionMode: d, selectedItems: f, defaultSelectedItems: p, onSelectedItemsChange: m }) {
11
+ let h = s(void 0), [g, _] = e(f, p ?? l, (e) => m?.([...e], h.current)), [v, y] = c(() => ({
12
+ ids: g,
13
+ set: new Set(g)
14
+ })), b = v.set;
15
+ u(v.ids, g) || (b = new Set(g), y({
16
+ ids: g,
17
+ set: b
18
+ }));
19
+ let x = a((e, t) => {
20
+ h.current = t, _((t) => {
21
+ let n = [...e(new Set(t))];
22
+ return u(t, n) ? t : n;
23
+ }), h.current = void 0;
24
+ }, [_]);
25
+ return o(() => ({
26
+ selectionMode: d,
27
+ selectedItems: b,
28
+ isSelected: (e) => b.has(e),
29
+ toggle: (e, t) => x((t) => i(t, e, d), {
30
+ event: t,
31
+ id: e
32
+ }),
33
+ select: (e, t) => x((t) => d === "single" ? /* @__PURE__ */ new Set([e]) : new Set(t).add(e), {
34
+ event: t,
35
+ id: e
36
+ }),
37
+ deselect: (e, t) => x((t) => {
38
+ let n = new Set(t);
39
+ return n.delete(e), n;
40
+ }, {
41
+ event: t,
42
+ id: e
43
+ }),
44
+ toggleAll: (e, i) => {
45
+ d !== "single" && x((i) => r(i, e).all ? t(i, e) : n(i, e), { event: i });
46
+ },
47
+ clear: (e) => x(() => /* @__PURE__ */ new Set(), { event: e })
48
+ }), [
49
+ d,
50
+ b,
51
+ x
52
+ ]);
53
+ }
54
+ //#endregion
55
+ export { d as useSelection };
56
+
57
+ //# sourceMappingURL=useSelection.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSelection.mjs","names":["NO_IDS","sameIds","a","b","id","index","useSelection","selectedItemsProp","detailsRef","useRef","ids","setIds","useControllable","defaultSelectedItems","onSelectedItemsChange","next","cache","setCache","useState","selectedItems","change","useCallback","details","update","prev","useMemo","toggleId","selectionMode","summarizeSelection","universe","clearIds","selectAllIds"],"sources":["../../src/hooks/useSelection.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\nimport { useControllable } from './useControllable';\nimport { clearIds, selectAllIds, summarizeSelection, toggleId } from '../lib/selection';\nimport type { SelectionMode } from '../lib/types';\n\n/** What {@link UseSelectionOptions.onSelectedItemsChange} receives as its second argument. */\nexport interface UseSelectionChangeDetails {\n /** The event that caused the change, when the caller passed one. */\n event?: Event;\n /** The id that was toggled, selected or deselected. Absent for `toggleAll` and `clear`. */\n id?: string;\n}\n\n/** Options of {@link useSelection}. */\nexport interface UseSelectionOptions {\n /**\n * `single` keeps at most one id selected and never deselects through `toggle`; `multiple` toggles\n * ids independently.\n */\n selectionMode: SelectionMode;\n /** The selected ids (controlled). Pair it with `onSelectedItemsChange`. */\n selectedItems?: readonly string[];\n /**\n * The ids selected at first render (uncontrolled).\n * @default []\n */\n defaultSelectedItems?: readonly string[];\n /**\n * Called with a new array of the selected ids whenever the selection changes. A call that leaves\n * the selection as it is (selecting a selected id, toggling the selected id in `single` mode)\n * does not call it.\n */\n onSelectedItemsChange?: (selectedItems: string[], details?: UseSelectionChangeDetails) => void;\n}\n\n/** What {@link useSelection} returns. */\nexport interface UseSelectionResult {\n /** The mode the hook was given. */\n selectionMode: SelectionMode;\n /**\n * The selected ids. The same set object is returned while the ids stay the same, so it can key a\n * memo. It holds ids whose items are not rendered.\n */\n selectedItems: ReadonlySet<string>;\n /** Whether `id` is selected. */\n isSelected(id: string): boolean;\n /** Toggles `id`: in `multiple` mode adds or removes it, in `single` mode selects it. */\n toggle(id: string, event?: Event): void;\n /** Selects `id`; in `single` mode it replaces the selection. */\n select(id: string, event?: Event): void;\n /** Removes `id` from the selection. */\n deselect(id: string, event?: Event): void;\n /**\n * Selects every id of `universe` when not all of them are selected, else removes only the ids of\n * `universe`: ids outside it stay selected. It does nothing in `single` mode.\n */\n toggleAll(universe: readonly string[], event?: Event): void;\n /** Empties the selection, ids outside any universe included. */\n clear(event?: Event): void;\n}\n\nconst NO_IDS: readonly string[] = [];\n\nfunction sameIds(a: readonly string[], b: readonly string[]): boolean {\n return a === b || (a.length === b.length && a.every((id, index) => id === b[index]));\n}\n\n/**\n * Selection by id, held outside the DOM, for lists, tables and trees whose items are not all\n * rendered. State is controlled with `selectedItems` or uncontrolled with `defaultSelectedItems`;\n * `onSelectedItemsChange` fires once per change, never for a no-op.\n *\n * @example\n * const selection = useSelection({ selectionMode: 'multiple' });\n * selection.toggleAll(rows.map((row) => row.id));\n */\nexport function useSelection({\n selectionMode,\n selectedItems: selectedItemsProp,\n defaultSelectedItems,\n onSelectedItemsChange,\n}: UseSelectionOptions): UseSelectionResult {\n // The details of the change in flight: set by the action just before it asks for the change, read\n // by the change callback, which `useControllable` calls synchronously from the same action.\n const detailsRef = useRef<UseSelectionChangeDetails | undefined>(undefined);\n const [ids, setIds] = useControllable<readonly string[]>(\n selectedItemsProp,\n defaultSelectedItems ?? NO_IDS,\n (next) => onSelectedItemsChange?.([...next], detailsRef.current),\n );\n\n // The set is rebuilt only when the ids differ, by content: a controlled parent that passes a new\n // array of the same ids on every render keeps the same set.\n const [cache, setCache] = useState(() => ({ ids, set: new Set(ids) as ReadonlySet<string> }));\n let selectedItems = cache.set;\n if (!sameIds(cache.ids, ids)) {\n selectedItems = new Set(ids);\n setCache({ ids, set: selectedItems });\n }\n\n const change = useCallback(\n (\n update: (prev: ReadonlySet<string>) => ReadonlySet<string>,\n details: UseSelectionChangeDetails,\n ) => {\n detailsRef.current = details;\n setIds((prev) => {\n const next = [...update(new Set(prev))];\n return sameIds(prev, next) ? prev : next;\n });\n detailsRef.current = undefined;\n },\n [setIds],\n );\n\n return useMemo<UseSelectionResult>(\n () => ({\n selectionMode,\n selectedItems,\n isSelected: (id) => selectedItems.has(id),\n toggle: (id, event) => change((prev) => toggleId(prev, id, selectionMode), { event, id }),\n select: (id, event) =>\n change((prev) => (selectionMode === 'single' ? new Set([id]) : new Set(prev).add(id)), {\n event,\n id,\n }),\n deselect: (id, event) =>\n change(\n (prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n },\n { event, id },\n ),\n toggleAll: (universe, event) => {\n if (selectionMode === 'single') return;\n change(\n (prev) =>\n summarizeSelection(prev, universe).all\n ? clearIds(prev, universe)\n : selectAllIds(prev, universe),\n { event },\n );\n },\n clear: (event) => change(() => new Set(), { event }),\n }),\n [selectionMode, selectedItems, change],\n );\n}\n"],"mappings":";;;;;AA6DA,IAAMA,IAA4B,CAAC;AAEnC,SAASC,EAAQ,GAAsB,GAA+B;CACpE,OAAOC,MAAMC,KAAMD,EAAE,WAAWC,EAAE,UAAUD,EAAE,OAAO,GAAI,MAAUE,MAAOD,EAAEE,EAAM;AACpF;AAWA,SAAgBC,EAAa,EAC3B,kBACA,eAAeC,GACf,yBACA,4BAC0C;CAG1C,IAAMC,IAAaC,EAA8C,KAAA,CAAS,GACpE,CAACC,GAAKC,KAAUC,EACpBL,GACAM,KAAwBb,IACvB,MAASc,IAAwB,CAAC,GAAGC,CAAI,GAAGP,EAAW,OAAO,CACjE,GAIM,CAACQ,GAAOC,KAAYC,SAAgB;EAAE;EAAK,KAAK,IAAI,IAAIR,CAAG;CAAyB,EAAE,GACxFS,IAAgBH,EAAM;CAC1B,AAAKf,EAAQe,EAAM,KAAKN,CAAG,MACzB,IAAgB,IAAI,IAAIA,CAAG,GAC3BO,EAAS;EAAE;EAAK,KAAKE;CAAc,CAAC;CAGtC,IAAMC,IAASC,GAEX,GACA,MACG;EAMH,AALA,EAAW,UAAUC,GACrBX,GAAQ,MAAS;GACf,IAAMI,IAAO,CAAC,GAAGQ,EAAO,IAAI,IAAIC,CAAI,CAAC,CAAC;GACtC,OAAOvB,EAAQuB,GAAMT,CAAI,IAAIS,IAAOT;EACtC,CAAC,GACD,EAAW,UAAU,KAAA;CACvB,GACA,CAACJ,CAAM,CACT;CAEA,OAAOc,SACE;EACL;EACA;EACA,aAAa,MAAON,EAAc,IAAIf,CAAE;EACxC,SAAS,GAAI,MAAUgB,GAAQ,MAASM,EAASF,GAAMpB,GAAIuB,CAAa,GAAG;GAAE;GAAO;EAAG,CAAC;EACxF,SAAS,GAAI,MACXP,GAAQ,MAAUO,MAAkB,2BAAW,IAAI,IAAI,CAACvB,CAAE,CAAC,IAAI,IAAI,IAAIoB,CAAI,CAAC,CAAC,IAAIpB,CAAE,GAAI;GACrF;GACA;EACF,CAAC;EACH,WAAW,GAAI,MACbgB,GACG,MAAS;GACR,IAAML,IAAO,IAAI,IAAIS,CAAI;GAEzB,OADAT,EAAK,OAAOX,CAAE,GACPW;EACT,GACA;GAAE;GAAO;EAAG,CACd;EACF,YAAY,GAAU,MAAU;GAC9B,AAAIY,MAAkB,YACtBP,GACG,MACCQ,EAAmBJ,GAAMK,CAAQ,CAAC,CAAC,MAC/BC,EAASN,GAAMK,CAAQ,IACvBE,EAAaP,GAAMK,CAAQ,GACjC,EAAE,SAAM,CACV;EACF;EACA,QAAQ,MAAUT,wBAAa,IAAI,IAAI,GAAG,EAAE,SAAM,CAAC;CACrD,IACA;EAACO;EAAeR;EAAeC;CAAM,CACvC;AACF"}
@@ -0,0 +1,2 @@
1
+ "use client";const e=require("../lib/dev.cjs"),t=require("./useEventCallback.cjs"),n=require("../lib/virtual.cjs");let r=require("react");var i={firstVisible:0,viewportSize:0},a=240;function o(e){return Number.isFinite(e)&&e>0}function s({itemSize:s,overscan:c,initialViewportSize:l=480,scrollPaddingStart:u=0}){let d=o(s)?s:1,f=c??Math.max(6,Math.ceil(a/d)),p=Number.isFinite(u)&&u>0?u:0,[m,h]=(0,r.useState)(i),[g,_]=(0,r.useState)(null),v=(0,r.useRef)(i),y=(0,r.useRef)(null),b=(0,r.useRef)(null);(0,r.useEffect)(()=>{o(s)||e.warnOnce(`useVirtualWindow:item-size`,`useVirtualWindow: itemSize must be a positive number of pixels (got ${String(s)}); 1 is used.`)},[s]);let x=t.useEventCallback(e=>{let{scrollTop:t,clientHeight:n}=e,r={firstVisible:t>0?Math.floor(t/d):0,viewportSize:n},i=v.current;(r.firstVisible!==i.firstVisible||r.viewportSize!==i.viewportSize)&&(v.current=r,h(r))}),S=t.useEventCallback((e,t)=>{let{scrollTop:r,scrollHeight:i,clientHeight:a}=e,o=a>0?a:l;e.scrollTop=n.scrollOffsetForIndex({index:t.index,align:t.align,itemSize:d,count:i>0?Math.floor((i-p)/d):2**53-1,scrollTop:r,viewportSize:Math.max(d,o-p)}),x(e)}),C=t.useEventCallback((e,t)=>{let n={index:e,align:t?.align??`auto`};b.current=null,g?.isConnected?S(g,n):y.current=n}),w=(0,r.useCallback)(e=>_(e),[]);return(0,r.useLayoutEffect)(()=>{if(b.current&&b.current.element!==g&&(b.current=null),!g)return;let e=null,t=!1,n=()=>{e=null,t||x(g)},r=()=>{e===null&&(typeof requestAnimationFrame==`function`?e=requestAnimationFrame(n):x(g))};g.addEventListener(`scroll`,r,{passive:!0});let i=typeof ResizeObserver>`u`?null:new ResizeObserver(()=>{let e=b.current;if(e?.element===g&&g.clientHeight!==e.viewportSize&&(b.current=null,Math.abs(g.scrollTop-e.offset)<1)){S(g,e.request);return}x(g)});i?.observe(g);let a=y.current;return y.current=null,a&&(S(g,a),b.current={element:g,request:a,viewportSize:g.clientHeight,offset:g.scrollTop}),()=>{t=!0,g.removeEventListener(`scroll`,r),i?.disconnect(),e!==null&&cancelAnimationFrame(e)}},[g,x,S]),(0,r.useLayoutEffect)(()=>{g&&x(g)},[g,d,x]),(0,r.useMemo)(()=>({itemSize:d,scrollRef:w,scrollToIndex:C,layout:(e,t)=>n.computeVirtualLayout({count:e,itemSize:d,firstVisible:m.firstVisible,viewportSize:m.viewportSize>0?m.viewportSize:l,overscan:f,keep:t?.keep})}),[d,w,C,m,f,l])}exports.useVirtualWindow=s;
2
+ //# sourceMappingURL=useVirtualWindow.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVirtualWindow.cjs","names":["useState","useRef","useEventCallback","scrollOffsetForIndex","useCallback","useMemo","computeVirtualLayout","require_dev","require_useEventCallback","require_virtual","react","UNMEASURED","OVERSCAN_PIXELS","isValidItemSize","itemSize","useVirtualWindow","size","overscanRows","overscan","paddingStart","scrollPaddingStart","state","setState","scroller","setScroller","measuredRef","pendingRef","settlingRef","measure","element","next","scrollTop","clientHeight","last","scrollElementTo","viewport","initialViewportSize","request","scrollHeight","scrollToIndex","options","scrollRef","frame","released","onFrame","onScroll","observer","settling","pending"],"sources":["../../src/hooks/useVirtualWindow.ts"],"sourcesContent":["import type * as React from 'react';\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { useEventCallback } from './useEventCallback';\nimport { warnOnce } from '../lib/dev';\nimport { computeVirtualLayout, scrollOffsetForIndex, type VirtualEntry } from '../lib/virtual';\n\n/** Options of {@link useVirtualWindow}. */\nexport interface UseVirtualWindowOptions {\n /**\n * The height of every row, in pixels (all rows have the same height). A value that is not a\n * positive finite number warns once in development and counts as 1.\n */\n itemSize: number;\n /**\n * Rows rendered beyond each edge of the viewport, so a fast scroll shows rows instead of blank\n * space before the next window renders. A given number is a number of rows; the default covers\n * at least 240 pixels, so small rows get more of them (9 rows of 28 pixels, 8 of 32).\n * @default enough rows for 240 pixels, and at least 6\n */\n overscan?: number;\n /**\n * The viewport height, in pixels, assumed until the scroller is measured: on the server, in the\n * first render, and while the scroller measures 0 (it has no layout, as in jsdom).\n * @default 480\n */\n initialViewportSize?: number;\n /**\n * The height, in pixels, of content pinned over the top of the rows inside the scroller (a\n * sticky header). `scrollToIndex` places rows in the area below it, and its content bound leaves\n * it out; the rows' offsets and the layout do not change. A value that is not a finite number of\n * 0 or more counts as 0.\n * @default 0\n */\n scrollPaddingStart?: number;\n}\n\n/**\n * A window over a long list of fixed-height rows: which rows to render for the scroll position,\n * and the space that stands in for the others. {@link useVirtualWindow} returns one; a component\n * that takes a `virtualWindow` prop renders its rows through it, and an adapter over a\n * virtualization library can implement the same interface.\n */\nexport interface VirtualWindow {\n /** The row height the window lays out with, in pixels. */\n itemSize: number;\n /**\n * The callback ref of the scrolling element (the element with a bounded height and its own\n * scrollbar). It attaches once per element and detaches when called with `null`.\n */\n scrollRef: React.RefCallback<HTMLElement>;\n /**\n * The entries to render, in order, for a list of `count` rows: the rows in view plus the\n * overscan, every `keep` index outside them as a row of its own (the focused row, a row about\n * to be focused), and spacers for the rows in between. The sizes add up to `count * itemSize`.\n * A pure function of the window's state and its arguments, called during render; it reads no\n * DOM.\n */\n layout(count: number, options?: { keep?: readonly number[] }): readonly VirtualEntry[];\n /**\n * Scrolls the row at `index` into view and moves the window to it in the same update, so the\n * row is rendered once that update commits. `align` places it at the `start`, `center` or `end`\n * of the viewport; `auto` (the default) scrolls the least that shows it whole. The offset is\n * bounded by the rendered content. Before the scroller attaches, the last request is kept and\n * applied when it does, and once more when the scroller first changes height after that (a\n * popup that positioning shrinks once it is shown), unless it was scrolled or another request\n * came in between. Call it from an event handler or an effect, never during render.\n */\n scrollToIndex(index: number, options?: { align?: 'auto' | 'start' | 'center' | 'end' }): void;\n}\n\n/** The measured state: the first row in view and the viewport height (0 until measured). */\ninterface WindowState {\n firstVisible: number;\n viewportSize: number;\n}\n\ninterface ScrollRequest {\n index: number;\n align: 'auto' | 'start' | 'center' | 'end';\n}\n\n/** A request applied on attach, with the viewport height and the offset it was applied with. */\ninterface SettlingRequest {\n element: HTMLElement;\n request: ScrollRequest;\n viewportSize: number;\n offset: number;\n}\n\nconst UNMEASURED: WindowState = { firstVisible: 0, viewportSize: 0 };\n\n/** The pixels the default overscan covers beyond each edge of the viewport (at least 6 rows). */\nconst OVERSCAN_PIXELS = 240;\n\nfunction isValidItemSize(itemSize: number): boolean {\n return Number.isFinite(itemSize) && itemSize > 0;\n}\n\n/**\n * A window over a long list of fixed-height rows, for rendering only the rows in view. Give\n * `scrollRef` to the scrolling element and render `layout(count)`: rows for the item entries and\n * empty blocks of `size` pixels for the spacer entries.\n *\n * The window re-renders only when the first row in view or the viewport height changes, not on\n * every scroll event: scrolling is read in a passive `scroll` listener once per animation frame,\n * and the viewport height on attach, on scroll and from a `ResizeObserver` where one exists. The\n * server and the first render lay out the top of the list in `initialViewportSize`, and read no\n * DOM.\n *\n * Never write `ref={virtualWindow.scrollRef}` in JSX: the React Compiler's lint types a member\n * read in the `ref` position as a ref, then reports that read and every call on the window during\n * render (`virtualWindow.layout(count)` included). Merge the callback with the element's other\n * refs through `useMergedRefs(ownRef, virtualWindow.scrollRef)`, or destructure it first\n * (`const { scrollRef } = virtualWindow`); `virtualWindow.layout(count)` can then be called as is.\n *\n * @example\n * const virtualWindow = useVirtualWindow({ itemSize: 32 });\n * const { scrollRef } = virtualWindow;\n * return (\n * <div ref={scrollRef} style={{ height: 320, overflowY: 'auto' }}>\n * {virtualWindow.layout(rows.length).map((entry) =>\n * entry.kind === 'item' ? (\n * <Row key={rows[entry.index].id} row={rows[entry.index]} />\n * ) : (\n * <div key={entry.key} aria-hidden=\"true\" style={{ height: entry.size }} />\n * ),\n * )}\n * </div>\n * );\n */\nexport function useVirtualWindow({\n itemSize,\n overscan,\n initialViewportSize = 480,\n scrollPaddingStart = 0,\n}: UseVirtualWindowOptions): VirtualWindow {\n const size = isValidItemSize(itemSize) ? itemSize : 1;\n const overscanRows = overscan ?? Math.max(6, Math.ceil(OVERSCAN_PIXELS / size));\n const paddingStart =\n Number.isFinite(scrollPaddingStart) && scrollPaddingStart > 0 ? scrollPaddingStart : 0;\n const [state, setState] = useState<WindowState>(UNMEASURED);\n const [scroller, setScroller] = useState<HTMLElement | null>(null);\n // The values last given to `setState`, so a frame inside the same row sets nothing (React may\n // render a component once more for an update to an equal value).\n const measuredRef = useRef<WindowState>(UNMEASURED);\n const pendingRef = useRef<ScrollRequest | null>(null);\n // The request applied on attach, applied once more at the scroller's first change of height\n // unless the scroller moved or another request came first.\n const settlingRef = useRef<SettlingRequest | null>(null);\n\n useEffect(() => {\n if (!isValidItemSize(itemSize))\n warnOnce(\n 'useVirtualWindow:item-size',\n `useVirtualWindow: itemSize must be a positive number of pixels (got ${String(itemSize)}); 1 is used.`,\n );\n }, [itemSize]);\n\n const measure = useEventCallback((element: HTMLElement) => {\n const { scrollTop, clientHeight } = element;\n const next: WindowState = {\n // Negative while an elastic overscroll pulls past the top.\n firstVisible: scrollTop > 0 ? Math.floor(scrollTop / size) : 0,\n viewportSize: clientHeight,\n };\n const last = measuredRef.current;\n if (next.firstVisible === last.firstVisible && next.viewportSize === last.viewportSize) return;\n measuredRef.current = next;\n setState(next);\n });\n\n const scrollElementTo = useEventCallback((element: HTMLElement, request: ScrollRequest) => {\n const { scrollTop, scrollHeight, clientHeight } = element;\n const viewport = clientHeight > 0 ? clientHeight : initialViewportSize;\n element.scrollTop = scrollOffsetForIndex({\n index: request.index,\n align: request.align,\n itemSize: size,\n // The browser bounds `scrollTop` by the content it laid out; its rows bound the request the\n // same way. Without layout (jsdom, a hidden scroller) only the start bounds it.\n count:\n scrollHeight > 0\n ? Math.floor((scrollHeight - paddingStart) / size)\n : Number.MAX_SAFE_INTEGER,\n scrollTop,\n // A sticky header covers the top of the viewport and leaves the rows where they are: the\n // rows are seen in the area below it (at least one row tall).\n viewportSize: Math.max(size, viewport - paddingStart),\n });\n measure(element);\n });\n\n const scrollToIndex = useEventCallback(\n (index: number, options?: { align?: 'auto' | 'start' | 'center' | 'end' }) => {\n const request: ScrollRequest = { index, align: options?.align ?? 'auto' };\n settlingRef.current = null;\n // A scroller that left the document but whose `null` has not committed yet counts as gone.\n if (scroller?.isConnected) scrollElementTo(scroller, request);\n else pendingRef.current = request;\n },\n );\n\n const scrollRef = useCallback((element: HTMLElement | null) => setScroller(element), []);\n\n // Attach: the scroll listener, the size observer, and a request made before the scroller.\n useLayoutEffect(() => {\n // The request applied on attach is forgotten with its scroller (a closed popup's list is not\n // held). Not in the cleanup: StrictMode's second attach of the same element keeps it.\n if (settlingRef.current && settlingRef.current.element !== scroller) settlingRef.current = null;\n if (!scroller) return;\n let frame: number | null = null;\n let released = false;\n const onFrame = () => {\n frame = null;\n if (!released) measure(scroller);\n };\n const onScroll = () => {\n if (frame !== null) return;\n if (typeof requestAnimationFrame === 'function') frame = requestAnimationFrame(onFrame);\n else measure(scroller);\n };\n scroller.addEventListener('scroll', onScroll, { passive: true });\n // The first change of height after a request applied on attach applies it once more (the\n // height it was applied with was not the final one), unless the scroller moved since.\n const onResize = () => {\n const settling = settlingRef.current;\n if (settling?.element === scroller && scroller.clientHeight !== settling.viewportSize) {\n settlingRef.current = null;\n if (Math.abs(scroller.scrollTop - settling.offset) < 1) {\n scrollElementTo(scroller, settling.request);\n return;\n }\n }\n measure(scroller);\n };\n const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(onResize);\n observer?.observe(scroller);\n const pending = pendingRef.current;\n pendingRef.current = null;\n if (pending) {\n scrollElementTo(scroller, pending);\n settlingRef.current = {\n element: scroller,\n request: pending,\n viewportSize: scroller.clientHeight,\n offset: scroller.scrollTop,\n };\n }\n return () => {\n released = true;\n scroller.removeEventListener('scroll', onScroll);\n observer?.disconnect();\n if (frame !== null) cancelAnimationFrame(frame);\n };\n }, [scroller, measure, scrollElementTo]);\n\n // Read the offset and the size on attach, and again for a new row height: the first row in view\n // is counted in rows of the old one.\n useLayoutEffect(() => {\n if (scroller) measure(scroller);\n }, [scroller, size, measure]);\n\n return useMemo<VirtualWindow>(\n () => ({\n itemSize: size,\n scrollRef,\n scrollToIndex,\n layout: (count, options) =>\n computeVirtualLayout({\n count,\n itemSize: size,\n firstVisible: state.firstVisible,\n viewportSize: state.viewportSize > 0 ? state.viewportSize : initialViewportSize,\n overscan: overscanRows,\n keep: options?.keep,\n }),\n }),\n [size, scrollRef, scrollToIndex, state, overscanRows, initialViewportSize],\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { warnOnce as e } from "../lib/dev.mjs";
3
+ import { useEventCallback as t } from "./useEventCallback.mjs";
4
+ import { computeVirtualLayout as n, scrollOffsetForIndex as r } from "../lib/virtual.mjs";
5
+ import { useCallback as i, useEffect as a, useLayoutEffect as o, useMemo as s, useRef as c, useState as l } from "react";
6
+ //#region src/hooks/useVirtualWindow.ts
7
+ var u = {
8
+ firstVisible: 0,
9
+ viewportSize: 0
10
+ }, d = 240;
11
+ function f(e) {
12
+ return Number.isFinite(e) && e > 0;
13
+ }
14
+ function p({ itemSize: p, overscan: m, initialViewportSize: h = 480, scrollPaddingStart: g = 0 }) {
15
+ let _ = f(p) ? p : 1, v = m ?? Math.max(6, Math.ceil(d / _)), y = Number.isFinite(g) && g > 0 ? g : 0, [b, x] = l(u), [S, C] = l(null), w = c(u), T = c(null), E = c(null);
16
+ a(() => {
17
+ f(p) || e("useVirtualWindow:item-size", `useVirtualWindow: itemSize must be a positive number of pixels (got ${String(p)}); 1 is used.`);
18
+ }, [p]);
19
+ let D = t((e) => {
20
+ let { scrollTop: t, clientHeight: n } = e, r = {
21
+ firstVisible: t > 0 ? Math.floor(t / _) : 0,
22
+ viewportSize: n
23
+ }, i = w.current;
24
+ (r.firstVisible !== i.firstVisible || r.viewportSize !== i.viewportSize) && (w.current = r, x(r));
25
+ }), O = t((e, t) => {
26
+ let { scrollTop: n, scrollHeight: i, clientHeight: a } = e, o = a > 0 ? a : h;
27
+ e.scrollTop = r({
28
+ index: t.index,
29
+ align: t.align,
30
+ itemSize: _,
31
+ count: i > 0 ? Math.floor((i - y) / _) : 2 ** 53 - 1,
32
+ scrollTop: n,
33
+ viewportSize: Math.max(_, o - y)
34
+ }), D(e);
35
+ }), k = t((e, t) => {
36
+ let n = {
37
+ index: e,
38
+ align: t?.align ?? "auto"
39
+ };
40
+ E.current = null, S?.isConnected ? O(S, n) : T.current = n;
41
+ }), A = i((e) => C(e), []);
42
+ return o(() => {
43
+ if (E.current && E.current.element !== S && (E.current = null), !S) return;
44
+ let e = null, t = !1, n = () => {
45
+ e = null, t || D(S);
46
+ }, r = () => {
47
+ e === null && (typeof requestAnimationFrame == "function" ? e = requestAnimationFrame(n) : D(S));
48
+ };
49
+ S.addEventListener("scroll", r, { passive: !0 });
50
+ let i = typeof ResizeObserver > "u" ? null : new ResizeObserver(() => {
51
+ let e = E.current;
52
+ if (e?.element === S && S.clientHeight !== e.viewportSize && (E.current = null, Math.abs(S.scrollTop - e.offset) < 1)) {
53
+ O(S, e.request);
54
+ return;
55
+ }
56
+ D(S);
57
+ });
58
+ i?.observe(S);
59
+ let a = T.current;
60
+ return T.current = null, a && (O(S, a), E.current = {
61
+ element: S,
62
+ request: a,
63
+ viewportSize: S.clientHeight,
64
+ offset: S.scrollTop
65
+ }), () => {
66
+ t = !0, S.removeEventListener("scroll", r), i?.disconnect(), e !== null && cancelAnimationFrame(e);
67
+ };
68
+ }, [
69
+ S,
70
+ D,
71
+ O
72
+ ]), o(() => {
73
+ S && D(S);
74
+ }, [
75
+ S,
76
+ _,
77
+ D
78
+ ]), s(() => ({
79
+ itemSize: _,
80
+ scrollRef: A,
81
+ scrollToIndex: k,
82
+ layout: (e, t) => n({
83
+ count: e,
84
+ itemSize: _,
85
+ firstVisible: b.firstVisible,
86
+ viewportSize: b.viewportSize > 0 ? b.viewportSize : h,
87
+ overscan: v,
88
+ keep: t?.keep
89
+ })
90
+ }), [
91
+ _,
92
+ A,
93
+ k,
94
+ b,
95
+ v,
96
+ h
97
+ ]);
98
+ }
99
+ //#endregion
100
+ export { p as useVirtualWindow };
101
+
102
+ //# sourceMappingURL=useVirtualWindow.mjs.map