@mortenbrudvik/waveui 0.8.0 → 0.10.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 (206) hide show
  1. package/README.md +634 -167
  2. package/dist/components/data-display/InfoButton.cjs +2 -0
  3. package/dist/components/data-display/InfoButton.cjs.map +1 -0
  4. package/dist/components/data-display/InfoButton.mjs +173 -0
  5. package/dist/components/data-display/InfoButton.mjs.map +1 -0
  6. package/dist/components/data-display/InfoLabel.cjs +1 -1
  7. package/dist/components/data-display/InfoLabel.cjs.map +1 -1
  8. package/dist/components/data-display/InfoLabel.mjs +39 -96
  9. package/dist/components/data-display/InfoLabel.mjs.map +1 -1
  10. package/dist/components/input/Calendar.cjs +2 -0
  11. package/dist/components/input/Calendar.cjs.map +1 -0
  12. package/dist/components/input/Calendar.mjs +352 -0
  13. package/dist/components/input/Calendar.mjs.map +1 -0
  14. package/dist/components/input/Calendar.views.cjs +2 -0
  15. package/dist/components/input/Calendar.views.cjs.map +1 -0
  16. package/dist/components/input/Calendar.views.mjs +194 -0
  17. package/dist/components/input/Calendar.views.mjs.map +1 -0
  18. package/dist/components/input/Checkbox.cjs +1 -1
  19. package/dist/components/input/Checkbox.cjs.map +1 -1
  20. package/dist/components/input/Checkbox.mjs +91 -75
  21. package/dist/components/input/Checkbox.mjs.map +1 -1
  22. package/dist/components/input/ColorArea.cjs +1 -1
  23. package/dist/components/input/ColorArea.cjs.map +1 -1
  24. package/dist/components/input/ColorArea.mjs +18 -18
  25. package/dist/components/input/ColorArea.mjs.map +1 -1
  26. package/dist/components/input/ColorPicker.parts.cjs +1 -1
  27. package/dist/components/input/ColorPicker.parts.cjs.map +1 -1
  28. package/dist/components/input/ColorPicker.parts.mjs +1 -1
  29. package/dist/components/input/ColorPicker.parts.mjs.map +1 -1
  30. package/dist/components/input/Combobox.cjs +1 -1
  31. package/dist/components/input/Combobox.cjs.map +1 -1
  32. package/dist/components/input/Combobox.expand.cjs +1 -1
  33. package/dist/components/input/Combobox.expand.cjs.map +1 -1
  34. package/dist/components/input/Combobox.expand.mjs +20 -20
  35. package/dist/components/input/Combobox.expand.mjs.map +1 -1
  36. package/dist/components/input/Combobox.mjs +219 -138
  37. package/dist/components/input/Combobox.mjs.map +1 -1
  38. package/dist/components/input/Combobox.text.cjs +2 -0
  39. package/dist/components/input/Combobox.text.cjs.map +1 -0
  40. package/dist/components/input/Combobox.text.mjs +24 -0
  41. package/dist/components/input/Combobox.text.mjs.map +1 -0
  42. package/dist/components/input/DatePicker.cjs +1 -1
  43. package/dist/components/input/DatePicker.cjs.map +1 -1
  44. package/dist/components/input/DatePicker.mjs +205 -286
  45. package/dist/components/input/DatePicker.mjs.map +1 -1
  46. package/dist/components/input/Dropdown.cjs +1 -1
  47. package/dist/components/input/Dropdown.cjs.map +1 -1
  48. package/dist/components/input/Dropdown.mjs +137 -105
  49. package/dist/components/input/Dropdown.mjs.map +1 -1
  50. package/dist/components/input/Field.cjs +1 -1
  51. package/dist/components/input/Field.cjs.map +1 -1
  52. package/dist/components/input/Field.mjs +99 -66
  53. package/dist/components/input/Field.mjs.map +1 -1
  54. package/dist/components/input/Input.cjs +1 -1
  55. package/dist/components/input/Input.cjs.map +1 -1
  56. package/dist/components/input/Input.mjs +79 -55
  57. package/dist/components/input/Input.mjs.map +1 -1
  58. package/dist/components/input/Label.cjs +1 -1
  59. package/dist/components/input/Label.cjs.map +1 -1
  60. package/dist/components/input/Label.mjs +19 -13
  61. package/dist/components/input/Label.mjs.map +1 -1
  62. package/dist/components/input/Listbox.cjs +2 -0
  63. package/dist/components/input/Listbox.cjs.map +1 -0
  64. package/dist/components/input/Listbox.mjs +134 -0
  65. package/dist/components/input/Listbox.mjs.map +1 -0
  66. package/dist/components/input/Option.cjs +1 -1
  67. package/dist/components/input/Option.cjs.map +1 -1
  68. package/dist/components/input/Option.mjs +158 -97
  69. package/dist/components/input/Option.mjs.map +1 -1
  70. package/dist/components/input/Rating.cjs +1 -1
  71. package/dist/components/input/Rating.cjs.map +1 -1
  72. package/dist/components/input/Rating.item.cjs +2 -0
  73. package/dist/components/input/Rating.item.cjs.map +1 -0
  74. package/dist/components/input/Rating.item.mjs +255 -0
  75. package/dist/components/input/Rating.item.mjs.map +1 -0
  76. package/dist/components/input/Rating.mjs +150 -151
  77. package/dist/components/input/Rating.mjs.map +1 -1
  78. package/dist/components/input/SearchBox.cjs +1 -1
  79. package/dist/components/input/SearchBox.cjs.map +1 -1
  80. package/dist/components/input/SearchBox.mjs +146 -115
  81. package/dist/components/input/SearchBox.mjs.map +1 -1
  82. package/dist/components/input/Select.cjs +1 -1
  83. package/dist/components/input/Select.cjs.map +1 -1
  84. package/dist/components/input/Select.mjs +34 -25
  85. package/dist/components/input/Select.mjs.map +1 -1
  86. package/dist/components/input/Slider.cjs +1 -1
  87. package/dist/components/input/Slider.cjs.map +1 -1
  88. package/dist/components/input/Slider.mjs +77 -54
  89. package/dist/components/input/Slider.mjs.map +1 -1
  90. package/dist/components/input/SpinButton.cjs +1 -1
  91. package/dist/components/input/SpinButton.cjs.map +1 -1
  92. package/dist/components/input/SpinButton.mjs +198 -96
  93. package/dist/components/input/SpinButton.mjs.map +1 -1
  94. package/dist/components/input/SwatchPicker.cjs +1 -1
  95. package/dist/components/input/SwatchPicker.cjs.map +1 -1
  96. package/dist/components/input/SwatchPicker.context.cjs +2 -0
  97. package/dist/components/input/SwatchPicker.context.cjs.map +1 -0
  98. package/dist/components/input/SwatchPicker.context.mjs +35 -0
  99. package/dist/components/input/SwatchPicker.context.mjs.map +1 -0
  100. package/dist/components/input/SwatchPicker.mjs +131 -103
  101. package/dist/components/input/SwatchPicker.mjs.map +1 -1
  102. package/dist/components/input/SwatchPicker.swatches.cjs +2 -0
  103. package/dist/components/input/SwatchPicker.swatches.cjs.map +1 -0
  104. package/dist/components/input/SwatchPicker.swatches.mjs +207 -0
  105. package/dist/components/input/SwatchPicker.swatches.mjs.map +1 -0
  106. package/dist/components/input/Switch.cjs +1 -1
  107. package/dist/components/input/Switch.cjs.map +1 -1
  108. package/dist/components/input/Switch.mjs +71 -55
  109. package/dist/components/input/Switch.mjs.map +1 -1
  110. package/dist/components/input/TagPicker.cjs +1 -1
  111. package/dist/components/input/TagPicker.cjs.map +1 -1
  112. package/dist/components/input/TagPicker.mjs +154 -123
  113. package/dist/components/input/TagPicker.mjs.map +1 -1
  114. package/dist/components/input/Textarea.cjs +1 -1
  115. package/dist/components/input/Textarea.cjs.map +1 -1
  116. package/dist/components/input/Textarea.mjs +28 -21
  117. package/dist/components/input/Textarea.mjs.map +1 -1
  118. package/dist/components/input/TimePicker.cjs +1 -1
  119. package/dist/components/input/TimePicker.cjs.map +1 -1
  120. package/dist/components/input/TimePicker.mjs +204 -199
  121. package/dist/components/input/TimePicker.mjs.map +1 -1
  122. package/dist/components/input/dateUtils.cjs +1 -1
  123. package/dist/components/input/dateUtils.cjs.map +1 -1
  124. package/dist/components/input/dateUtils.mjs +114 -96
  125. package/dist/components/input/dateUtils.mjs.map +1 -1
  126. package/dist/components/input/fieldLabel.cjs +2 -0
  127. package/dist/components/input/fieldLabel.cjs.map +1 -0
  128. package/dist/components/input/fieldLabel.mjs +17 -0
  129. package/dist/components/input/fieldLabel.mjs.map +1 -0
  130. package/dist/components/input/inputLook.cjs +2 -0
  131. package/dist/components/input/inputLook.cjs.map +1 -0
  132. package/dist/components/input/inputLook.mjs +28 -0
  133. package/dist/components/input/inputLook.mjs.map +1 -0
  134. package/dist/components/input/pickerLabels.cjs +2 -0
  135. package/dist/components/input/pickerLabels.cjs.map +1 -0
  136. package/dist/components/input/pickerLabels.mjs +16 -0
  137. package/dist/components/input/pickerLabels.mjs.map +1 -0
  138. package/dist/components/input/pickerStyles.cjs +1 -1
  139. package/dist/components/input/pickerStyles.cjs.map +1 -1
  140. package/dist/components/input/pickerStyles.mjs +42 -4
  141. package/dist/components/input/pickerStyles.mjs.map +1 -1
  142. package/dist/components/input/pickerValues.cjs +2 -0
  143. package/dist/components/input/pickerValues.cjs.map +1 -0
  144. package/dist/components/input/pickerValues.mjs +24 -0
  145. package/dist/components/input/pickerValues.mjs.map +1 -0
  146. package/dist/components/navigation/Menu.popover.cjs +1 -1
  147. package/dist/components/navigation/Menu.popover.cjs.map +1 -1
  148. package/dist/components/navigation/Menu.popover.mjs +10 -10
  149. package/dist/components/navigation/Menu.root.cjs +1 -1
  150. package/dist/components/navigation/Menu.root.cjs.map +1 -1
  151. package/dist/components/navigation/Menu.root.mjs +10 -10
  152. package/dist/components/overlays/Popover.cjs +1 -1
  153. package/dist/components/overlays/Popover.cjs.map +1 -1
  154. package/dist/components/overlays/Popover.mjs +203 -208
  155. package/dist/components/overlays/Popover.mjs.map +1 -1
  156. package/dist/components/overlays/Popover.shared.cjs +1 -1
  157. package/dist/components/overlays/Popover.shared.cjs.map +1 -1
  158. package/dist/components/overlays/Popover.shared.mjs +82 -77
  159. package/dist/components/overlays/Popover.shared.mjs.map +1 -1
  160. package/dist/components/provider/WaveProvider.cjs +1 -1
  161. package/dist/components/provider/WaveProvider.cjs.map +1 -1
  162. package/dist/components/provider/WaveProvider.mjs +25 -19
  163. package/dist/components/provider/WaveProvider.mjs.map +1 -1
  164. package/dist/components/table/useGridNavigation.cjs +1 -1
  165. package/dist/components/table/useGridNavigation.cjs.map +1 -1
  166. package/dist/components/table/useGridNavigation.mjs +67 -70
  167. package/dist/components/table/useGridNavigation.mjs.map +1 -1
  168. package/dist/hooks/useActiveDescendant.cjs +2 -0
  169. package/dist/hooks/useActiveDescendant.cjs.map +1 -0
  170. package/dist/hooks/useActiveDescendant.mjs +56 -0
  171. package/dist/hooks/useActiveDescendant.mjs.map +1 -0
  172. package/dist/hooks/useFieldControl.cjs.map +1 -1
  173. package/dist/hooks/useFieldControl.mjs.map +1 -1
  174. package/dist/hooks/useListbox.cjs +1 -1
  175. package/dist/hooks/useListbox.cjs.map +1 -1
  176. package/dist/hooks/useListbox.mjs +250 -210
  177. package/dist/hooks/useListbox.mjs.map +1 -1
  178. package/dist/hooks/useRovingGrid.cjs +2 -0
  179. package/dist/hooks/useRovingGrid.cjs.map +1 -0
  180. package/dist/hooks/useRovingGrid.mjs +69 -0
  181. package/dist/hooks/useRovingGrid.mjs.map +1 -0
  182. package/dist/index.cjs +1 -1
  183. package/dist/index.d.cts +2901 -203
  184. package/dist/index.d.ts +2901 -203
  185. package/dist/index.mjs +107 -99
  186. package/dist/lib/classNames.cjs +1 -1
  187. package/dist/lib/classNames.cjs.map +1 -1
  188. package/dist/lib/classNames.mjs +111 -67
  189. package/dist/lib/classNames.mjs.map +1 -1
  190. package/dist/lib/date.cjs +2 -0
  191. package/dist/lib/date.cjs.map +1 -0
  192. package/dist/lib/date.mjs +44 -0
  193. package/dist/lib/date.mjs.map +1 -0
  194. package/dist/lib/focus.cjs +1 -1
  195. package/dist/lib/focus.cjs.map +1 -1
  196. package/dist/lib/focus.mjs +5 -2
  197. package/dist/lib/focus.mjs.map +1 -1
  198. package/dist/lib/layers.cjs.map +1 -1
  199. package/dist/lib/layers.mjs.map +1 -1
  200. package/dist/lib/styles.cjs +1 -1
  201. package/dist/lib/styles.cjs.map +1 -1
  202. package/dist/lib/styles.mjs +21 -4
  203. package/dist/lib/styles.mjs.map +1 -1
  204. package/dist/styles.css +1 -1
  205. package/package.json +1 -1
  206. package/src/styles/tokens.css +13 -0
@@ -1 +1 @@
1
- {"version":3,"file":"useGridNavigation.mjs","names":["React","NON_TEXT_INPUT_TYPES","KEY_CONSUMING_ROLES","COMPOSITE_SELECTOR","role","WIDGET_CANDIDATE_SELECTOR","FOCUSABLE_SELECTOR","OBSERVED_ATTRIBUTES","isTextEntryElement","tag","el","editable","isNestedComposite","isInsideNestedComposite","node","grid","getCompositeEntry","stop","composite","isFocusable","getFirstTabbable","isLikelyFocusable","hasCheckedPeer","radio","root","checked","other","isCellElement","isFocused","getCell","getRows","row","MAX_COL_SPAN","getColumnMap","indexOf","rows","index","map","cover","offset","colSpan","rowIndex","column","cell","own","remaining","next","covered","getCellAtColumn","rowMap","collectCells","into","useGridNavigation","pageSize","options","setGrid","activeCellRef","interactionCellRef","stamped","written","retained","pageSizeRef","getWidgets","widgets","precise","tabIndex","getTarget","element","checkRadioGroup","setTabIndex","value","syncCell","isActive","isInteraction","widget","target","keepCell","isStop","sync","table","active","targets","previous","interaction","cells","observer","relevant","all","dirty","record","records","activate","previousActive","previousInteraction","focusCell","onKeyDown","event","widgetTarget","onCell","entry","columnIndex","lastRow","ctrl","vertical","getArrowIntent","getDirection","destination","columns","candidate","nextRow","nextColumn","rowCells","onFocus","onBlur","left"],"sources":["../../../src/components/table/useGridNavigation.ts"],"sourcesContent":["import * as React from 'react';\nimport { getArrowIntent, getDirection } from '../../lib/direction';\nimport { FOCUSABLE_SELECTOR, getFirstTabbable, isFocusable } from '../../lib/focus';\n\n/** Options of {@link useGridNavigation}. */\nexport interface UseGridNavigationOptions {\n /**\n * How many rows PageUp and PageDown move focus.\n * @default 10\n */\n pageSize?: number;\n}\n\n/** Handlers {@link useGridNavigation} needs on the grid element (compose them with the consumer's). */\nexport interface GridNavigationHandlers {\n onKeyDown: React.KeyboardEventHandler<HTMLTableElement>;\n onFocus: React.FocusEventHandler<HTMLTableElement>;\n onBlur: React.FocusEventHandler<HTMLTableElement>;\n}\n\n/** Result of {@link useGridNavigation}. */\nexport interface UseGridNavigationResult {\n /** Callback ref for the `<table role=\"grid\">` element. Stable identity. */\n gridRef: React.RefCallback<HTMLTableElement>;\n /** Keyboard and focus handlers for the grid element. Stable identities. */\n gridProps: GridNavigationHandlers;\n}\n\n/** Input types that do not take arrow or character keys, so a lone one can be a cell's focus target. */\nconst NON_TEXT_INPUT_TYPES: ReadonlySet<string> = new Set([\n 'checkbox',\n 'radio',\n 'button',\n 'submit',\n 'reset',\n 'image',\n 'file',\n 'color',\n]);\n\n/** Widget roles that use arrow or character keys themselves (treated like text entry). */\nconst KEY_CONSUMING_ROLES: ReadonlySet<string> = new Set([\n 'textbox',\n 'searchbox',\n 'combobox',\n 'spinbutton',\n 'slider',\n]);\n\n/**\n * Roles of composite widgets that manage their own children (and usually their tab indexes): an\n * element with one of them, or with `data-roving-container`, is a nested composite. Close to the\n * list of `useRovingTabIndex`, which is kept separately and differs on purpose: here a `toolbar`\n * is a composite (a toolbar in a cell is one widget), while `spinbutton` is not (it is one\n * focusable control that uses the arrow keys, handled by `KEY_CONSUMING_ROLES` like a text\n * field). Keep both lists in mind when adding a role.\n */\nconst COMPOSITE_ROLES: readonly string[] = [\n 'toolbar',\n 'radiogroup',\n 'listbox',\n 'grid',\n 'treegrid',\n 'tablist',\n 'menu',\n 'menubar',\n 'tree',\n];\n\nconst COMPOSITE_SELECTOR = [\n '[data-roving-container]',\n ...COMPOSITE_ROLES.map((role) => `[role=\"${role}\"]`),\n].join(', ');\n\n/** Every element that can be a cell widget: a focusable element or a nested composite root. */\nconst WIDGET_CANDIDATE_SELECTOR = `${FOCUSABLE_SELECTOR}, ${COMPOSITE_SELECTOR}`;\n\n/**\n * Attributes whose changes can change a cell's widgets or the grid's rows. `aria-selected` on a row\n * signals a selection change, which can change the active cell's target (radio groups).\n */\nconst OBSERVED_ATTRIBUTES = [\n 'disabled',\n 'tabindex',\n 'hidden',\n 'inert',\n 'type',\n 'contenteditable',\n 'role',\n 'data-roving-container',\n 'aria-selected',\n];\n\n/**\n * Whether `el` is a text-entry control (or another widget that uses arrow keys itself): a text-like\n * `input`, `textarea`, `select`, a `contenteditable` element, or an element with role textbox,\n * searchbox, combobox, spinbutton or slider. Such widgets are never a cell's navigation target: the\n * cell takes focus and Enter/F2 moves into the widget, so the arrow keys keep moving the caret.\n */\nexport function isTextEntryElement(el: Element): boolean {\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.getAttribute('contenteditable');\n if (editable !== null && editable !== 'false') return true;\n const role = el.getAttribute('role');\n return role !== null && KEY_CONSUMING_ROLES.has(role);\n}\n\n/** Whether `el` is a nested composite widget (own roving container or a composite role). */\nfunction isNestedComposite(el: Element): boolean {\n return el.matches(COMPOSITE_SELECTOR);\n}\n\n/** Whether `node` is strictly inside a nested composite of `grid` (the composite root excluded). */\nfunction isInsideNestedComposite(node: Element, grid: HTMLTableElement): boolean {\n if (node === grid) return false;\n for (let el = node.parentElement; el && el !== grid; el = el.parentElement) {\n if (isNestedComposite(el)) return true;\n }\n return false;\n}\n\n/**\n * The element a nested composite is entered at: its own tab stop (`tabindex=\"0\"`), else its first\n * tabbable element (the root itself when it is the focusable element).\n */\nfunction getCompositeEntry(composite: HTMLElement): HTMLElement | null {\n const stop = composite.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (stop && isFocusable(stop)) return stop;\n return getFirstTabbable(composite, { includeContainer: true });\n}\n\n/**\n * A cheap focusability check for cells that do not hold the tab stop: no style reads, only the\n * attributes that take an element out of the focus order. The cell that holds (or receives) the tab\n * stop is checked precisely with `isFocusable`.\n */\nfunction isLikelyFocusable(el: HTMLElement): boolean {\n if (el.localName === 'input' && (el as HTMLInputElement).type === 'hidden') return false;\n if (el.matches(':disabled')) return false;\n return el.closest('[hidden], [inert]') === null;\n}\n\n/** Whether another radio of `radio`'s named group is checked (the browser's tab stop of the group). */\nfunction hasCheckedPeer(radio: HTMLInputElement): boolean {\n if (!radio.name) return false;\n const root = radio.getRootNode() as Document | ShadowRoot;\n if (typeof root.querySelectorAll !== 'function') return false;\n const checked = root.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]:checked');\n for (const other of Array.from(checked)) {\n if (other !== radio && other.name === radio.name && other.form === radio.form) return true;\n }\n return false;\n}\n\nfunction isCellElement(el: Element): el is HTMLTableCellElement {\n return el.localName === 'td' || el.localName === 'th';\n}\n\n/** Whether `el` is the focused element of its document (or shadow root). */\nfunction isFocused(el: Element): boolean {\n return (el.getRootNode() as Partial<DocumentOrShadowRoot>).activeElement === el;\n}\n\n/** The cell of `grid` that contains `node` (a nested table's cells belong to the outer cell). */\nfunction getCell(grid: HTMLTableElement, node: EventTarget | null): HTMLTableCellElement | null {\n let el: Element | null = node instanceof Element ? node : null;\n while (el && el !== grid) {\n if (isCellElement(el) && el.closest('table') === grid) return el;\n el = el.parentElement;\n }\n return null;\n}\n\n/** The rows of `grid` (header, body and footer rows; a nested table's rows are not included). */\nfunction getRows(grid: HTMLTableElement): HTMLTableRowElement[] {\n return Array.from(grid.rows).filter((row) => row.cells.length > 0 && !row.hidden);\n}\n\n/** Browsers clamp `colSpan` to 1..1000 (`rowSpan` 0 covers the rest of its row group). */\nconst MAX_COL_SPAN = 1000;\n\n/** Per row, the cell covering each visual column (see {@link getColumnMap}). */\ntype ColumnMap = ReadonlyArray<ReadonlyArray<HTMLTableCellElement | undefined>>;\n\n/**\n * The HTML table model of `rows`: per row, the cell covering each visual column, with `colSpan` and\n * `rowSpan` counted. A row span covers the next rows of its row group in the DOM (a hidden row\n * counts, but gets no entry). A column no cell covers is empty. O(number of cells).\n */\nfunction getColumnMap(rows: readonly HTMLTableRowElement[]): ColumnMap {\n const indexOf = new Map<Element, number>(rows.map((row, index) => [row, index]));\n const map: Array<Array<HTMLTableCellElement | undefined>> = rows.map(() => []);\n const cover = (rowIndex: number, column: number, colSpan: number, cell: HTMLTableCellElement) => {\n for (let offset = 0; offset < colSpan; offset += 1) map[rowIndex][column + offset] = cell;\n };\n rows.forEach((row, rowIndex) => {\n const own = map[rowIndex];\n let column = 0;\n for (const cell of Array.from(row.cells)) {\n while (own[column]) column += 1;\n const colSpan = Math.min(Math.max(cell.colSpan, 1), MAX_COL_SPAN);\n cover(rowIndex, column, colSpan, cell);\n let remaining = cell.rowSpan === 0 ? Infinity : cell.rowSpan - 1;\n for (\n let next = row.nextElementSibling;\n next && remaining > 0;\n next = next.nextElementSibling\n ) {\n if (next.localName !== 'tr') continue;\n remaining -= 1;\n const covered = indexOf.get(next);\n if (covered !== undefined) cover(covered, column, colSpan, cell);\n }\n column += colSpan;\n }\n });\n return map;\n}\n\n/** The cell of a row (as {@link getColumnMap} maps it) at `column`, else the last one before it. */\nfunction getCellAtColumn(\n rowMap: ReadonlyArray<HTMLTableCellElement | undefined>,\n column: number,\n): HTMLTableCellElement | null {\n for (let index = Math.min(column, rowMap.length - 1); index >= 0; index -= 1) {\n const cell = rowMap[index];\n if (cell) return cell;\n }\n return null;\n}\n\n/** The grid cells in `node` (itself, or its descendants that belong to `grid`). */\nfunction collectCells(grid: HTMLTableElement, node: Node, into: Set<HTMLTableCellElement>): void {\n if (!(node instanceof Element)) return;\n if (isCellElement(node) && node.closest('table') === grid) into.add(node);\n for (const el of Array.from(node.querySelectorAll<HTMLTableCellElement>('td, th'))) {\n if (el.closest('table') === grid) into.add(el);\n }\n}\n\n/** A widget of a cell: a focusable element, or a nested composite counted as one widget. */\ninterface CellWidget {\n /** The focusable element, or the nested composite's root. */\n element: HTMLElement;\n /** A nested composite: the grid never writes `tabindex` on or inside it. */\n composite: boolean;\n}\n\n/**\n * Grid keyboard model of the APG Grid pattern for a `<table role=\"grid\">` (DataGrid).\n *\n * - **One tab stop.** Exactly one cell of the grid is in the tab order (initially the first cell).\n * Its focus target is its only focusable widget when that widget is not a text-entry control\n * (a checkbox, a sort button, a link); otherwise — no widget, several widgets, a text-entry\n * control or a nested composite — the cell itself. An unchecked radio whose named group has a\n * checked radio is not a target either (browsers skip it in the Tab order), so its cell is.\n * Every other cell and every widget inside a cell gets `tabindex=\"-1\"`. Rows are never focusable.\n * When a widget becomes the target of the focused cell (Space on the cell checks its radio), the\n * cell keeps focus and the tab stop (removing its `tabindex` would blur it) while its target stays\n * at `tabindex=\"-1\"`; the target takes the tab stop once the cell is blurred.\n * - **Navigation mode** (focus on a cell or on its target widget): ArrowLeft/ArrowRight (the\n * previous/next cell of the row, mirrored in RTL), ArrowUp/ArrowDown, Home/End (first/last cell\n * of the row), Ctrl+Home/Ctrl+End (first cell of the grid / last cell of the last row) and\n * PageUp/PageDown ({@link UseGridNavigationOptions.pageSize} rows) move focus between cells.\n * ArrowUp/ArrowDown and PageUp/PageDown keep the visual column (`colSpan` and `rowSpan` counted,\n * so a grouped header works), clamped to a shorter row. Space and Enter on a target widget reach\n * the widget.\n * - **Interaction mode**: Enter or F2 on a cell that is its own target moves focus into its first\n * widget; clicking a widget that is not the cell's target (a text input) enters it too. The\n * grid keys are then ignored, so the arrow keys move the caret, and Tab moves between the cell's\n * widgets. Escape returns focus to the cell. Leaving the grid ends interaction mode.\n * - **Nested composites** (an element with `data-roving-container` or role toolbar, radiogroup,\n * listbox, grid, treegrid, tablist, menu, menubar or tree) count as one widget that uses the arrow\n * keys: its cell takes focus, and Enter/F2 enters the composite at its own tab stop. The grid never\n * writes `tabindex` on or inside a composite (the composite manages its own), so a composite\n * keeps its own Tab stop — the same documented limitation as `useRovingTabIndex`.\n *\n * Keys are ignored when the event's default was prevented (a widget that handled the key, a\n * consumer handler), or with Alt/Meta held. The tab indexes are written to the DOM (layout effect\n * plus a `MutationObserver`), so rows and widgets added later are covered without a re-render. Focus\n * moves re-sync only the cells involved, the observer only the cells that changed, and the grid's\n * own `tabindex` writes do not trigger a re-sync.\n *\n * Internal to the table package.\n */\nexport function useGridNavigation(options: UseGridNavigationOptions = {}): UseGridNavigationResult {\n const pageSize = Math.max(1, options.pageSize ?? 10);\n const [grid, setGrid] = React.useState<HTMLTableElement | null>(null);\n /** The cell that holds the tab stop. */\n const activeCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** The cell whose widgets are being interacted with, or `null` (navigation mode). */\n const interactionCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** Elements whose `tabindex` this hook wrote. */\n const [stamped] = React.useState(() => new WeakSet<Element>());\n /** The `tabindex` value this hook last wrote per element (`null`: removed), to skip own records. */\n const [written] = React.useState(() => new WeakMap<Element, string | null>());\n /**\n * Cells kept focusable (holding the tab stop, their target at -1) because they were focused when\n * a widget became their target (a radio that got checked, a widget added): they lose the\n * attribute once blurred.\n */\n const [retained] = React.useState(() => new WeakSet<Element>());\n const pageSizeRef = React.useRef(pageSize);\n\n React.useLayoutEffect(() => {\n pageSizeRef.current = pageSize;\n }, [pageSize]);\n\n /**\n * The widgets of `cell` in DOM order. `precise` checks focusability with `isFocusable` (style\n * reads); otherwise only attributes are checked (cells that do not hold the tab stop).\n */\n const getWidgets = React.useCallback(\n (cell: HTMLTableCellElement, precise: boolean): CellWidget[] => {\n const widgets: CellWidget[] = [];\n let composite: HTMLElement | null = null;\n for (const el of Array.from(cell.querySelectorAll<HTMLElement>(WIDGET_CANDIDATE_SELECTOR))) {\n // Descendants of a nested composite belong to it (candidates come in document order).\n if (composite && composite.contains(el)) continue;\n if (isNestedComposite(el)) {\n composite = el;\n const available = precise ? getCompositeEntry(el) !== null : isLikelyFocusable(el);\n if (available) widgets.push({ element: el, composite: true });\n continue;\n }\n const tabIndex = el.getAttribute('tabindex');\n // An author tabindex=\"-1\" (not written by this hook) opts the element out.\n if (tabIndex !== null && Number(tabIndex) < 0 && !stamped.has(el)) continue;\n if (precise ? isFocusable(el) : isLikelyFocusable(el)) {\n widgets.push({ element: el, composite: false });\n }\n }\n return widgets;\n },\n [stamped],\n );\n\n /**\n * The element of `cell` that takes focus in navigation mode. `checkRadioGroup` applies the radio\n * group rule, which only decides where the tab stop goes (the cell that holds it); a focused\n * lone radio is in navigation mode either way.\n */\n const getTarget = React.useCallback(\n (cell: HTMLTableCellElement, widgets: CellWidget[], checkRadioGroup: boolean): HTMLElement => {\n if (widgets.length !== 1) return cell;\n const [{ element, composite }] = widgets;\n if (composite || isTextEntryElement(element)) return cell;\n if (\n checkRadioGroup &&\n element instanceof HTMLInputElement &&\n element.type === 'radio' &&\n !element.checked &&\n hasCheckedPeer(element)\n ) {\n return cell;\n }\n return element;\n },\n [],\n );\n\n const setTabIndex = React.useCallback(\n (el: HTMLElement, value: 0 | -1) => {\n const next = String(value);\n if (el.getAttribute('tabindex') !== next) {\n written.set(el, next);\n el.setAttribute('tabindex', next);\n }\n stamped.add(el);\n },\n [stamped, written],\n );\n\n /** Writes the tab indexes of one cell and its widgets (only attributes that change). */\n const syncCell = React.useCallback(\n (cell: HTMLTableCellElement, isActive: boolean, isInteraction: boolean) => {\n const widgets = getWidgets(cell, isActive);\n if (isInteraction) {\n retained.delete(cell);\n setTabIndex(cell, -1);\n for (const widget of widgets) {\n if (!widget.composite) setTabIndex(widget.element, 0);\n }\n return;\n }\n const target = getTarget(cell, widgets, isActive);\n /** The focused cell stands in for its target until it is blurred (see below). */\n let keepCell = false;\n if (target === cell) {\n retained.delete(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell) && isFocused(cell)) {\n // A widget became the focused cell's target (its radio got checked, a widget was added).\n // Removing the tabindex of the focused element blurs it (browsers move focus to the body,\n // which ends the grid session), so the cell stays focusable and stays the grid's position.\n // It keeps the tab stop (`tabindex=\"0\"`, when active) and its target stays at -1: the grid\n // still has exactly one Tab stop even if focus is lost without a focusout, and Tab and\n // Shift+Tab from the cell leave the grid (nothing after or before it inside is tabbable).\n // Once the cell is blurred, onBlur re-syncs it and the target takes the tab stop.\n keepCell = true;\n retained.add(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell)) {\n // The cell's widget is its target: the cell itself is not focusable.\n retained.delete(cell);\n stamped.delete(cell);\n if (cell.hasAttribute('tabindex')) {\n written.set(cell, null);\n cell.removeAttribute('tabindex');\n }\n }\n for (const widget of widgets) {\n if (widget.composite) continue;\n const isStop = widget.element === target && isActive && !keepCell;\n setTabIndex(widget.element, isStop ? 0 : -1);\n }\n },\n [getTarget, getWidgets, retained, setTabIndex, stamped, written],\n );\n\n /**\n * Re-resolves the active cell (the first cell when it is gone) and syncs `cells` — every cell\n * when omitted. A new active cell is always synced.\n */\n const sync = React.useCallback(\n (table: HTMLTableElement, cells?: Iterable<HTMLTableCellElement | null>) => {\n const rows = getRows(table);\n let active = activeCellRef.current;\n const targets = new Set<HTMLTableCellElement>();\n if (\n !active ||\n !active.isConnected ||\n !rows.includes(active.parentElement as HTMLTableRowElement)\n ) {\n active = rows[0]?.cells[0] ?? null;\n activeCellRef.current = active;\n if (active) targets.add(active);\n }\n if (interactionCellRef.current !== null && interactionCellRef.current !== active) {\n const previous = interactionCellRef.current;\n interactionCellRef.current = null;\n targets.add(previous);\n }\n const interaction = interactionCellRef.current;\n\n if (cells === undefined) {\n for (const row of rows) for (const cell of Array.from(row.cells)) targets.add(cell);\n } else {\n for (const cell of cells) if (cell) targets.add(cell);\n }\n for (const cell of targets) {\n if (!cell.isConnected || getCell(table, cell) !== cell) continue;\n syncCell(cell, cell === active, cell === interaction);\n }\n },\n [syncCell],\n );\n\n React.useLayoutEffect(() => {\n if (!grid) return;\n sync(grid);\n if (typeof MutationObserver === 'undefined') return;\n const observer = new MutationObserver((records) => {\n let relevant = false;\n let all = false;\n const dirty = new Set<HTMLTableCellElement>();\n for (const record of records) {\n const target = record.target;\n if (!(target instanceof Element)) continue;\n // A nested composite manages its own items (and their tab indexes).\n if (isInsideNestedComposite(target, grid)) continue;\n if (record.type === 'attributes') {\n // The record of one of our own writes.\n if (\n record.attributeName === 'tabindex' &&\n written.has(target) &&\n written.get(target) === target.getAttribute('tabindex')\n ) {\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) dirty.add(cell);\n else if (target.localName === 'tr') collectCells(grid, target, dirty);\n else all = true;\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) {\n dirty.add(cell);\n continue;\n }\n // Rows, sections or cells added (removed ones need no sync; `sync` re-resolves the\n // active cell when it went away).\n for (const node of Array.from(record.addedNodes)) collectCells(grid, node, dirty);\n }\n if (!relevant) return;\n // The active cell's target can depend on other cells (a radio group whose checked radio\n // changed, reflected by the rows' `aria-selected`), so it is always re-checked.\n if (activeCellRef.current) dirty.add(activeCellRef.current);\n sync(grid, all ? undefined : dirty);\n });\n observer.observe(grid, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n return () => observer.disconnect();\n }, [grid, sync, written]);\n\n /**\n * Makes `cell` the active cell (in interaction mode when `interaction`) and re-syncs only the\n * cells involved: the previous active and interaction cells and `cell`.\n */\n const activate = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement, interaction: boolean) => {\n const previousActive = activeCellRef.current;\n const previousInteraction = interactionCellRef.current;\n activeCellRef.current = cell;\n interactionCellRef.current = interaction ? cell : null;\n sync(table, [previousActive, previousInteraction, cell]);\n },\n [sync],\n );\n\n const focusCell = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement) => {\n activate(table, cell, false);\n getTarget(cell, getWidgets(cell, true), true).focus();\n },\n [activate, getTarget, getWidgets],\n );\n\n const onKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n if (event.defaultPrevented) return;\n const cell = getCell(table, event.target);\n if (!cell) return;\n const widgets = getWidgets(cell, true);\n // A lone non-text widget is in navigation mode when focused, even an unchecked radio that\n // the radio group rule keeps from holding the tab stop.\n const widgetTarget = getTarget(cell, widgets, false);\n const onCell = event.target === cell;\n\n if (!onCell && event.target !== widgetTarget) {\n // Interaction mode: the widget keeps its keys; Escape returns to the cell.\n if (event.key === 'Escape') {\n event.preventDefault();\n focusCell(table, cell);\n }\n return;\n }\n if (event.altKey || event.metaKey) return;\n\n if ((event.key === 'Enter' || event.key === 'F2') && onCell) {\n let entry: HTMLElement | null = null;\n for (const widget of widgets) {\n entry = widget.composite ? getCompositeEntry(widget.element) : widget.element;\n if (entry) break;\n }\n if (!entry) return;\n event.preventDefault();\n activate(table, cell, true);\n entry.focus();\n return;\n }\n\n const rows = getRows(table);\n const rowIndex = rows.findIndex((row) => row === cell.parentElement);\n if (rowIndex < 0) return;\n const columnIndex = Array.from(rows[rowIndex].cells).indexOf(cell);\n const lastRow = rows.length - 1;\n const ctrl = event.ctrlKey;\n /** Left/Right, Home/End: target row and cell index in it (`Infinity`: the row's last cell). */\n let next: [row: number, column: number] | null = null;\n /** Up/Down, PageUp/PageDown: the first row to try and the direction to keep going in. */\n let vertical: [row: number, step: 1 | -1] | null = null;\n\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (ctrl) break;\n const intent = getArrowIntent(event.key, {\n orientation: 'horizontal',\n dir: getDirection(table),\n });\n next = [rowIndex, columnIndex + (intent === 'next' ? 1 : -1)];\n break;\n }\n case 'ArrowDown':\n if (!ctrl) vertical = [rowIndex + 1, 1];\n break;\n case 'ArrowUp':\n if (!ctrl) vertical = [rowIndex - 1, -1];\n break;\n case 'Home':\n next = ctrl ? [0, 0] : [rowIndex, 0];\n break;\n case 'End':\n next = ctrl ? [lastRow, Infinity] : [rowIndex, Infinity];\n break;\n case 'PageDown':\n if (!ctrl) vertical = [Math.min(rowIndex + pageSizeRef.current, lastRow), 1];\n break;\n case 'PageUp':\n if (!ctrl) vertical = [Math.max(rowIndex - pageSizeRef.current, 0), -1];\n break;\n default:\n break;\n }\n if (!next && !vertical) return;\n // A handled key never scrolls the page or reaches the widget, even at the grid's edge.\n event.preventDefault();\n let destination: HTMLTableCellElement | null = null;\n if (vertical) {\n // Up/Down keep the visual column (spans counted), clamped to a shorter row; the rows a\n // row-spanning cell covers are skipped when moving away from it.\n const columns = getColumnMap(rows);\n const column = Math.max(columns[rowIndex].indexOf(cell), 0);\n for (let row = vertical[0]; row >= 0 && row <= lastRow; row += vertical[1]) {\n const candidate = getCellAtColumn(columns[row], column);\n if (candidate && candidate !== cell) {\n destination = candidate;\n break;\n }\n }\n } else if (next) {\n // Left/Right stop at the row's edges (no cell there).\n const [nextRow, nextColumn] = next;\n const rowCells = rows[nextRow].cells;\n destination =\n nextColumn === Infinity ? rowCells[rowCells.length - 1] : rowCells.item(nextColumn);\n }\n if (destination && destination !== cell) focusCell(table, destination);\n },\n [activate, focusCell, getTarget, getWidgets],\n );\n\n const onFocus = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the focused descendant.\n const target: EventTarget = event.target;\n const cell = getCell(table, target);\n if (!cell) return;\n const interaction =\n target !== cell && target !== getTarget(cell, getWidgets(cell, true), false);\n activate(table, cell, interaction);\n },\n [activate, getTarget, getWidgets],\n );\n\n const onBlur = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the blurred descendant.\n const left: EventTarget = event.target;\n const cells: HTMLTableCellElement[] = [];\n // A cell kept focusable while focused (see syncCell) is re-synced now that it is blurred\n // (during focusout the blurred element is no longer the active element).\n if (left instanceof Element && isCellElement(left) && retained.has(left)) cells.push(left);\n const next = event.relatedTarget;\n const interaction = interactionCellRef.current;\n if (interaction !== null && !(next instanceof Node && table.contains(next))) {\n interactionCellRef.current = null;\n cells.push(interaction);\n }\n if (cells.length > 0) sync(table, cells);\n },\n [retained, sync],\n );\n\n const gridProps = React.useMemo<GridNavigationHandlers>(\n () => ({ onKeyDown, onFocus, onBlur }),\n [onBlur, onFocus, onKeyDown],\n );\n\n return { gridRef: setGrid, gridProps };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useGridNavigation.mjs","names":["React","NON_TEXT_INPUT_TYPES","KEY_CONSUMING_ROLES","COMPOSITE_SELECTOR","role","WIDGET_CANDIDATE_SELECTOR","FOCUSABLE_SELECTOR","OBSERVED_ATTRIBUTES","isTextEntryElement","tag","el","editable","isNestedComposite","isInsideNestedComposite","node","grid","getCompositeEntry","stop","composite","isFocusable","getFirstTabbable","isLikelyFocusable","hasCheckedPeer","radio","root","checked","other","isCellElement","getCell","getRows","row","MAX_COL_SPAN","getColumnMap","indexOf","rows","index","map","cover","offset","colSpan","rowIndex","column","cell","own","remaining","next","covered","getCellAtColumn","rowMap","collectCells","into","useGridNavigation","pageSize","options","setGrid","activeCellRef","interactionCellRef","stamped","written","retained","pageSizeRef","getWidgets","widgets","precise","tabIndex","getTarget","element","checkRadioGroup","setTabIndex","value","syncCell","isActive","isInteraction","widget","target","keepCell","isFocused","isStop","sync","table","active","targets","previous","interaction","cells","observer","relevant","all","dirty","record","records","activate","previousActive","previousInteraction","focusCell","onKeyDown","event","widgetTarget","onCell","entry","columnIndex","lastRow","ctrl","vertical","getArrowIntent","getDirection","destination","columns","candidate","nextRow","nextColumn","rowCells","onFocus","onBlur","left"],"sources":["../../../src/components/table/useGridNavigation.ts"],"sourcesContent":["import * as React from 'react';\nimport { getArrowIntent, getDirection } from '../../lib/direction';\nimport { FOCUSABLE_SELECTOR, getFirstTabbable, isFocusable, isFocused } from '../../lib/focus';\n\n/** Options of {@link useGridNavigation}. */\nexport interface UseGridNavigationOptions {\n /**\n * How many rows PageUp and PageDown move focus.\n * @default 10\n */\n pageSize?: number;\n}\n\n/** Handlers {@link useGridNavigation} needs on the grid element (compose them with the consumer's). */\nexport interface GridNavigationHandlers {\n onKeyDown: React.KeyboardEventHandler<HTMLTableElement>;\n onFocus: React.FocusEventHandler<HTMLTableElement>;\n onBlur: React.FocusEventHandler<HTMLTableElement>;\n}\n\n/** Result of {@link useGridNavigation}. */\nexport interface UseGridNavigationResult {\n /** Callback ref for the `<table role=\"grid\">` element. Stable identity. */\n gridRef: React.RefCallback<HTMLTableElement>;\n /** Keyboard and focus handlers for the grid element. Stable identities. */\n gridProps: GridNavigationHandlers;\n}\n\n/** Input types that do not take arrow or character keys, so a lone one can be a cell's focus target. */\nconst NON_TEXT_INPUT_TYPES: ReadonlySet<string> = new Set([\n 'checkbox',\n 'radio',\n 'button',\n 'submit',\n 'reset',\n 'image',\n 'file',\n 'color',\n]);\n\n/** Widget roles that use arrow or character keys themselves (treated like text entry). */\nconst KEY_CONSUMING_ROLES: ReadonlySet<string> = new Set([\n 'textbox',\n 'searchbox',\n 'combobox',\n 'spinbutton',\n 'slider',\n]);\n\n/**\n * Roles of composite widgets that manage their own children (and usually their tab indexes): an\n * element with one of them, or with `data-roving-container`, is a nested composite. Close to the\n * list of `useRovingTabIndex`, which is kept separately and differs on purpose: here a `toolbar`\n * is a composite (a toolbar in a cell is one widget), while `spinbutton` is not (it is one\n * focusable control that uses the arrow keys, handled by `KEY_CONSUMING_ROLES` like a text\n * field). Keep both lists in mind when adding a role.\n */\nconst COMPOSITE_ROLES: readonly string[] = [\n 'toolbar',\n 'radiogroup',\n 'listbox',\n 'grid',\n 'treegrid',\n 'tablist',\n 'menu',\n 'menubar',\n 'tree',\n];\n\nconst COMPOSITE_SELECTOR = [\n '[data-roving-container]',\n ...COMPOSITE_ROLES.map((role) => `[role=\"${role}\"]`),\n].join(', ');\n\n/** Every element that can be a cell widget: a focusable element or a nested composite root. */\nconst WIDGET_CANDIDATE_SELECTOR = `${FOCUSABLE_SELECTOR}, ${COMPOSITE_SELECTOR}`;\n\n/**\n * Attributes whose changes can change a cell's widgets or the grid's rows. `aria-selected` on a row\n * signals a selection change, which can change the active cell's target (radio groups).\n */\nconst OBSERVED_ATTRIBUTES = [\n 'disabled',\n 'tabindex',\n 'hidden',\n 'inert',\n 'type',\n 'contenteditable',\n 'role',\n 'data-roving-container',\n 'aria-selected',\n];\n\n/**\n * Whether `el` is a text-entry control (or another widget that uses arrow keys itself): a text-like\n * `input`, `textarea`, `select`, a `contenteditable` element, or an element with role textbox,\n * searchbox, combobox, spinbutton or slider. Such widgets are never a cell's navigation target: the\n * cell takes focus and Enter/F2 moves into the widget, so the arrow keys keep moving the caret.\n */\nexport function isTextEntryElement(el: Element): boolean {\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.getAttribute('contenteditable');\n if (editable !== null && editable !== 'false') return true;\n const role = el.getAttribute('role');\n return role !== null && KEY_CONSUMING_ROLES.has(role);\n}\n\n/** Whether `el` is a nested composite widget (own roving container or a composite role). */\nfunction isNestedComposite(el: Element): boolean {\n return el.matches(COMPOSITE_SELECTOR);\n}\n\n/** Whether `node` is strictly inside a nested composite of `grid` (the composite root excluded). */\nfunction isInsideNestedComposite(node: Element, grid: HTMLTableElement): boolean {\n if (node === grid) return false;\n for (let el = node.parentElement; el && el !== grid; el = el.parentElement) {\n if (isNestedComposite(el)) return true;\n }\n return false;\n}\n\n/**\n * The element a nested composite is entered at: its own tab stop (`tabindex=\"0\"`), else its first\n * tabbable element (the root itself when it is the focusable element).\n */\nfunction getCompositeEntry(composite: HTMLElement): HTMLElement | null {\n const stop = composite.querySelector<HTMLElement>('[tabindex=\"0\"]');\n if (stop && isFocusable(stop)) return stop;\n return getFirstTabbable(composite, { includeContainer: true });\n}\n\n/**\n * A cheap focusability check for cells that do not hold the tab stop: no style reads, only the\n * attributes that take an element out of the focus order. The cell that holds (or receives) the tab\n * stop is checked precisely with `isFocusable`.\n */\nfunction isLikelyFocusable(el: HTMLElement): boolean {\n if (el.localName === 'input' && (el as HTMLInputElement).type === 'hidden') return false;\n if (el.matches(':disabled')) return false;\n return el.closest('[hidden], [inert]') === null;\n}\n\n/** Whether another radio of `radio`'s named group is checked (the browser's tab stop of the group). */\nfunction hasCheckedPeer(radio: HTMLInputElement): boolean {\n if (!radio.name) return false;\n const root = radio.getRootNode() as Document | ShadowRoot;\n if (typeof root.querySelectorAll !== 'function') return false;\n const checked = root.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]:checked');\n for (const other of Array.from(checked)) {\n if (other !== radio && other.name === radio.name && other.form === radio.form) return true;\n }\n return false;\n}\n\nfunction isCellElement(el: Element): el is HTMLTableCellElement {\n return el.localName === 'td' || el.localName === 'th';\n}\n\n/** The cell of `grid` that contains `node` (a nested table's cells belong to the outer cell). */\nfunction getCell(grid: HTMLTableElement, node: EventTarget | null): HTMLTableCellElement | null {\n let el: Element | null = node instanceof Element ? node : null;\n while (el && el !== grid) {\n if (isCellElement(el) && el.closest('table') === grid) return el;\n el = el.parentElement;\n }\n return null;\n}\n\n/** The rows of `grid` (header, body and footer rows; a nested table's rows are not included). */\nfunction getRows(grid: HTMLTableElement): HTMLTableRowElement[] {\n return Array.from(grid.rows).filter((row) => row.cells.length > 0 && !row.hidden);\n}\n\n/** Browsers clamp `colSpan` to 1..1000 (`rowSpan` 0 covers the rest of its row group). */\nconst MAX_COL_SPAN = 1000;\n\n/** Per row, the cell covering each visual column (see {@link getColumnMap}). */\ntype ColumnMap = ReadonlyArray<ReadonlyArray<HTMLTableCellElement | undefined>>;\n\n/**\n * The HTML table model of `rows`: per row, the cell covering each visual column, with `colSpan` and\n * `rowSpan` counted. A row span covers the next rows of its row group in the DOM (a hidden row\n * counts, but gets no entry). A column no cell covers is empty. O(number of cells).\n */\nfunction getColumnMap(rows: readonly HTMLTableRowElement[]): ColumnMap {\n const indexOf = new Map<Element, number>(rows.map((row, index) => [row, index]));\n const map: Array<Array<HTMLTableCellElement | undefined>> = rows.map(() => []);\n const cover = (rowIndex: number, column: number, colSpan: number, cell: HTMLTableCellElement) => {\n for (let offset = 0; offset < colSpan; offset += 1) map[rowIndex][column + offset] = cell;\n };\n rows.forEach((row, rowIndex) => {\n const own = map[rowIndex];\n let column = 0;\n for (const cell of Array.from(row.cells)) {\n while (own[column]) column += 1;\n const colSpan = Math.min(Math.max(cell.colSpan, 1), MAX_COL_SPAN);\n cover(rowIndex, column, colSpan, cell);\n let remaining = cell.rowSpan === 0 ? Infinity : cell.rowSpan - 1;\n for (\n let next = row.nextElementSibling;\n next && remaining > 0;\n next = next.nextElementSibling\n ) {\n if (next.localName !== 'tr') continue;\n remaining -= 1;\n const covered = indexOf.get(next);\n if (covered !== undefined) cover(covered, column, colSpan, cell);\n }\n column += colSpan;\n }\n });\n return map;\n}\n\n/** The cell of a row (as {@link getColumnMap} maps it) at `column`, else the last one before it. */\nfunction getCellAtColumn(\n rowMap: ReadonlyArray<HTMLTableCellElement | undefined>,\n column: number,\n): HTMLTableCellElement | null {\n for (let index = Math.min(column, rowMap.length - 1); index >= 0; index -= 1) {\n const cell = rowMap[index];\n if (cell) return cell;\n }\n return null;\n}\n\n/** The grid cells in `node` (itself, or its descendants that belong to `grid`). */\nfunction collectCells(grid: HTMLTableElement, node: Node, into: Set<HTMLTableCellElement>): void {\n if (!(node instanceof Element)) return;\n if (isCellElement(node) && node.closest('table') === grid) into.add(node);\n for (const el of Array.from(node.querySelectorAll<HTMLTableCellElement>('td, th'))) {\n if (el.closest('table') === grid) into.add(el);\n }\n}\n\n/** A widget of a cell: a focusable element, or a nested composite counted as one widget. */\ninterface CellWidget {\n /** The focusable element, or the nested composite's root. */\n element: HTMLElement;\n /** A nested composite: the grid never writes `tabindex` on or inside it. */\n composite: boolean;\n}\n\n/**\n * Grid keyboard model of the APG Grid pattern for a `<table role=\"grid\">` (DataGrid).\n *\n * - **One tab stop.** Exactly one cell of the grid is in the tab order (initially the first cell).\n * Its focus target is its only focusable widget when that widget is not a text-entry control\n * (a checkbox, a sort button, a link); otherwise — no widget, several widgets, a text-entry\n * control or a nested composite — the cell itself. An unchecked radio whose named group has a\n * checked radio is not a target either (browsers skip it in the Tab order), so its cell is.\n * Every other cell and every widget inside a cell gets `tabindex=\"-1\"`. Rows are never focusable.\n * When a widget becomes the target of the focused cell (Space on the cell checks its radio), the\n * cell keeps focus and the tab stop (removing its `tabindex` would blur it) while its target stays\n * at `tabindex=\"-1\"`; the target takes the tab stop once the cell is blurred.\n * - **Navigation mode** (focus on a cell or on its target widget): ArrowLeft/ArrowRight (the\n * previous/next cell of the row, mirrored in RTL), ArrowUp/ArrowDown, Home/End (first/last cell\n * of the row), Ctrl+Home/Ctrl+End (first cell of the grid / last cell of the last row) and\n * PageUp/PageDown ({@link UseGridNavigationOptions.pageSize} rows) move focus between cells.\n * ArrowUp/ArrowDown and PageUp/PageDown keep the visual column (`colSpan` and `rowSpan` counted,\n * so a grouped header works), clamped to a shorter row. Space and Enter on a target widget reach\n * the widget.\n * - **Interaction mode**: Enter or F2 on a cell that is its own target moves focus into its first\n * widget; clicking a widget that is not the cell's target (a text input) enters it too. The\n * grid keys are then ignored, so the arrow keys move the caret, and Tab moves between the cell's\n * widgets. Escape returns focus to the cell. Leaving the grid ends interaction mode.\n * - **Nested composites** (an element with `data-roving-container` or role toolbar, radiogroup,\n * listbox, grid, treegrid, tablist, menu, menubar or tree) count as one widget that uses the arrow\n * keys: its cell takes focus, and Enter/F2 enters the composite at its own tab stop. The grid never\n * writes `tabindex` on or inside a composite (the composite manages its own), so a composite\n * keeps its own Tab stop — the same documented limitation as `useRovingTabIndex`.\n *\n * Keys are ignored when the event's default was prevented (a widget that handled the key, a\n * consumer handler), or with Alt/Meta held. The tab indexes are written to the DOM (layout effect\n * plus a `MutationObserver`), so rows and widgets added later are covered without a re-render. Focus\n * moves re-sync only the cells involved, the observer only the cells that changed, and the grid's\n * own `tabindex` writes do not trigger a re-sync.\n *\n * Internal to the table package.\n */\nexport function useGridNavigation(options: UseGridNavigationOptions = {}): UseGridNavigationResult {\n const pageSize = Math.max(1, options.pageSize ?? 10);\n const [grid, setGrid] = React.useState<HTMLTableElement | null>(null);\n /** The cell that holds the tab stop. */\n const activeCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** The cell whose widgets are being interacted with, or `null` (navigation mode). */\n const interactionCellRef = React.useRef<HTMLTableCellElement | null>(null);\n /** Elements whose `tabindex` this hook wrote. */\n const [stamped] = React.useState(() => new WeakSet<Element>());\n /** The `tabindex` value this hook last wrote per element (`null`: removed), to skip own records. */\n const [written] = React.useState(() => new WeakMap<Element, string | null>());\n /**\n * Cells kept focusable (holding the tab stop, their target at -1) because they were focused when\n * a widget became their target (a radio that got checked, a widget added): they lose the\n * attribute once blurred.\n */\n const [retained] = React.useState(() => new WeakSet<Element>());\n const pageSizeRef = React.useRef(pageSize);\n\n React.useLayoutEffect(() => {\n pageSizeRef.current = pageSize;\n }, [pageSize]);\n\n /**\n * The widgets of `cell` in DOM order. `precise` checks focusability with `isFocusable` (style\n * reads); otherwise only attributes are checked (cells that do not hold the tab stop).\n */\n const getWidgets = React.useCallback(\n (cell: HTMLTableCellElement, precise: boolean): CellWidget[] => {\n const widgets: CellWidget[] = [];\n let composite: HTMLElement | null = null;\n for (const el of Array.from(cell.querySelectorAll<HTMLElement>(WIDGET_CANDIDATE_SELECTOR))) {\n // Descendants of a nested composite belong to it (candidates come in document order).\n if (composite && composite.contains(el)) continue;\n if (isNestedComposite(el)) {\n composite = el;\n const available = precise ? getCompositeEntry(el) !== null : isLikelyFocusable(el);\n if (available) widgets.push({ element: el, composite: true });\n continue;\n }\n const tabIndex = el.getAttribute('tabindex');\n // An author tabindex=\"-1\" (not written by this hook) opts the element out.\n if (tabIndex !== null && Number(tabIndex) < 0 && !stamped.has(el)) continue;\n if (precise ? isFocusable(el) : isLikelyFocusable(el)) {\n widgets.push({ element: el, composite: false });\n }\n }\n return widgets;\n },\n [stamped],\n );\n\n /**\n * The element of `cell` that takes focus in navigation mode. `checkRadioGroup` applies the radio\n * group rule, which only decides where the tab stop goes (the cell that holds it); a focused\n * lone radio is in navigation mode either way.\n */\n const getTarget = React.useCallback(\n (cell: HTMLTableCellElement, widgets: CellWidget[], checkRadioGroup: boolean): HTMLElement => {\n if (widgets.length !== 1) return cell;\n const [{ element, composite }] = widgets;\n if (composite || isTextEntryElement(element)) return cell;\n if (\n checkRadioGroup &&\n element instanceof HTMLInputElement &&\n element.type === 'radio' &&\n !element.checked &&\n hasCheckedPeer(element)\n ) {\n return cell;\n }\n return element;\n },\n [],\n );\n\n const setTabIndex = React.useCallback(\n (el: HTMLElement, value: 0 | -1) => {\n const next = String(value);\n if (el.getAttribute('tabindex') !== next) {\n written.set(el, next);\n el.setAttribute('tabindex', next);\n }\n stamped.add(el);\n },\n [stamped, written],\n );\n\n /** Writes the tab indexes of one cell and its widgets (only attributes that change). */\n const syncCell = React.useCallback(\n (cell: HTMLTableCellElement, isActive: boolean, isInteraction: boolean) => {\n const widgets = getWidgets(cell, isActive);\n if (isInteraction) {\n retained.delete(cell);\n setTabIndex(cell, -1);\n for (const widget of widgets) {\n if (!widget.composite) setTabIndex(widget.element, 0);\n }\n return;\n }\n const target = getTarget(cell, widgets, isActive);\n /** The focused cell stands in for its target until it is blurred (see below). */\n let keepCell = false;\n if (target === cell) {\n retained.delete(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell) && isFocused(cell)) {\n // A widget became the focused cell's target (its radio got checked, a widget was added).\n // Removing the tabindex of the focused element blurs it (browsers move focus to the body,\n // which ends the grid session), so the cell stays focusable and stays the grid's position.\n // It keeps the tab stop (`tabindex=\"0\"`, when active) and its target stays at -1: the grid\n // still has exactly one Tab stop even if focus is lost without a focusout, and Tab and\n // Shift+Tab from the cell leave the grid (nothing after or before it inside is tabbable).\n // Once the cell is blurred, onBlur re-syncs it and the target takes the tab stop.\n keepCell = true;\n retained.add(cell);\n setTabIndex(cell, isActive ? 0 : -1);\n } else if (stamped.has(cell)) {\n // The cell's widget is its target: the cell itself is not focusable.\n retained.delete(cell);\n stamped.delete(cell);\n if (cell.hasAttribute('tabindex')) {\n written.set(cell, null);\n cell.removeAttribute('tabindex');\n }\n }\n for (const widget of widgets) {\n if (widget.composite) continue;\n const isStop = widget.element === target && isActive && !keepCell;\n setTabIndex(widget.element, isStop ? 0 : -1);\n }\n },\n [getTarget, getWidgets, retained, setTabIndex, stamped, written],\n );\n\n /**\n * Re-resolves the active cell (the first cell when it is gone) and syncs `cells` — every cell\n * when omitted. A new active cell is always synced.\n */\n const sync = React.useCallback(\n (table: HTMLTableElement, cells?: Iterable<HTMLTableCellElement | null>) => {\n const rows = getRows(table);\n let active = activeCellRef.current;\n const targets = new Set<HTMLTableCellElement>();\n if (\n !active ||\n !active.isConnected ||\n !rows.includes(active.parentElement as HTMLTableRowElement)\n ) {\n active = rows[0]?.cells[0] ?? null;\n activeCellRef.current = active;\n if (active) targets.add(active);\n }\n if (interactionCellRef.current !== null && interactionCellRef.current !== active) {\n const previous = interactionCellRef.current;\n interactionCellRef.current = null;\n targets.add(previous);\n }\n const interaction = interactionCellRef.current;\n\n if (cells === undefined) {\n for (const row of rows) for (const cell of Array.from(row.cells)) targets.add(cell);\n } else {\n for (const cell of cells) if (cell) targets.add(cell);\n }\n for (const cell of targets) {\n if (!cell.isConnected || getCell(table, cell) !== cell) continue;\n syncCell(cell, cell === active, cell === interaction);\n }\n },\n [syncCell],\n );\n\n React.useLayoutEffect(() => {\n if (!grid) return;\n sync(grid);\n if (typeof MutationObserver === 'undefined') return;\n const observer = new MutationObserver((records) => {\n let relevant = false;\n let all = false;\n const dirty = new Set<HTMLTableCellElement>();\n for (const record of records) {\n const target = record.target;\n if (!(target instanceof Element)) continue;\n // A nested composite manages its own items (and their tab indexes).\n if (isInsideNestedComposite(target, grid)) continue;\n if (record.type === 'attributes') {\n // The record of one of our own writes.\n if (\n record.attributeName === 'tabindex' &&\n written.has(target) &&\n written.get(target) === target.getAttribute('tabindex')\n ) {\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) dirty.add(cell);\n else if (target.localName === 'tr') collectCells(grid, target, dirty);\n else all = true;\n continue;\n }\n relevant = true;\n const cell = getCell(grid, target);\n if (cell) {\n dirty.add(cell);\n continue;\n }\n // Rows, sections or cells added (removed ones need no sync; `sync` re-resolves the\n // active cell when it went away).\n for (const node of Array.from(record.addedNodes)) collectCells(grid, node, dirty);\n }\n if (!relevant) return;\n // The active cell's target can depend on other cells (a radio group whose checked radio\n // changed, reflected by the rows' `aria-selected`), so it is always re-checked.\n if (activeCellRef.current) dirty.add(activeCellRef.current);\n sync(grid, all ? undefined : dirty);\n });\n observer.observe(grid, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRIBUTES,\n });\n return () => observer.disconnect();\n }, [grid, sync, written]);\n\n /**\n * Makes `cell` the active cell (in interaction mode when `interaction`) and re-syncs only the\n * cells involved: the previous active and interaction cells and `cell`.\n */\n const activate = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement, interaction: boolean) => {\n const previousActive = activeCellRef.current;\n const previousInteraction = interactionCellRef.current;\n activeCellRef.current = cell;\n interactionCellRef.current = interaction ? cell : null;\n sync(table, [previousActive, previousInteraction, cell]);\n },\n [sync],\n );\n\n const focusCell = React.useCallback(\n (table: HTMLTableElement, cell: HTMLTableCellElement) => {\n activate(table, cell, false);\n getTarget(cell, getWidgets(cell, true), true).focus();\n },\n [activate, getTarget, getWidgets],\n );\n\n const onKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n if (event.defaultPrevented) return;\n const cell = getCell(table, event.target);\n if (!cell) return;\n const widgets = getWidgets(cell, true);\n // A lone non-text widget is in navigation mode when focused, even an unchecked radio that\n // the radio group rule keeps from holding the tab stop.\n const widgetTarget = getTarget(cell, widgets, false);\n const onCell = event.target === cell;\n\n if (!onCell && event.target !== widgetTarget) {\n // Interaction mode: the widget keeps its keys; Escape returns to the cell.\n if (event.key === 'Escape') {\n event.preventDefault();\n focusCell(table, cell);\n }\n return;\n }\n if (event.altKey || event.metaKey) return;\n\n if ((event.key === 'Enter' || event.key === 'F2') && onCell) {\n let entry: HTMLElement | null = null;\n for (const widget of widgets) {\n entry = widget.composite ? getCompositeEntry(widget.element) : widget.element;\n if (entry) break;\n }\n if (!entry) return;\n event.preventDefault();\n activate(table, cell, true);\n entry.focus();\n return;\n }\n\n const rows = getRows(table);\n const rowIndex = rows.findIndex((row) => row === cell.parentElement);\n if (rowIndex < 0) return;\n const columnIndex = Array.from(rows[rowIndex].cells).indexOf(cell);\n const lastRow = rows.length - 1;\n const ctrl = event.ctrlKey;\n /** Left/Right, Home/End: target row and cell index in it (`Infinity`: the row's last cell). */\n let next: [row: number, column: number] | null = null;\n /** Up/Down, PageUp/PageDown: the first row to try and the direction to keep going in. */\n let vertical: [row: number, step: 1 | -1] | null = null;\n\n switch (event.key) {\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (ctrl) break;\n const intent = getArrowIntent(event.key, {\n orientation: 'horizontal',\n dir: getDirection(table),\n });\n next = [rowIndex, columnIndex + (intent === 'next' ? 1 : -1)];\n break;\n }\n case 'ArrowDown':\n if (!ctrl) vertical = [rowIndex + 1, 1];\n break;\n case 'ArrowUp':\n if (!ctrl) vertical = [rowIndex - 1, -1];\n break;\n case 'Home':\n next = ctrl ? [0, 0] : [rowIndex, 0];\n break;\n case 'End':\n next = ctrl ? [lastRow, Infinity] : [rowIndex, Infinity];\n break;\n case 'PageDown':\n if (!ctrl) vertical = [Math.min(rowIndex + pageSizeRef.current, lastRow), 1];\n break;\n case 'PageUp':\n if (!ctrl) vertical = [Math.max(rowIndex - pageSizeRef.current, 0), -1];\n break;\n default:\n break;\n }\n if (!next && !vertical) return;\n // A handled key never scrolls the page or reaches the widget, even at the grid's edge.\n event.preventDefault();\n let destination: HTMLTableCellElement | null = null;\n if (vertical) {\n // Up/Down keep the visual column (spans counted), clamped to a shorter row; the rows a\n // row-spanning cell covers are skipped when moving away from it.\n const columns = getColumnMap(rows);\n const column = Math.max(columns[rowIndex].indexOf(cell), 0);\n for (let row = vertical[0]; row >= 0 && row <= lastRow; row += vertical[1]) {\n const candidate = getCellAtColumn(columns[row], column);\n if (candidate && candidate !== cell) {\n destination = candidate;\n break;\n }\n }\n } else if (next) {\n // Left/Right stop at the row's edges (no cell there).\n const [nextRow, nextColumn] = next;\n const rowCells = rows[nextRow].cells;\n destination =\n nextColumn === Infinity ? rowCells[rowCells.length - 1] : rowCells.item(nextColumn);\n }\n if (destination && destination !== cell) focusCell(table, destination);\n },\n [activate, focusCell, getTarget, getWidgets],\n );\n\n const onFocus = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the focused descendant.\n const target: EventTarget = event.target;\n const cell = getCell(table, target);\n if (!cell) return;\n const interaction =\n target !== cell && target !== getTarget(cell, getWidgets(cell, true), false);\n activate(table, cell, interaction);\n },\n [activate, getTarget, getWidgets],\n );\n\n const onBlur = React.useCallback<React.FocusEventHandler<HTMLTableElement>>(\n (event) => {\n const table = event.currentTarget;\n // React types `target` as the handler's element type; it is the blurred descendant.\n const left: EventTarget = event.target;\n const cells: HTMLTableCellElement[] = [];\n // A cell kept focusable while focused (see syncCell) is re-synced now that it is blurred\n // (during focusout the blurred element is no longer the active element).\n if (left instanceof Element && isCellElement(left) && retained.has(left)) cells.push(left);\n const next = event.relatedTarget;\n const interaction = interactionCellRef.current;\n if (interaction !== null && !(next instanceof Node && table.contains(next))) {\n interactionCellRef.current = null;\n cells.push(interaction);\n }\n if (cells.length > 0) sync(table, cells);\n },\n [retained, sync],\n );\n\n const gridProps = React.useMemo<GridNavigationHandlers>(\n () => ({ onKeyDown, onFocus, onBlur }),\n [onBlur, onFocus, onKeyDown],\n );\n\n return { gridRef: setGrid, gridProps };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use client";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./useEventCallback.cjs");let n=require("react");n=e.__toESM(n,1);function r(e,t){return e===t||e.length===t.length&&e.every((e,n)=>e===t[n])}function i(e,t,n,r){let i=e.length;if(i===0)return null;let a=t===null?-1:e.indexOf(t);if(a===-1)return n>0?e[0]:e[i-1];let o=a+n;return(o<0||o>=i)&&(o=r&&Math.abs(n)===1?(o+i)%i:Math.max(0,Math.min(i-1,o))),e[o]}function a(e){let{items:a,getId:o,enabled:s=!0,fallback:c=null,loop:l=!1,activateFirstOnChange:u=!1,getElement:d,onActiveValueChange:f}=e,p=n.useMemo(()=>new Set(a),[a]),[m,h]=n.useState(null),g=s?m:null,[_,v]=n.useState(()=>({enabled:s,items:a}));if(u){let e=!r(_.items,a);(e||_.enabled!==s)&&(v({enabled:s,items:a}),s&&e&&(g=a[0]??null))}g!==null&&!p.has(g)&&(g=null),g!==m&&h(g);let y=s?g??(c!==null&&p.has(c)?c:null):null,b=y===null?void 0:o(y),x=n.useRef(null),S=t.useEventCallback((e,t)=>{x.current=t?.scroll===!1?e:null,h(e)}),C=t.useEventCallback(e=>{s&&p.has(e)&&e!==y&&(x.current=e,h(e))}),w=t.useEventCallback(()=>S(a[0]??null)),T=t.useEventCallback(()=>S(a[a.length-1]??null)),E=t.useEventCallback(()=>S(i(a,y,1,l))),D=t.useEventCallback(()=>S(i(a,y,-1,l))),O=t.useEventCallback(e=>S(i(a,y,e,!1))),k=t.useEventCallback(e=>d?.(e)||(typeof document>`u`?null:document.getElementById(o(e))));n.useLayoutEffect(()=>{let e=y!==null&&y===x.current;x.current=null,!(y===null||e)&&k(y)?.scrollIntoView?.({block:`nearest`})},[y,k]);let A=n.useRef(null),j=t.useEventCallback(e=>f?.(e));return n.useEffect(()=>{A.current!==y&&(A.current=y,j(y))},[y,j]),{activeValue:y,activeDescendantId:b,setActiveValue:S,highlight:C,first:w,last:T,next:E,prev:D,move:O}}exports.useActiveDescendant=a;
2
+ //# sourceMappingURL=useActiveDescendant.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useActiveDescendant.cjs","names":["React","useEventCallback","require_runtime","require_useEventCallback","react","sameValues","a","b","value","index","step","count","values","current","delta","next","loop","useActiveDescendant","options","itemSet","items","movedRaw","setMovedRaw","moved","enabled","track","setTrack","activateFirstOnChange","itemsChanged","activeValue","fallback","activeDescendantId","getId","noScrollRef","setActiveValue","moveOptions","highlight","first","last","prev","move","resolveElement","getElement","suppressed","reportedRef","report","onActiveValueChange"],"sources":["../../src/hooks/useActiveDescendant.ts"],"sourcesContent":["import * as React from 'react';\nimport { useEventCallback } from './useEventCallback';\n\n/** Options of {@link useActiveDescendant}. */\nexport interface UseActiveDescendantOptions {\n /** The values that can become active, in navigation order. */\n items: readonly string[];\n /** The DOM id of an item's element: the `aria-activedescendant` target. */\n getId: (value: string) => string;\n /**\n * While `false`, no item is active and the moved-to item is forgotten (a closed popup, an\n * unfocused list). A move made in the same update that enables the hook is kept.\n * @default true\n */\n enabled?: boolean;\n /**\n * The active item while none was moved to, or after the moved-to item left `items` (the\n * selected option, for example). Ignored when it is not in `items`.\n * @default null\n */\n fallback?: string | null;\n /** `next()` and `prev()` wrap at the ends. @default false */\n loop?: boolean;\n /**\n * Whenever `items` changes while the hook is enabled (compared by content), its first item\n * becomes active — also when the change comes with enabling (the keystroke that opens a\n * filtered list). Enabling with unchanged items keeps the fallback. It wins over a move made in\n * the same update.\n * @default false\n */\n activateFirstOnChange?: boolean;\n /** The element of an item, for scrolling it into view. @default document.getElementById(getId(value)) */\n getElement?: (value: string) => HTMLElement | null;\n /** Called after the active item changed (from an effect), `null` included. */\n onActiveValueChange?: (value: string | null) => void;\n}\n\n/** Result of {@link useActiveDescendant}. The methods have stable identities. */\nexport interface UseActiveDescendantResult {\n /** The moved-to item, else the fallback; `null` while disabled. Derived during render. */\n activeValue: string | null;\n /** `getId(activeValue)`, or `undefined`: spread as `aria-activedescendant`. */\n activeDescendantId: string | undefined;\n /**\n * Moves to `value`, or back to the fallback with `null`. The value is kept as it is; the next\n * render drops it when it is not in that render's `items` or the hook is disabled, clearing any\n * earlier move. Scrolled into view unless `scroll` is `false`.\n */\n setActiveValue: (value: string | null, options?: { scroll?: boolean }) => void;\n /**\n * Moves to `value` for pointer movement, not scrolled into view; ignored while disabled, when\n * `value` is not in `items` or is already active.\n */\n highlight: (value: string) => void;\n /** Moves to the first item. */\n first: () => void;\n /** Moves to the last item. */\n last: () => void;\n /** The item after the active one (the first when none is active); wraps with `loop`. */\n next: () => void;\n /** The item before the active one (the last when none is active); wraps with `loop`. */\n prev: () => void;\n /**\n * Moves `delta` items (PageDown: `move(10)`), clamped at the ends, never wrapping; from no active\n * item, a positive delta goes to the first item and a negative one to the last.\n */\n move: (delta: number) => void;\n}\n\nfunction sameValues(a: readonly string[], b: readonly string[]): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n return a.every((value, index) => value === b[index]);\n}\n\nfunction step(values: readonly string[], current: string | null, delta: number, loop: boolean) {\n const count = values.length;\n if (count === 0) return null;\n const index = current === null ? -1 : values.indexOf(current);\n if (index === -1) return delta > 0 ? values[0] : values[count - 1];\n let next = index + delta;\n if (next < 0 || next >= count) {\n next =\n loop && Math.abs(delta) === 1\n ? (next + count) % count\n : Math.max(0, Math.min(count - 1, next));\n }\n return values[next];\n}\n\n/**\n * The active (virtually focused) item of a composite that keeps DOM focus on one element and points\n * `aria-activedescendant` at the active item: a combobox, a listbox that holds focus, a command\n * palette. The state is React state, so the active id is derived during render (server HTML\n * included) and nothing reads the DOM during render.\n *\n * - The moved-to item is dropped when it leaves `items` or while `enabled` is `false`, and does not\n * come back without a user action; `fallback` is active while nothing was moved to.\n * - Keyboard and programmatic moves are scrolled into view (`{ block: 'nearest' }`); pointer moves\n * (`highlight`) and `setActiveValue(value, { scroll: false })` are not.\n * - `onActiveValueChange` reports every change after the commit, once (StrictMode included).\n *\n * `useListbox` is built on it; use it directly for composites that are not listboxes.\n *\n * @example\n * const ad = useActiveDescendant({ items: results.map((r) => r.id), getId: (id) => `cmd-${id}`, enabled: open });\n * <input aria-activedescendant={ad.activeDescendantId} onKeyDown={(e) => { if (e.key === 'ArrowDown') ad.next(); }} />\n */\nexport function useActiveDescendant(\n options: UseActiveDescendantOptions,\n): UseActiveDescendantResult {\n const {\n items,\n getId,\n enabled = true,\n fallback = null,\n loop = false,\n activateFirstOnChange = false,\n getElement,\n onActiveValueChange,\n } = options;\n\n const itemSet = React.useMemo(() => new Set(items), [items]);\n const [movedRaw, setMovedRaw] = React.useState<string | null>(null);\n let moved = enabled ? movedRaw : null;\n\n // activateFirstOnChange: tracked (and updated) only while the option is on, so a render without\n // it adds no render-phase update (C-HOOKS: adjust-during-render).\n const [track, setTrack] = React.useState(() => ({ enabled, items }));\n if (activateFirstOnChange) {\n const itemsChanged = !sameValues(track.items, items);\n if (itemsChanged || track.enabled !== enabled) {\n setTrack({ enabled, items });\n if (enabled && itemsChanged) moved = items[0] ?? null;\n }\n }\n if (moved !== null && !itemSet.has(moved)) moved = null;\n if (moved !== movedRaw) setMovedRaw(moved);\n\n const activeValue = enabled\n ? (moved ?? (fallback !== null && itemSet.has(fallback) ? fallback : null))\n : null;\n const activeDescendantId = activeValue === null ? undefined : getId(activeValue);\n\n // The value the pointer (or a `scroll: false` move) activated, until the scroll effect saw it.\n const noScrollRef = React.useRef<string | null>(null);\n\n const setActiveValue = useEventCallback(\n (value: string | null, moveOptions?: { scroll?: boolean }) => {\n noScrollRef.current = moveOptions?.scroll === false ? value : null;\n setMovedRaw(value);\n },\n );\n const highlight = useEventCallback((value: string) => {\n if (!enabled || !itemSet.has(value) || value === activeValue) return;\n noScrollRef.current = value;\n setMovedRaw(value);\n });\n const first = useEventCallback(() => setActiveValue(items[0] ?? null));\n const last = useEventCallback(() => setActiveValue(items[items.length - 1] ?? null));\n const next = useEventCallback(() => setActiveValue(step(items, activeValue, 1, loop)));\n const prev = useEventCallback(() => setActiveValue(step(items, activeValue, -1, loop)));\n // Never wraps, whatever `loop` is: `loop` is next()/prev()'s wrap at the ends only (fix round 1).\n const move = useEventCallback((delta: number) =>\n setActiveValue(step(items, activeValue, delta, false)),\n );\n\n const resolveElement = useEventCallback((value: string): HTMLElement | null => {\n const custom = getElement?.(value);\n if (custom) return custom;\n return typeof document === 'undefined' ? null : document.getElementById(getId(value));\n });\n React.useLayoutEffect(() => {\n const suppressed = activeValue !== null && activeValue === noScrollRef.current;\n noScrollRef.current = null;\n if (activeValue === null || suppressed) return;\n resolveElement(activeValue)?.scrollIntoView?.({ block: 'nearest' });\n }, [activeValue, resolveElement]);\n\n const reportedRef = React.useRef<string | null>(null);\n const report = useEventCallback((value: string | null) => onActiveValueChange?.(value));\n React.useEffect(() => {\n if (reportedRef.current === activeValue) return;\n reportedRef.current = activeValue;\n report(activeValue);\n }, [activeValue, report]);\n\n return {\n activeValue,\n activeDescendantId,\n setActiveValue,\n highlight,\n first,\n last,\n next,\n prev,\n move,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import { useEventCallback as e } from "./useEventCallback.mjs";
3
+ import * as t from "react";
4
+ //#region src/hooks/useActiveDescendant.ts
5
+ function n(e, t) {
6
+ return e === t || e.length === t.length && e.every((e, n) => e === t[n]);
7
+ }
8
+ function r(e, t, n, r) {
9
+ let i = e.length;
10
+ if (i === 0) return null;
11
+ let a = t === null ? -1 : e.indexOf(t);
12
+ if (a === -1) return n > 0 ? e[0] : e[i - 1];
13
+ let o = a + n;
14
+ return (o < 0 || o >= i) && (o = r && Math.abs(n) === 1 ? (o + i) % i : Math.max(0, Math.min(i - 1, o))), e[o];
15
+ }
16
+ function i(i) {
17
+ let { items: a, getId: o, enabled: s = !0, fallback: c = null, loop: l = !1, activateFirstOnChange: u = !1, getElement: d, onActiveValueChange: f } = i, p = t.useMemo(() => new Set(a), [a]), [m, h] = t.useState(null), g = s ? m : null, [_, v] = t.useState(() => ({
18
+ enabled: s,
19
+ items: a
20
+ }));
21
+ if (u) {
22
+ let e = !n(_.items, a);
23
+ (e || _.enabled !== s) && (v({
24
+ enabled: s,
25
+ items: a
26
+ }), s && e && (g = a[0] ?? null));
27
+ }
28
+ g !== null && !p.has(g) && (g = null), g !== m && h(g);
29
+ let y = s ? g ?? (c !== null && p.has(c) ? c : null) : null, b = y === null ? void 0 : o(y), x = t.useRef(null), S = e((e, t) => {
30
+ x.current = t?.scroll === !1 ? e : null, h(e);
31
+ }), C = e((e) => {
32
+ s && p.has(e) && e !== y && (x.current = e, h(e));
33
+ }), w = e(() => S(a[0] ?? null)), T = e(() => S(a[a.length - 1] ?? null)), E = e(() => S(r(a, y, 1, l))), D = e(() => S(r(a, y, -1, l))), O = e((e) => S(r(a, y, e, !1))), k = e((e) => d?.(e) || (typeof document > "u" ? null : document.getElementById(o(e))));
34
+ t.useLayoutEffect(() => {
35
+ let e = y !== null && y === x.current;
36
+ x.current = null, !(y === null || e) && k(y)?.scrollIntoView?.({ block: "nearest" });
37
+ }, [y, k]);
38
+ let A = t.useRef(null), j = e((e) => f?.(e));
39
+ return t.useEffect(() => {
40
+ A.current !== y && (A.current = y, j(y));
41
+ }, [y, j]), {
42
+ activeValue: y,
43
+ activeDescendantId: b,
44
+ setActiveValue: S,
45
+ highlight: C,
46
+ first: w,
47
+ last: T,
48
+ next: E,
49
+ prev: D,
50
+ move: O
51
+ };
52
+ }
53
+ //#endregion
54
+ export { i as useActiveDescendant };
55
+
56
+ //# sourceMappingURL=useActiveDescendant.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useActiveDescendant.mjs","names":["React","sameValues","a","b","value","index","step","count","values","current","delta","next","loop","useActiveDescendant","options","itemSet","items","movedRaw","setMovedRaw","moved","enabled","track","setTrack","activateFirstOnChange","itemsChanged","activeValue","fallback","activeDescendantId","getId","noScrollRef","setActiveValue","useEventCallback","moveOptions","highlight","first","last","prev","move","resolveElement","getElement","suppressed","reportedRef","report","onActiveValueChange"],"sources":["../../src/hooks/useActiveDescendant.ts"],"sourcesContent":["import * as React from 'react';\nimport { useEventCallback } from './useEventCallback';\n\n/** Options of {@link useActiveDescendant}. */\nexport interface UseActiveDescendantOptions {\n /** The values that can become active, in navigation order. */\n items: readonly string[];\n /** The DOM id of an item's element: the `aria-activedescendant` target. */\n getId: (value: string) => string;\n /**\n * While `false`, no item is active and the moved-to item is forgotten (a closed popup, an\n * unfocused list). A move made in the same update that enables the hook is kept.\n * @default true\n */\n enabled?: boolean;\n /**\n * The active item while none was moved to, or after the moved-to item left `items` (the\n * selected option, for example). Ignored when it is not in `items`.\n * @default null\n */\n fallback?: string | null;\n /** `next()` and `prev()` wrap at the ends. @default false */\n loop?: boolean;\n /**\n * Whenever `items` changes while the hook is enabled (compared by content), its first item\n * becomes active — also when the change comes with enabling (the keystroke that opens a\n * filtered list). Enabling with unchanged items keeps the fallback. It wins over a move made in\n * the same update.\n * @default false\n */\n activateFirstOnChange?: boolean;\n /** The element of an item, for scrolling it into view. @default document.getElementById(getId(value)) */\n getElement?: (value: string) => HTMLElement | null;\n /** Called after the active item changed (from an effect), `null` included. */\n onActiveValueChange?: (value: string | null) => void;\n}\n\n/** Result of {@link useActiveDescendant}. The methods have stable identities. */\nexport interface UseActiveDescendantResult {\n /** The moved-to item, else the fallback; `null` while disabled. Derived during render. */\n activeValue: string | null;\n /** `getId(activeValue)`, or `undefined`: spread as `aria-activedescendant`. */\n activeDescendantId: string | undefined;\n /**\n * Moves to `value`, or back to the fallback with `null`. The value is kept as it is; the next\n * render drops it when it is not in that render's `items` or the hook is disabled, clearing any\n * earlier move. Scrolled into view unless `scroll` is `false`.\n */\n setActiveValue: (value: string | null, options?: { scroll?: boolean }) => void;\n /**\n * Moves to `value` for pointer movement, not scrolled into view; ignored while disabled, when\n * `value` is not in `items` or is already active.\n */\n highlight: (value: string) => void;\n /** Moves to the first item. */\n first: () => void;\n /** Moves to the last item. */\n last: () => void;\n /** The item after the active one (the first when none is active); wraps with `loop`. */\n next: () => void;\n /** The item before the active one (the last when none is active); wraps with `loop`. */\n prev: () => void;\n /**\n * Moves `delta` items (PageDown: `move(10)`), clamped at the ends, never wrapping; from no active\n * item, a positive delta goes to the first item and a negative one to the last.\n */\n move: (delta: number) => void;\n}\n\nfunction sameValues(a: readonly string[], b: readonly string[]): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n return a.every((value, index) => value === b[index]);\n}\n\nfunction step(values: readonly string[], current: string | null, delta: number, loop: boolean) {\n const count = values.length;\n if (count === 0) return null;\n const index = current === null ? -1 : values.indexOf(current);\n if (index === -1) return delta > 0 ? values[0] : values[count - 1];\n let next = index + delta;\n if (next < 0 || next >= count) {\n next =\n loop && Math.abs(delta) === 1\n ? (next + count) % count\n : Math.max(0, Math.min(count - 1, next));\n }\n return values[next];\n}\n\n/**\n * The active (virtually focused) item of a composite that keeps DOM focus on one element and points\n * `aria-activedescendant` at the active item: a combobox, a listbox that holds focus, a command\n * palette. The state is React state, so the active id is derived during render (server HTML\n * included) and nothing reads the DOM during render.\n *\n * - The moved-to item is dropped when it leaves `items` or while `enabled` is `false`, and does not\n * come back without a user action; `fallback` is active while nothing was moved to.\n * - Keyboard and programmatic moves are scrolled into view (`{ block: 'nearest' }`); pointer moves\n * (`highlight`) and `setActiveValue(value, { scroll: false })` are not.\n * - `onActiveValueChange` reports every change after the commit, once (StrictMode included).\n *\n * `useListbox` is built on it; use it directly for composites that are not listboxes.\n *\n * @example\n * const ad = useActiveDescendant({ items: results.map((r) => r.id), getId: (id) => `cmd-${id}`, enabled: open });\n * <input aria-activedescendant={ad.activeDescendantId} onKeyDown={(e) => { if (e.key === 'ArrowDown') ad.next(); }} />\n */\nexport function useActiveDescendant(\n options: UseActiveDescendantOptions,\n): UseActiveDescendantResult {\n const {\n items,\n getId,\n enabled = true,\n fallback = null,\n loop = false,\n activateFirstOnChange = false,\n getElement,\n onActiveValueChange,\n } = options;\n\n const itemSet = React.useMemo(() => new Set(items), [items]);\n const [movedRaw, setMovedRaw] = React.useState<string | null>(null);\n let moved = enabled ? movedRaw : null;\n\n // activateFirstOnChange: tracked (and updated) only while the option is on, so a render without\n // it adds no render-phase update (C-HOOKS: adjust-during-render).\n const [track, setTrack] = React.useState(() => ({ enabled, items }));\n if (activateFirstOnChange) {\n const itemsChanged = !sameValues(track.items, items);\n if (itemsChanged || track.enabled !== enabled) {\n setTrack({ enabled, items });\n if (enabled && itemsChanged) moved = items[0] ?? null;\n }\n }\n if (moved !== null && !itemSet.has(moved)) moved = null;\n if (moved !== movedRaw) setMovedRaw(moved);\n\n const activeValue = enabled\n ? (moved ?? (fallback !== null && itemSet.has(fallback) ? fallback : null))\n : null;\n const activeDescendantId = activeValue === null ? undefined : getId(activeValue);\n\n // The value the pointer (or a `scroll: false` move) activated, until the scroll effect saw it.\n const noScrollRef = React.useRef<string | null>(null);\n\n const setActiveValue = useEventCallback(\n (value: string | null, moveOptions?: { scroll?: boolean }) => {\n noScrollRef.current = moveOptions?.scroll === false ? value : null;\n setMovedRaw(value);\n },\n );\n const highlight = useEventCallback((value: string) => {\n if (!enabled || !itemSet.has(value) || value === activeValue) return;\n noScrollRef.current = value;\n setMovedRaw(value);\n });\n const first = useEventCallback(() => setActiveValue(items[0] ?? null));\n const last = useEventCallback(() => setActiveValue(items[items.length - 1] ?? null));\n const next = useEventCallback(() => setActiveValue(step(items, activeValue, 1, loop)));\n const prev = useEventCallback(() => setActiveValue(step(items, activeValue, -1, loop)));\n // Never wraps, whatever `loop` is: `loop` is next()/prev()'s wrap at the ends only (fix round 1).\n const move = useEventCallback((delta: number) =>\n setActiveValue(step(items, activeValue, delta, false)),\n );\n\n const resolveElement = useEventCallback((value: string): HTMLElement | null => {\n const custom = getElement?.(value);\n if (custom) return custom;\n return typeof document === 'undefined' ? null : document.getElementById(getId(value));\n });\n React.useLayoutEffect(() => {\n const suppressed = activeValue !== null && activeValue === noScrollRef.current;\n noScrollRef.current = null;\n if (activeValue === null || suppressed) return;\n resolveElement(activeValue)?.scrollIntoView?.({ block: 'nearest' });\n }, [activeValue, resolveElement]);\n\n const reportedRef = React.useRef<string | null>(null);\n const report = useEventCallback((value: string | null) => onActiveValueChange?.(value));\n React.useEffect(() => {\n if (reportedRef.current === activeValue) return;\n reportedRef.current = activeValue;\n report(activeValue);\n }, [activeValue, report]);\n\n return {\n activeValue,\n activeDescendantId,\n setActiveValue,\n highlight,\n first,\n last,\n next,\n prev,\n move,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFieldControl.cjs","names":["React","useId","joinIds","require_runtime","require_useId","require_aria","react","FieldControlIdClaimStore","token","onChange","listener","createFieldControlIdClaim","subscribeNothing","noVersion","FieldContext","useFieldContext","useFieldControl","field","fallbackId","options","wantsControlId","props","claim","holdsControlId","id","labelledBy","labelable","describedBy","invalid","ariaRequired","required","nativeRequired","result"],"sources":["../../src/hooks/useFieldControl.ts"],"sourcesContent":["import * as React from 'react';\nimport { joinIds } from '../lib/aria';\nimport type { ValidationState } from '../lib/types';\nimport { useId } from './useId';\n\n/**\n * What a `Field` tells the controls inside it: the ids of its label, hint and validation message,\n * and its validation, invalid and required state. `Field` provides it; every library input reads\n * it through {@link useFieldControl}, and so can your own control.\n */\nexport interface FieldContextValue {\n /** The id the Field's `<label htmlFor>` points at. */\n controlId: string;\n /** The id of the Field's `<label>`, used for `aria-labelledby` on non-labelable controls. */\n labelId: string | undefined;\n /** The id of the rendered hint, if any. */\n hintId: string | undefined;\n /**\n * The id of the rendered error message, if any: set only while the validation message is an\n * error (see `validationMessageId` for every state).\n */\n errorId: string | undefined;\n /** Whether the Field is in an error state (only the `error` validation state is invalid). */\n invalid: boolean;\n /** Whether the Field is required. */\n required: boolean;\n /** Field renders the error message itself; controls with their own `error` must not repeat it. */\n hasErrorMessage: boolean;\n /**\n * Field already gave `controlId` to its first child, a label-for merge target (component,\n * labelable element, custom element), whether Field merged it or it was the child's own id; a\n * control that receives no `id` prop is then not that child (it is nested inside it, e.g.\n * `Field > Tooltip > Input`, or a later sibling) and must not reuse `controlId`.\n * {@link useFieldControl} gives such a control its own id and names it through\n * `aria-labelledby`. Absent or `false`: a control without an `id` uses `controlId`, subject to\n * `controlIdClaim`.\n */\n controlIdAssigned?: boolean;\n /**\n * Hands `controlId` to one control when `controlIdAssigned` is not set — Field left its first\n * child alone (a plain `<div>`, a Fragment), so every library control inside reads this context.\n * The first control without an `id` of its own takes `controlId`; the others get their own id\n * and are named through `aria-labelledby`, so no id is duplicated. `Field` creates one for each\n * Field. Absent: every control without an `id` uses `controlId`.\n */\n controlIdClaim?: FieldControlIdClaim;\n /**\n * The Field's validation state (`validationState`, or `'error'` from `error`). Absent in\n * contexts built before 0.6: read it as `invalid ? 'error' : 'none'`.\n */\n validationState?: ValidationState;\n /**\n * The id of the rendered validation message in any state (error, warning, success, neutral).\n * `errorId` is set only while that message is an error.\n */\n validationMessageId?: string;\n}\n\n/**\n * Decides which control inside a `Field` takes the Field's `controlId` (the id its\n * `<label htmlFor>` points at) when several library controls without an `id` of their own read\n * the same context. `Field` creates one for each Field and passes it as\n * {@link FieldContextValue.controlIdClaim}; {@link useFieldControl} calls the methods, so a\n * control never calls them itself.\n *\n * - The first control to render takes `controlId`: on the first render (and on the server, so\n * hydration matches) that is the first one in document order. It keeps it across re-renders.\n * - When the holder unmounts or stops wanting `controlId` (it gets an `id` of its own), the\n * subscribers are notified, re-render, and the first of them takes `controlId` over.\n * - A render that claimed `controlId` but never committed (a discarded concurrent render) does not\n * keep it: a mounted control that renders without `controlId` frees it.\n */\nexport interface FieldControlIdClaim {\n /**\n * During render, with the control's stable token (its `useId`): gives `controlId` to `token`\n * when no control holds it, and returns whether `token` holds it. Idempotent per token, so\n * StrictMode double renders and later re-renders agree.\n */\n claim(token: string): boolean;\n /**\n * From a layout effect while the control wants `controlId`: marks `token` mounted. The returned\n * cleanup frees `controlId` when `token` holds it and notifies the subscribers.\n */\n mount(token: string): () => void;\n /**\n * From a layout effect of the control that rendered with `controlId`: takes it again after a\n * StrictMode effect replay freed it; if another control took it meanwhile, notifies the\n * subscribers so both re-render and agree.\n */\n hold(token: string): void;\n /**\n * From a passive effect of a mounted control that rendered without `controlId`: frees\n * `controlId` when its holder is not mounted (its render was discarded) and notifies the\n * subscribers.\n */\n releaseStale(): void;\n /** Subscribes to changes of the holder (for `useSyncExternalStore`); returns the unsubscribe. */\n subscribe(onChange: () => void): () => void;\n /** A number that changes whenever the subscribers are notified (the store snapshot). */\n getVersion(): number;\n}\n\nclass FieldControlIdClaimStore implements FieldControlIdClaim {\n private holder: string | undefined = undefined;\n private readonly mounted = new Set<string>();\n private readonly listeners = new Set<() => void>();\n private version = 0;\n\n claim = (token: string): boolean => {\n if (this.holder === undefined) this.holder = token;\n return this.holder === token;\n };\n\n mount = (token: string): (() => void) => {\n this.mounted.add(token);\n return () => {\n this.mounted.delete(token);\n if (this.holder === token) this.free();\n };\n };\n\n hold = (token: string): void => {\n if (this.holder === undefined) this.holder = token;\n else if (this.holder !== token) this.notify();\n };\n\n releaseStale = (): void => {\n if (this.holder !== undefined && !this.mounted.has(this.holder)) this.free();\n };\n\n subscribe = (onChange: () => void): (() => void) => {\n this.listeners.add(onChange);\n return () => {\n this.listeners.delete(onChange);\n };\n };\n\n getVersion = (): number => this.version;\n\n private free(): void {\n this.holder = undefined;\n this.notify();\n }\n\n private notify(): void {\n this.version += 1;\n for (const listener of Array.from(this.listeners)) listener();\n }\n}\n\n/**\n * Creates the {@link FieldControlIdClaim} a Field puts into its context (one per Field instance,\n * e.g. `const [claim] = useState(createFieldControlIdClaim)`).\n *\n * @internal Not exported from the package.\n */\nexport function createFieldControlIdClaim(): FieldControlIdClaim {\n return new FieldControlIdClaimStore();\n}\n\nconst subscribeNothing = (): (() => void) => () => {};\nconst noVersion = (): number => 0;\n\n/**\n * Context provided by `Field`. `null` outside a Field (controls then use only their own props).\n */\nexport const FieldContext: React.Context<FieldContextValue | null> =\n React.createContext<FieldContextValue | null>(null);\nFieldContext.displayName = 'FieldContext';\n\n/** The surrounding Field's context value, or `null` when the control is not inside a Field. */\nexport function useFieldContext(): FieldContextValue | null {\n return React.useContext(FieldContext);\n}\n\n/** The labelling/validation props a control receives from its consumer and from its Field. */\nexport interface FieldControlProps {\n id?: string;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n 'aria-invalid'?: React.AriaAttributes['aria-invalid'];\n 'aria-required'?: React.AriaAttributes['aria-required'];\n required?: boolean;\n}\n\n/** Options of {@link useFieldControl}. */\nexport interface UseFieldControlOptions {\n /**\n * Whether the focusable element can be the target of `<label htmlFor>` (`<input>`, `<button>`,\n * `<select>`, `<textarea>`). Non-labelable controls (`role=\"radiogroup\"`/`role=\"group\"` divs)\n * are named with `aria-labelledby` instead.\n * @default true\n */\n labelable?: boolean;\n /**\n * Whether the focusable element is a native form control (Input, Select, Textarea, Slider,\n * SpinButton's input), so the Field's `required` becomes the native `required` attribute and\n * constraint validation runs. Controls built on buttons or divs validate through `HiddenInput`.\n * @default false\n */\n nativeRequired?: boolean;\n}\n\n/**\n * Merges a control's own labelling props with its surrounding `Field`. Spread the result onto the\n * **focusable element** (for a control whose root wraps it, not onto the root). Consumer values\n * are merged, never overwritten, and only defined keys are returned, so spreading never clears an\n * attribute.\n *\n * - `id`: the consumer's id; else, when the Field already gave `controlId` to its first child\n * (`controlIdAssigned`) — so this control is nested inside that child or a later sibling — a\n * generated id of its own; else the Field's `controlId`, which only one control takes when the\n * Field provides a `controlIdClaim` (several controls inside a plain `<div>`: the first one\n * takes it, the others get a generated id of their own). The first child itself receives\n * `controlId` as its `id` prop from Field, so it keeps it.\n * - `aria-labelledby`: when the consumer set an `aria-label`, only the consumer's\n * `aria-labelledby`; otherwise, when the control is not labelable **or** its id is not the\n * Field's `controlId` (it carries its own id, or is not the Field's first child, so\n * `<label htmlFor>` does not reach it), the consumer's ids plus the Field's `labelId`.\n * - `aria-describedby`: the consumer's ids, then the Field's validation message\n * (`validationMessageId`, else `errorId`), then its hint (deduplicated). A warning, success or\n * neutral message describes the control too.\n * - `aria-invalid` / `aria-required`: the consumer's value, else `true` when the Field is\n * invalid/required (`aria-required`: not when the consumer passes `required={false}`). Only the\n * `error` validation state makes the Field invalid.\n * - `required`: only with `nativeRequired` — the consumer's value, else the Field's `required`.\n * - `aria-label`: passed through.\n *\n * @example\n * const fieldProps = useFieldControl({ id, 'aria-label': ariaLabel, 'aria-labelledby': labelledBy,\n * 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired });\n * return <button role=\"switch\" {...fieldProps} />;\n */\nexport function useFieldControl(\n props: FieldControlProps,\n options: UseFieldControlOptions = {},\n): FieldControlProps {\n const field = useFieldContext();\n // Always called (stable hook order); used only when the Field's controlId is taken. It is also\n // the control's token in the Field's controlIdClaim.\n const fallbackId = useId('field-control');\n const { labelable = true, nativeRequired = false } = options;\n\n // A control without an id of its own wants the Field's controlId unless Field gave it to its\n // first child. With a claim, only the first such control takes it.\n const wantsControlId = field !== null && props.id === undefined && !field.controlIdAssigned;\n const claim = wantsControlId ? field.controlIdClaim : undefined;\n // Re-render when the controlId is freed, so the next control takes it over.\n React.useSyncExternalStore(\n claim ? claim.subscribe : subscribeNothing,\n claim ? claim.getVersion : noVersion,\n claim ? claim.getVersion : noVersion,\n );\n const holdsControlId = wantsControlId && (claim ? claim.claim(fallbackId) : true);\n // Layout effects do nothing on the server, and React 19 no longer warns about them there.\n React.useLayoutEffect(() => claim?.mount(fallbackId), [claim, fallbackId]);\n React.useLayoutEffect(() => {\n if (claim && holdsControlId) claim.hold(fallbackId);\n }, [claim, holdsControlId, fallbackId]);\n // After every commit (all layout effects of the commit, and so every mount, have run): a\n // control rendered without controlId frees one held by a render that never committed.\n React.useEffect(() => {\n if (claim && !holdsControlId) claim.releaseStale();\n });\n\n const id =\n props.id ?? (field === null ? undefined : holdsControlId ? field.controlId : fallbackId);\n\n let labelledBy = props['aria-labelledby'];\n if (field && props['aria-label'] === undefined && (!labelable || id !== field.controlId)) {\n labelledBy = joinIds(labelledBy, field.labelId);\n }\n\n // The message in any validation state (a context built before 0.6 has only `errorId`).\n const describedBy = field\n ? joinIds(props['aria-describedby'], field.validationMessageId ?? field.errorId, field.hintId)\n : props['aria-describedby'];\n const invalid = props['aria-invalid'] ?? (field?.invalid || undefined);\n // A consumer `required={false}` (native controls pass `required`) also keeps the Field's\n // required state out of aria-required, so the announced state matches native validation.\n const ariaRequired =\n props['aria-required'] ?? (props.required === false ? undefined : field?.required || undefined);\n const required = nativeRequired ? (props.required ?? (field?.required || undefined)) : undefined;\n\n const result: FieldControlProps = {};\n if (id !== undefined) result.id = id;\n if (props['aria-label'] !== undefined) result['aria-label'] = props['aria-label'];\n if (labelledBy !== undefined) result['aria-labelledby'] = labelledBy;\n if (describedBy !== undefined) result['aria-describedby'] = describedBy;\n if (invalid !== undefined) result['aria-invalid'] = invalid;\n if (ariaRequired !== undefined) result['aria-required'] = ariaRequired;\n if (required !== undefined) result.required = required;\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useFieldControl.cjs","names":["React","useId","joinIds","require_runtime","require_useId","require_aria","react","FieldControlIdClaimStore","token","onChange","listener","createFieldControlIdClaim","subscribeNothing","noVersion","FieldContext","useFieldContext","useFieldControl","field","fallbackId","options","wantsControlId","props","claim","holdsControlId","id","labelledBy","labelable","describedBy","invalid","ariaRequired","required","nativeRequired","result"],"sources":["../../src/hooks/useFieldControl.ts"],"sourcesContent":["import * as React from 'react';\nimport { joinIds } from '../lib/aria';\nimport type { CoreSize, ValidationState } from '../lib/types';\nimport { useId } from './useId';\n\n/**\n * What a `Field` tells the controls inside it: the ids of its label, hint and validation message,\n * and its validation, invalid and required state. `Field` provides it; every library input reads\n * it through {@link useFieldControl}, and so can your own control.\n */\nexport interface FieldContextValue {\n /** The id the Field's `<label htmlFor>` points at. */\n controlId: string;\n /** The id of the Field's `<label>`, used for `aria-labelledby` on non-labelable controls. */\n labelId: string | undefined;\n /** The id of the rendered hint, if any. */\n hintId: string | undefined;\n /**\n * The id of the rendered error message, if any: set only while the validation message is an\n * error (see `validationMessageId` for every state).\n */\n errorId: string | undefined;\n /** Whether the Field is in an error state (only the `error` validation state is invalid). */\n invalid: boolean;\n /** Whether the Field is required. */\n required: boolean;\n /** Field renders the error message itself; controls with their own `error` must not repeat it. */\n hasErrorMessage: boolean;\n /**\n * Field already gave `controlId` to its first child, a label-for merge target (component,\n * labelable element, custom element), whether Field merged it or it was the child's own id; a\n * control that receives no `id` prop is then not that child (it is nested inside it, e.g.\n * `Field > Tooltip > Input`, or a later sibling) and must not reuse `controlId`.\n * {@link useFieldControl} gives such a control its own id and names it through\n * `aria-labelledby`. Absent or `false`: a control without an `id` uses `controlId`, subject to\n * `controlIdClaim`.\n */\n controlIdAssigned?: boolean;\n /**\n * Hands `controlId` to one control when `controlIdAssigned` is not set — Field left its first\n * child alone (a plain `<div>`, a Fragment), so every library control inside reads this context.\n * The first control without an `id` of its own takes `controlId`; the others get their own id\n * and are named through `aria-labelledby`, so no id is duplicated. `Field` creates one for each\n * Field. Absent: every control without an `id` uses `controlId`.\n */\n controlIdClaim?: FieldControlIdClaim;\n /**\n * The Field's validation state (`validationState`, or `'error'` from `error`). Absent in\n * contexts built before 0.6: read it as `invalid ? 'error' : 'none'`.\n */\n validationState?: ValidationState;\n /**\n * The id of the rendered validation message in any state (error, warning, success, neutral).\n * `errorId` is set only while that message is an error.\n */\n validationMessageId?: string;\n /**\n * The Field's own `size` prop, when set: the default size of the text inputs and pickers inside\n * it (their own `size` wins). Absent when the Field has no `size` (the controls then fall back to\n * `WaveProvider inputDefaults`) and in contexts built before 0.9.\n */\n size?: CoreSize;\n}\n\n/**\n * Decides which control inside a `Field` takes the Field's `controlId` (the id its\n * `<label htmlFor>` points at) when several library controls without an `id` of their own read\n * the same context. `Field` creates one for each Field and passes it as\n * {@link FieldContextValue.controlIdClaim}; {@link useFieldControl} calls the methods, so a\n * control never calls them itself.\n *\n * - The first control to render takes `controlId`: on the first render (and on the server, so\n * hydration matches) that is the first one in document order. It keeps it across re-renders.\n * - When the holder unmounts or stops wanting `controlId` (it gets an `id` of its own), the\n * subscribers are notified, re-render, and the first of them takes `controlId` over.\n * - A render that claimed `controlId` but never committed (a discarded concurrent render) does not\n * keep it: a mounted control that renders without `controlId` frees it.\n */\nexport interface FieldControlIdClaim {\n /**\n * During render, with the control's stable token (its `useId`): gives `controlId` to `token`\n * when no control holds it, and returns whether `token` holds it. Idempotent per token, so\n * StrictMode double renders and later re-renders agree.\n */\n claim(token: string): boolean;\n /**\n * From a layout effect while the control wants `controlId`: marks `token` mounted. The returned\n * cleanup frees `controlId` when `token` holds it and notifies the subscribers.\n */\n mount(token: string): () => void;\n /**\n * From a layout effect of the control that rendered with `controlId`: takes it again after a\n * StrictMode effect replay freed it; if another control took it meanwhile, notifies the\n * subscribers so both re-render and agree.\n */\n hold(token: string): void;\n /**\n * From a passive effect of a mounted control that rendered without `controlId`: frees\n * `controlId` when its holder is not mounted (its render was discarded) and notifies the\n * subscribers.\n */\n releaseStale(): void;\n /** Subscribes to changes of the holder (for `useSyncExternalStore`); returns the unsubscribe. */\n subscribe(onChange: () => void): () => void;\n /** A number that changes whenever the subscribers are notified (the store snapshot). */\n getVersion(): number;\n}\n\nclass FieldControlIdClaimStore implements FieldControlIdClaim {\n private holder: string | undefined = undefined;\n private readonly mounted = new Set<string>();\n private readonly listeners = new Set<() => void>();\n private version = 0;\n\n claim = (token: string): boolean => {\n if (this.holder === undefined) this.holder = token;\n return this.holder === token;\n };\n\n mount = (token: string): (() => void) => {\n this.mounted.add(token);\n return () => {\n this.mounted.delete(token);\n if (this.holder === token) this.free();\n };\n };\n\n hold = (token: string): void => {\n if (this.holder === undefined) this.holder = token;\n else if (this.holder !== token) this.notify();\n };\n\n releaseStale = (): void => {\n if (this.holder !== undefined && !this.mounted.has(this.holder)) this.free();\n };\n\n subscribe = (onChange: () => void): (() => void) => {\n this.listeners.add(onChange);\n return () => {\n this.listeners.delete(onChange);\n };\n };\n\n getVersion = (): number => this.version;\n\n private free(): void {\n this.holder = undefined;\n this.notify();\n }\n\n private notify(): void {\n this.version += 1;\n for (const listener of Array.from(this.listeners)) listener();\n }\n}\n\n/**\n * Creates the {@link FieldControlIdClaim} a Field puts into its context (one per Field instance,\n * e.g. `const [claim] = useState(createFieldControlIdClaim)`).\n *\n * @internal Not exported from the package.\n */\nexport function createFieldControlIdClaim(): FieldControlIdClaim {\n return new FieldControlIdClaimStore();\n}\n\nconst subscribeNothing = (): (() => void) => () => {};\nconst noVersion = (): number => 0;\n\n/**\n * Context provided by `Field`. `null` outside a Field (controls then use only their own props).\n */\nexport const FieldContext: React.Context<FieldContextValue | null> =\n React.createContext<FieldContextValue | null>(null);\nFieldContext.displayName = 'FieldContext';\n\n/** The surrounding Field's context value, or `null` when the control is not inside a Field. */\nexport function useFieldContext(): FieldContextValue | null {\n return React.useContext(FieldContext);\n}\n\n/** The labelling/validation props a control receives from its consumer and from its Field. */\nexport interface FieldControlProps {\n id?: string;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n 'aria-invalid'?: React.AriaAttributes['aria-invalid'];\n 'aria-required'?: React.AriaAttributes['aria-required'];\n required?: boolean;\n}\n\n/** Options of {@link useFieldControl}. */\nexport interface UseFieldControlOptions {\n /**\n * Whether the focusable element can be the target of `<label htmlFor>` (`<input>`, `<button>`,\n * `<select>`, `<textarea>`). Non-labelable controls (`role=\"radiogroup\"`/`role=\"group\"` divs)\n * are named with `aria-labelledby` instead.\n * @default true\n */\n labelable?: boolean;\n /**\n * Whether the focusable element is a native form control (Input, Select, Textarea, Slider,\n * SpinButton's input), so the Field's `required` becomes the native `required` attribute and\n * constraint validation runs. Controls built on buttons or divs validate through `HiddenInput`.\n * @default false\n */\n nativeRequired?: boolean;\n}\n\n/**\n * Merges a control's own labelling props with its surrounding `Field`. Spread the result onto the\n * **focusable element** (for a control whose root wraps it, not onto the root). Consumer values\n * are merged, never overwritten, and only defined keys are returned, so spreading never clears an\n * attribute.\n *\n * - `id`: the consumer's id; else, when the Field already gave `controlId` to its first child\n * (`controlIdAssigned`) — so this control is nested inside that child or a later sibling — a\n * generated id of its own; else the Field's `controlId`, which only one control takes when the\n * Field provides a `controlIdClaim` (several controls inside a plain `<div>`: the first one\n * takes it, the others get a generated id of their own). The first child itself receives\n * `controlId` as its `id` prop from Field, so it keeps it.\n * - `aria-labelledby`: when the consumer set an `aria-label`, only the consumer's\n * `aria-labelledby`; otherwise, when the control is not labelable **or** its id is not the\n * Field's `controlId` (it carries its own id, or is not the Field's first child, so\n * `<label htmlFor>` does not reach it), the consumer's ids plus the Field's `labelId`.\n * - `aria-describedby`: the consumer's ids, then the Field's validation message\n * (`validationMessageId`, else `errorId`), then its hint (deduplicated). A warning, success or\n * neutral message describes the control too.\n * - `aria-invalid` / `aria-required`: the consumer's value, else `true` when the Field is\n * invalid/required (`aria-required`: not when the consumer passes `required={false}`). Only the\n * `error` validation state makes the Field invalid.\n * - `required`: only with `nativeRequired` — the consumer's value, else the Field's `required`.\n * - `aria-label`: passed through.\n *\n * @example\n * const fieldProps = useFieldControl({ id, 'aria-label': ariaLabel, 'aria-labelledby': labelledBy,\n * 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired });\n * return <button role=\"switch\" {...fieldProps} />;\n */\nexport function useFieldControl(\n props: FieldControlProps,\n options: UseFieldControlOptions = {},\n): FieldControlProps {\n const field = useFieldContext();\n // Always called (stable hook order); used only when the Field's controlId is taken. It is also\n // the control's token in the Field's controlIdClaim.\n const fallbackId = useId('field-control');\n const { labelable = true, nativeRequired = false } = options;\n\n // A control without an id of its own wants the Field's controlId unless Field gave it to its\n // first child. With a claim, only the first such control takes it.\n const wantsControlId = field !== null && props.id === undefined && !field.controlIdAssigned;\n const claim = wantsControlId ? field.controlIdClaim : undefined;\n // Re-render when the controlId is freed, so the next control takes it over.\n React.useSyncExternalStore(\n claim ? claim.subscribe : subscribeNothing,\n claim ? claim.getVersion : noVersion,\n claim ? claim.getVersion : noVersion,\n );\n const holdsControlId = wantsControlId && (claim ? claim.claim(fallbackId) : true);\n // Layout effects do nothing on the server, and React 19 no longer warns about them there.\n React.useLayoutEffect(() => claim?.mount(fallbackId), [claim, fallbackId]);\n React.useLayoutEffect(() => {\n if (claim && holdsControlId) claim.hold(fallbackId);\n }, [claim, holdsControlId, fallbackId]);\n // After every commit (all layout effects of the commit, and so every mount, have run): a\n // control rendered without controlId frees one held by a render that never committed.\n React.useEffect(() => {\n if (claim && !holdsControlId) claim.releaseStale();\n });\n\n const id =\n props.id ?? (field === null ? undefined : holdsControlId ? field.controlId : fallbackId);\n\n let labelledBy = props['aria-labelledby'];\n if (field && props['aria-label'] === undefined && (!labelable || id !== field.controlId)) {\n labelledBy = joinIds(labelledBy, field.labelId);\n }\n\n // The message in any validation state (a context built before 0.6 has only `errorId`).\n const describedBy = field\n ? joinIds(props['aria-describedby'], field.validationMessageId ?? field.errorId, field.hintId)\n : props['aria-describedby'];\n const invalid = props['aria-invalid'] ?? (field?.invalid || undefined);\n // A consumer `required={false}` (native controls pass `required`) also keeps the Field's\n // required state out of aria-required, so the announced state matches native validation.\n const ariaRequired =\n props['aria-required'] ?? (props.required === false ? undefined : field?.required || undefined);\n const required = nativeRequired ? (props.required ?? (field?.required || undefined)) : undefined;\n\n const result: FieldControlProps = {};\n if (id !== undefined) result.id = id;\n if (props['aria-label'] !== undefined) result['aria-label'] = props['aria-label'];\n if (labelledBy !== undefined) result['aria-labelledby'] = labelledBy;\n if (describedBy !== undefined) result['aria-describedby'] = describedBy;\n if (invalid !== undefined) result['aria-invalid'] = invalid;\n if (ariaRequired !== undefined) result['aria-required'] = ariaRequired;\n if (required !== undefined) result.required = required;\n return result;\n}\n"],"mappings":"gKA4GA,IAAMO,EAAN,KAA8D,CAC5D,OAAqC,IAAA,GACrC,QAA2B,IAAI,IAC/B,UAA6B,IAAI,IACjC,QAAkB,EAElB,MAAS,IACH,KAAK,SAAW,IAAA,KAAW,KAAK,OAASC,GACtC,KAAK,SAAWA,GAGzB,MAAS,IACP,KAAK,QAAQ,IAAIA,CAAK,MACT,CACX,KAAK,QAAQ,OAAOA,CAAK,EACrB,KAAK,SAAWA,GAAO,KAAK,KAAK,CACvC,GAGF,KAAQ,GAAwB,CAC1B,KAAK,SAAW,IAAA,GAAW,KAAK,OAASA,EACpC,KAAK,SAAWA,GAAO,KAAK,OAAO,CAC9C,EAEA,iBAA2B,CACrB,KAAK,SAAW,IAAA,IAAa,CAAC,KAAK,QAAQ,IAAI,KAAK,MAAM,GAAG,KAAK,KAAK,CAC7E,EAEA,UAAa,IACX,KAAK,UAAU,IAAIC,CAAQ,MACd,CACX,KAAK,UAAU,OAAOA,CAAQ,CAChC,GAGF,eAA2B,KAAK,QAEhC,MAAqB,CACnB,KAAK,OAAS,IAAA,GACd,KAAK,OAAO,CACd,CAEA,QAAuB,CACrB,KAAK,SAAW,EAChB,IAAK,IAAMC,KAAY,MAAM,KAAK,KAAK,SAAS,EAAGA,EAAS,CAC9D,CACF,EAQA,SAAgBC,GAAiD,CAC/D,OAAO,IAAIJ,CACb,CAEA,IAAMK,UAA6C,CAAC,EAC9CC,MAA0B,EAKnBC,EACXd,EAAM,cAAwC,IAAI,EACpD,EAAa,YAAc,eAG3B,SAAgBe,GAA4C,CAC1D,OAAOf,EAAM,WAAWc,CAAY,CACtC,CA6DA,SAAgBE,EACd,EACA,EAAkC,CAAC,EAChB,CACnB,IAAMC,EAAQF,EAAgB,EAGxBG,EAAajB,EAAAA,MAAM,eAAe,EAClC,CAAE,YAAY,GAAM,iBAAiB,IAAUkB,EAI/CC,EAAiBH,IAAU,MAAQI,EAAM,KAAO,IAAA,IAAa,CAACJ,EAAM,kBACpEK,EAAQF,EAAiBH,EAAM,eAAiB,IAAA,GAEtDX,EAAM,qBACJgB,EAAQA,EAAM,UAAYV,EAC1BU,EAAQA,EAAM,WAAaT,EAC3BS,EAAQA,EAAM,WAAaT,CAC7B,EACA,IAAMU,EAAiBH,IAAmB,IAAQE,EAAM,MAAMJ,CAAU,GAExEZ,EAAM,oBAAsBgB,GAAO,MAAMJ,CAAU,EAAG,CAACI,EAAOJ,CAAU,CAAC,EACzEZ,EAAM,oBAAsB,CACtBgB,GAASC,GAAgBD,EAAM,KAAKJ,CAAU,CACpD,EAAG,CAACI,EAAOC,EAAgBL,CAAU,CAAC,EAGtCZ,EAAM,cAAgB,CAChBgB,GAAS,CAACC,GAAgBD,EAAM,aAAa,CACnD,CAAC,EAED,IAAME,EACJH,EAAM,KAAOJ,IAAU,KAAO,IAAA,GAAYM,EAAiBN,EAAM,UAAYC,GAE3EO,EAAaJ,EAAM,mBACnBJ,GAASI,EAAM,gBAAkB,IAAA,KAAc,CAACK,GAAaF,IAAOP,EAAM,aAC5E,EAAaf,EAAAA,QAAQuB,EAAYR,EAAM,OAAO,GAIhD,IAAMU,EAAcV,EAChBf,EAAAA,QAAQmB,EAAM,oBAAqBJ,EAAM,qBAAuBA,EAAM,QAASA,EAAM,MAAM,EAC3FI,EAAM,oBACJO,EAAUP,EAAM,kBAAoBJ,GAAO,SAAW,IAAA,IAGtDY,EACJR,EAAM,mBAAqBA,EAAM,WAAa,GAAQ,IAAA,GAAYJ,GAAO,UAAY,IAAA,IACjFa,EAAWC,EAAkBV,EAAM,WAAaJ,GAAO,UAAY,IAAA,IAAc,IAAA,GAEjFe,EAA4B,CAAC,EAQnC,OAPIR,IAAO,IAAA,KAAW,EAAO,GAAKA,GAC9BH,EAAM,gBAAkB,IAAA,KAAW,EAAO,cAAgBA,EAAM,eAChEI,IAAe,IAAA,KAAW,EAAO,mBAAqBA,GACtDE,IAAgB,IAAA,KAAW,EAAO,oBAAsBA,GACxDC,IAAY,IAAA,KAAW,EAAO,gBAAkBA,GAChDC,IAAiB,IAAA,KAAW,EAAO,iBAAmBA,GACtDC,IAAa,IAAA,KAAW,EAAO,SAAWA,GACvCE,CACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFieldControl.mjs","names":["useId","useId$1","React","FieldControlIdClaimStore","token","onChange","listener","createFieldControlIdClaim","subscribeNothing","noVersion","FieldContext","useFieldContext","useFieldControl","field","fallbackId","options","wantsControlId","props","claim","holdsControlId","id","labelledBy","labelable","joinIds","describedBy","invalid","ariaRequired","required","nativeRequired","result"],"sources":["../../src/hooks/useFieldControl.ts"],"sourcesContent":["import * as React from 'react';\nimport { joinIds } from '../lib/aria';\nimport type { ValidationState } from '../lib/types';\nimport { useId } from './useId';\n\n/**\n * What a `Field` tells the controls inside it: the ids of its label, hint and validation message,\n * and its validation, invalid and required state. `Field` provides it; every library input reads\n * it through {@link useFieldControl}, and so can your own control.\n */\nexport interface FieldContextValue {\n /** The id the Field's `<label htmlFor>` points at. */\n controlId: string;\n /** The id of the Field's `<label>`, used for `aria-labelledby` on non-labelable controls. */\n labelId: string | undefined;\n /** The id of the rendered hint, if any. */\n hintId: string | undefined;\n /**\n * The id of the rendered error message, if any: set only while the validation message is an\n * error (see `validationMessageId` for every state).\n */\n errorId: string | undefined;\n /** Whether the Field is in an error state (only the `error` validation state is invalid). */\n invalid: boolean;\n /** Whether the Field is required. */\n required: boolean;\n /** Field renders the error message itself; controls with their own `error` must not repeat it. */\n hasErrorMessage: boolean;\n /**\n * Field already gave `controlId` to its first child, a label-for merge target (component,\n * labelable element, custom element), whether Field merged it or it was the child's own id; a\n * control that receives no `id` prop is then not that child (it is nested inside it, e.g.\n * `Field > Tooltip > Input`, or a later sibling) and must not reuse `controlId`.\n * {@link useFieldControl} gives such a control its own id and names it through\n * `aria-labelledby`. Absent or `false`: a control without an `id` uses `controlId`, subject to\n * `controlIdClaim`.\n */\n controlIdAssigned?: boolean;\n /**\n * Hands `controlId` to one control when `controlIdAssigned` is not set — Field left its first\n * child alone (a plain `<div>`, a Fragment), so every library control inside reads this context.\n * The first control without an `id` of its own takes `controlId`; the others get their own id\n * and are named through `aria-labelledby`, so no id is duplicated. `Field` creates one for each\n * Field. Absent: every control without an `id` uses `controlId`.\n */\n controlIdClaim?: FieldControlIdClaim;\n /**\n * The Field's validation state (`validationState`, or `'error'` from `error`). Absent in\n * contexts built before 0.6: read it as `invalid ? 'error' : 'none'`.\n */\n validationState?: ValidationState;\n /**\n * The id of the rendered validation message in any state (error, warning, success, neutral).\n * `errorId` is set only while that message is an error.\n */\n validationMessageId?: string;\n}\n\n/**\n * Decides which control inside a `Field` takes the Field's `controlId` (the id its\n * `<label htmlFor>` points at) when several library controls without an `id` of their own read\n * the same context. `Field` creates one for each Field and passes it as\n * {@link FieldContextValue.controlIdClaim}; {@link useFieldControl} calls the methods, so a\n * control never calls them itself.\n *\n * - The first control to render takes `controlId`: on the first render (and on the server, so\n * hydration matches) that is the first one in document order. It keeps it across re-renders.\n * - When the holder unmounts or stops wanting `controlId` (it gets an `id` of its own), the\n * subscribers are notified, re-render, and the first of them takes `controlId` over.\n * - A render that claimed `controlId` but never committed (a discarded concurrent render) does not\n * keep it: a mounted control that renders without `controlId` frees it.\n */\nexport interface FieldControlIdClaim {\n /**\n * During render, with the control's stable token (its `useId`): gives `controlId` to `token`\n * when no control holds it, and returns whether `token` holds it. Idempotent per token, so\n * StrictMode double renders and later re-renders agree.\n */\n claim(token: string): boolean;\n /**\n * From a layout effect while the control wants `controlId`: marks `token` mounted. The returned\n * cleanup frees `controlId` when `token` holds it and notifies the subscribers.\n */\n mount(token: string): () => void;\n /**\n * From a layout effect of the control that rendered with `controlId`: takes it again after a\n * StrictMode effect replay freed it; if another control took it meanwhile, notifies the\n * subscribers so both re-render and agree.\n */\n hold(token: string): void;\n /**\n * From a passive effect of a mounted control that rendered without `controlId`: frees\n * `controlId` when its holder is not mounted (its render was discarded) and notifies the\n * subscribers.\n */\n releaseStale(): void;\n /** Subscribes to changes of the holder (for `useSyncExternalStore`); returns the unsubscribe. */\n subscribe(onChange: () => void): () => void;\n /** A number that changes whenever the subscribers are notified (the store snapshot). */\n getVersion(): number;\n}\n\nclass FieldControlIdClaimStore implements FieldControlIdClaim {\n private holder: string | undefined = undefined;\n private readonly mounted = new Set<string>();\n private readonly listeners = new Set<() => void>();\n private version = 0;\n\n claim = (token: string): boolean => {\n if (this.holder === undefined) this.holder = token;\n return this.holder === token;\n };\n\n mount = (token: string): (() => void) => {\n this.mounted.add(token);\n return () => {\n this.mounted.delete(token);\n if (this.holder === token) this.free();\n };\n };\n\n hold = (token: string): void => {\n if (this.holder === undefined) this.holder = token;\n else if (this.holder !== token) this.notify();\n };\n\n releaseStale = (): void => {\n if (this.holder !== undefined && !this.mounted.has(this.holder)) this.free();\n };\n\n subscribe = (onChange: () => void): (() => void) => {\n this.listeners.add(onChange);\n return () => {\n this.listeners.delete(onChange);\n };\n };\n\n getVersion = (): number => this.version;\n\n private free(): void {\n this.holder = undefined;\n this.notify();\n }\n\n private notify(): void {\n this.version += 1;\n for (const listener of Array.from(this.listeners)) listener();\n }\n}\n\n/**\n * Creates the {@link FieldControlIdClaim} a Field puts into its context (one per Field instance,\n * e.g. `const [claim] = useState(createFieldControlIdClaim)`).\n *\n * @internal Not exported from the package.\n */\nexport function createFieldControlIdClaim(): FieldControlIdClaim {\n return new FieldControlIdClaimStore();\n}\n\nconst subscribeNothing = (): (() => void) => () => {};\nconst noVersion = (): number => 0;\n\n/**\n * Context provided by `Field`. `null` outside a Field (controls then use only their own props).\n */\nexport const FieldContext: React.Context<FieldContextValue | null> =\n React.createContext<FieldContextValue | null>(null);\nFieldContext.displayName = 'FieldContext';\n\n/** The surrounding Field's context value, or `null` when the control is not inside a Field. */\nexport function useFieldContext(): FieldContextValue | null {\n return React.useContext(FieldContext);\n}\n\n/** The labelling/validation props a control receives from its consumer and from its Field. */\nexport interface FieldControlProps {\n id?: string;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n 'aria-invalid'?: React.AriaAttributes['aria-invalid'];\n 'aria-required'?: React.AriaAttributes['aria-required'];\n required?: boolean;\n}\n\n/** Options of {@link useFieldControl}. */\nexport interface UseFieldControlOptions {\n /**\n * Whether the focusable element can be the target of `<label htmlFor>` (`<input>`, `<button>`,\n * `<select>`, `<textarea>`). Non-labelable controls (`role=\"radiogroup\"`/`role=\"group\"` divs)\n * are named with `aria-labelledby` instead.\n * @default true\n */\n labelable?: boolean;\n /**\n * Whether the focusable element is a native form control (Input, Select, Textarea, Slider,\n * SpinButton's input), so the Field's `required` becomes the native `required` attribute and\n * constraint validation runs. Controls built on buttons or divs validate through `HiddenInput`.\n * @default false\n */\n nativeRequired?: boolean;\n}\n\n/**\n * Merges a control's own labelling props with its surrounding `Field`. Spread the result onto the\n * **focusable element** (for a control whose root wraps it, not onto the root). Consumer values\n * are merged, never overwritten, and only defined keys are returned, so spreading never clears an\n * attribute.\n *\n * - `id`: the consumer's id; else, when the Field already gave `controlId` to its first child\n * (`controlIdAssigned`) — so this control is nested inside that child or a later sibling — a\n * generated id of its own; else the Field's `controlId`, which only one control takes when the\n * Field provides a `controlIdClaim` (several controls inside a plain `<div>`: the first one\n * takes it, the others get a generated id of their own). The first child itself receives\n * `controlId` as its `id` prop from Field, so it keeps it.\n * - `aria-labelledby`: when the consumer set an `aria-label`, only the consumer's\n * `aria-labelledby`; otherwise, when the control is not labelable **or** its id is not the\n * Field's `controlId` (it carries its own id, or is not the Field's first child, so\n * `<label htmlFor>` does not reach it), the consumer's ids plus the Field's `labelId`.\n * - `aria-describedby`: the consumer's ids, then the Field's validation message\n * (`validationMessageId`, else `errorId`), then its hint (deduplicated). A warning, success or\n * neutral message describes the control too.\n * - `aria-invalid` / `aria-required`: the consumer's value, else `true` when the Field is\n * invalid/required (`aria-required`: not when the consumer passes `required={false}`). Only the\n * `error` validation state makes the Field invalid.\n * - `required`: only with `nativeRequired` — the consumer's value, else the Field's `required`.\n * - `aria-label`: passed through.\n *\n * @example\n * const fieldProps = useFieldControl({ id, 'aria-label': ariaLabel, 'aria-labelledby': labelledBy,\n * 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired });\n * return <button role=\"switch\" {...fieldProps} />;\n */\nexport function useFieldControl(\n props: FieldControlProps,\n options: UseFieldControlOptions = {},\n): FieldControlProps {\n const field = useFieldContext();\n // Always called (stable hook order); used only when the Field's controlId is taken. It is also\n // the control's token in the Field's controlIdClaim.\n const fallbackId = useId('field-control');\n const { labelable = true, nativeRequired = false } = options;\n\n // A control without an id of its own wants the Field's controlId unless Field gave it to its\n // first child. With a claim, only the first such control takes it.\n const wantsControlId = field !== null && props.id === undefined && !field.controlIdAssigned;\n const claim = wantsControlId ? field.controlIdClaim : undefined;\n // Re-render when the controlId is freed, so the next control takes it over.\n React.useSyncExternalStore(\n claim ? claim.subscribe : subscribeNothing,\n claim ? claim.getVersion : noVersion,\n claim ? claim.getVersion : noVersion,\n );\n const holdsControlId = wantsControlId && (claim ? claim.claim(fallbackId) : true);\n // Layout effects do nothing on the server, and React 19 no longer warns about them there.\n React.useLayoutEffect(() => claim?.mount(fallbackId), [claim, fallbackId]);\n React.useLayoutEffect(() => {\n if (claim && holdsControlId) claim.hold(fallbackId);\n }, [claim, holdsControlId, fallbackId]);\n // After every commit (all layout effects of the commit, and so every mount, have run): a\n // control rendered without controlId frees one held by a render that never committed.\n React.useEffect(() => {\n if (claim && !holdsControlId) claim.releaseStale();\n });\n\n const id =\n props.id ?? (field === null ? undefined : holdsControlId ? field.controlId : fallbackId);\n\n let labelledBy = props['aria-labelledby'];\n if (field && props['aria-label'] === undefined && (!labelable || id !== field.controlId)) {\n labelledBy = joinIds(labelledBy, field.labelId);\n }\n\n // The message in any validation state (a context built before 0.6 has only `errorId`).\n const describedBy = field\n ? joinIds(props['aria-describedby'], field.validationMessageId ?? field.errorId, field.hintId)\n : props['aria-describedby'];\n const invalid = props['aria-invalid'] ?? (field?.invalid || undefined);\n // A consumer `required={false}` (native controls pass `required`) also keeps the Field's\n // required state out of aria-required, so the announced state matches native validation.\n const ariaRequired =\n props['aria-required'] ?? (props.required === false ? undefined : field?.required || undefined);\n const required = nativeRequired ? (props.required ?? (field?.required || undefined)) : undefined;\n\n const result: FieldControlProps = {};\n if (id !== undefined) result.id = id;\n if (props['aria-label'] !== undefined) result['aria-label'] = props['aria-label'];\n if (labelledBy !== undefined) result['aria-labelledby'] = labelledBy;\n if (describedBy !== undefined) result['aria-describedby'] = describedBy;\n if (invalid !== undefined) result['aria-invalid'] = invalid;\n if (ariaRequired !== undefined) result['aria-required'] = ariaRequired;\n if (required !== undefined) result.required = required;\n return result;\n}\n"],"mappings":";;;;;AAsGA,IAAMG,IAAN,MAA8D;CAC5D,SAAqC,KAAA;CACrC,0BAA2B,IAAI,IAAY;CAC3C,4BAA6B,IAAI,IAAgB;CACjD,UAAkB;CAElB,SAAS,OACH,KAAK,WAAW,KAAA,MAAW,KAAK,SAASC,IACtC,KAAK,WAAWA;CAGzB,SAAS,OACP,KAAK,QAAQ,IAAIA,CAAK,SACT;EAEX,AADA,KAAK,QAAQ,OAAOA,CAAK,GACrB,KAAK,WAAWA,KAAO,KAAK,KAAK;CACvC;CAGF,QAAQ,MAAwB;EAC9B,AAAI,KAAK,WAAW,KAAA,IAAW,KAAK,SAASA,IACpC,KAAK,WAAWA,KAAO,KAAK,OAAO;CAC9C;CAEA,qBAA2B;EACzB,AAAI,KAAK,WAAW,KAAA,KAAa,CAAC,KAAK,QAAQ,IAAI,KAAK,MAAM,KAAG,KAAK,KAAK;CAC7E;CAEA,aAAa,OACX,KAAK,UAAU,IAAIC,CAAQ,SACd;EACX,KAAK,UAAU,OAAOA,CAAQ;CAChC;CAGF,mBAA2B,KAAK;CAEhC,OAAqB;EAEnB,AADA,KAAK,SAAS,KAAA,GACd,KAAK,OAAO;CACd;CAEA,SAAuB;EACrB,KAAK,WAAW;EAChB,KAAK,IAAMC,KAAY,MAAM,KAAK,KAAK,SAAS,GAAG,EAAS;CAC9D;AACF;AAQA,SAAgBC,IAAiD;CAC/D,OAAO,IAAIJ,EAAyB;AACtC;AAEA,IAAMK,gBAA6C,CAAC,GAC9CC,UAA0B,GAKnBC,IACXR,EAAM,cAAwC,IAAI;AACpD,EAAa,cAAc;AAG3B,SAAgBS,IAA4C;CAC1D,OAAOT,EAAM,WAAWQ,CAAY;AACtC;AA6DA,SAAgBE,EACd,GACA,IAAkC,CAAC,GAChB;CACnB,IAAMC,IAAQF,EAAgB,GAGxBG,IAAad,EAAM,eAAe,GAClC,EAAE,eAAY,IAAM,oBAAiB,OAAUe,GAI/CC,IAAiBH,MAAU,QAAQI,EAAM,OAAO,KAAA,KAAa,CAACJ,EAAM,mBACpEK,IAAQF,IAAiBH,EAAM,iBAAiB,KAAA;CAEtD,EAAM,qBACJK,IAAQA,EAAM,YAAYV,GAC1BU,IAAQA,EAAM,aAAaT,GAC3BS,IAAQA,EAAM,aAAaT,CAC7B;CACA,IAAMU,IAAiBH,MAAmB,MAAQE,EAAM,MAAMJ,CAAU;CAQxE,AANAZ,EAAM,sBAAsBgB,GAAO,MAAMJ,CAAU,GAAG,CAACI,GAAOJ,CAAU,CAAC,GACzEZ,EAAM,sBAAsB;EAC1B,AAAIgB,KAASC,KAAgBD,EAAM,KAAKJ,CAAU;CACpD,GAAG;EAACI;EAAOC;EAAgBL;CAAU,CAAC,GAGtCZ,EAAM,gBAAgB;EACpB,AAAIgB,KAAS,CAACC,KAAgBD,EAAM,aAAa;CACnD,CAAC;CAED,IAAME,IACJH,EAAM,OAAOJ,MAAU,OAAO,KAAA,IAAYM,IAAiBN,EAAM,YAAYC,IAE3EO,IAAaJ,EAAM;CACvB,AAAIJ,KAASI,EAAM,kBAAkB,KAAA,MAAc,CAACK,KAAaF,MAAOP,EAAM,eAC5E,IAAaU,EAAQF,GAAYR,EAAM,OAAO;CAIhD,IAAMW,IAAcX,IAChBU,EAAQN,EAAM,qBAAqBJ,EAAM,uBAAuBA,EAAM,SAASA,EAAM,MAAM,IAC3FI,EAAM,qBACJQ,IAAUR,EAAM,oBAAoBJ,GAAO,WAAW,KAAA,IAGtDa,IACJT,EAAM,qBAAqBA,EAAM,aAAa,KAAQ,KAAA,IAAYJ,GAAO,YAAY,KAAA,IACjFc,IAAWC,IAAkBX,EAAM,aAAaJ,GAAO,YAAY,KAAA,KAAc,KAAA,GAEjFgB,IAA4B,CAAC;CAQnC,OAPIT,MAAO,KAAA,MAAW,EAAO,KAAKA,IAC9BH,EAAM,kBAAkB,KAAA,MAAW,EAAO,gBAAgBA,EAAM,gBAChEI,MAAe,KAAA,MAAW,EAAO,qBAAqBA,IACtDG,MAAgB,KAAA,MAAW,EAAO,sBAAsBA,IACxDC,MAAY,KAAA,MAAW,EAAO,kBAAkBA,IAChDC,MAAiB,KAAA,MAAW,EAAO,mBAAmBA,IACtDC,MAAa,KAAA,MAAW,EAAO,WAAWA,IACvCE;AACT"}
1
+ {"version":3,"file":"useFieldControl.mjs","names":["useId","useId$1","React","FieldControlIdClaimStore","token","onChange","listener","createFieldControlIdClaim","subscribeNothing","noVersion","FieldContext","useFieldContext","useFieldControl","field","fallbackId","options","wantsControlId","props","claim","holdsControlId","id","labelledBy","labelable","joinIds","describedBy","invalid","ariaRequired","required","nativeRequired","result"],"sources":["../../src/hooks/useFieldControl.ts"],"sourcesContent":["import * as React from 'react';\nimport { joinIds } from '../lib/aria';\nimport type { CoreSize, ValidationState } from '../lib/types';\nimport { useId } from './useId';\n\n/**\n * What a `Field` tells the controls inside it: the ids of its label, hint and validation message,\n * and its validation, invalid and required state. `Field` provides it; every library input reads\n * it through {@link useFieldControl}, and so can your own control.\n */\nexport interface FieldContextValue {\n /** The id the Field's `<label htmlFor>` points at. */\n controlId: string;\n /** The id of the Field's `<label>`, used for `aria-labelledby` on non-labelable controls. */\n labelId: string | undefined;\n /** The id of the rendered hint, if any. */\n hintId: string | undefined;\n /**\n * The id of the rendered error message, if any: set only while the validation message is an\n * error (see `validationMessageId` for every state).\n */\n errorId: string | undefined;\n /** Whether the Field is in an error state (only the `error` validation state is invalid). */\n invalid: boolean;\n /** Whether the Field is required. */\n required: boolean;\n /** Field renders the error message itself; controls with their own `error` must not repeat it. */\n hasErrorMessage: boolean;\n /**\n * Field already gave `controlId` to its first child, a label-for merge target (component,\n * labelable element, custom element), whether Field merged it or it was the child's own id; a\n * control that receives no `id` prop is then not that child (it is nested inside it, e.g.\n * `Field > Tooltip > Input`, or a later sibling) and must not reuse `controlId`.\n * {@link useFieldControl} gives such a control its own id and names it through\n * `aria-labelledby`. Absent or `false`: a control without an `id` uses `controlId`, subject to\n * `controlIdClaim`.\n */\n controlIdAssigned?: boolean;\n /**\n * Hands `controlId` to one control when `controlIdAssigned` is not set — Field left its first\n * child alone (a plain `<div>`, a Fragment), so every library control inside reads this context.\n * The first control without an `id` of its own takes `controlId`; the others get their own id\n * and are named through `aria-labelledby`, so no id is duplicated. `Field` creates one for each\n * Field. Absent: every control without an `id` uses `controlId`.\n */\n controlIdClaim?: FieldControlIdClaim;\n /**\n * The Field's validation state (`validationState`, or `'error'` from `error`). Absent in\n * contexts built before 0.6: read it as `invalid ? 'error' : 'none'`.\n */\n validationState?: ValidationState;\n /**\n * The id of the rendered validation message in any state (error, warning, success, neutral).\n * `errorId` is set only while that message is an error.\n */\n validationMessageId?: string;\n /**\n * The Field's own `size` prop, when set: the default size of the text inputs and pickers inside\n * it (their own `size` wins). Absent when the Field has no `size` (the controls then fall back to\n * `WaveProvider inputDefaults`) and in contexts built before 0.9.\n */\n size?: CoreSize;\n}\n\n/**\n * Decides which control inside a `Field` takes the Field's `controlId` (the id its\n * `<label htmlFor>` points at) when several library controls without an `id` of their own read\n * the same context. `Field` creates one for each Field and passes it as\n * {@link FieldContextValue.controlIdClaim}; {@link useFieldControl} calls the methods, so a\n * control never calls them itself.\n *\n * - The first control to render takes `controlId`: on the first render (and on the server, so\n * hydration matches) that is the first one in document order. It keeps it across re-renders.\n * - When the holder unmounts or stops wanting `controlId` (it gets an `id` of its own), the\n * subscribers are notified, re-render, and the first of them takes `controlId` over.\n * - A render that claimed `controlId` but never committed (a discarded concurrent render) does not\n * keep it: a mounted control that renders without `controlId` frees it.\n */\nexport interface FieldControlIdClaim {\n /**\n * During render, with the control's stable token (its `useId`): gives `controlId` to `token`\n * when no control holds it, and returns whether `token` holds it. Idempotent per token, so\n * StrictMode double renders and later re-renders agree.\n */\n claim(token: string): boolean;\n /**\n * From a layout effect while the control wants `controlId`: marks `token` mounted. The returned\n * cleanup frees `controlId` when `token` holds it and notifies the subscribers.\n */\n mount(token: string): () => void;\n /**\n * From a layout effect of the control that rendered with `controlId`: takes it again after a\n * StrictMode effect replay freed it; if another control took it meanwhile, notifies the\n * subscribers so both re-render and agree.\n */\n hold(token: string): void;\n /**\n * From a passive effect of a mounted control that rendered without `controlId`: frees\n * `controlId` when its holder is not mounted (its render was discarded) and notifies the\n * subscribers.\n */\n releaseStale(): void;\n /** Subscribes to changes of the holder (for `useSyncExternalStore`); returns the unsubscribe. */\n subscribe(onChange: () => void): () => void;\n /** A number that changes whenever the subscribers are notified (the store snapshot). */\n getVersion(): number;\n}\n\nclass FieldControlIdClaimStore implements FieldControlIdClaim {\n private holder: string | undefined = undefined;\n private readonly mounted = new Set<string>();\n private readonly listeners = new Set<() => void>();\n private version = 0;\n\n claim = (token: string): boolean => {\n if (this.holder === undefined) this.holder = token;\n return this.holder === token;\n };\n\n mount = (token: string): (() => void) => {\n this.mounted.add(token);\n return () => {\n this.mounted.delete(token);\n if (this.holder === token) this.free();\n };\n };\n\n hold = (token: string): void => {\n if (this.holder === undefined) this.holder = token;\n else if (this.holder !== token) this.notify();\n };\n\n releaseStale = (): void => {\n if (this.holder !== undefined && !this.mounted.has(this.holder)) this.free();\n };\n\n subscribe = (onChange: () => void): (() => void) => {\n this.listeners.add(onChange);\n return () => {\n this.listeners.delete(onChange);\n };\n };\n\n getVersion = (): number => this.version;\n\n private free(): void {\n this.holder = undefined;\n this.notify();\n }\n\n private notify(): void {\n this.version += 1;\n for (const listener of Array.from(this.listeners)) listener();\n }\n}\n\n/**\n * Creates the {@link FieldControlIdClaim} a Field puts into its context (one per Field instance,\n * e.g. `const [claim] = useState(createFieldControlIdClaim)`).\n *\n * @internal Not exported from the package.\n */\nexport function createFieldControlIdClaim(): FieldControlIdClaim {\n return new FieldControlIdClaimStore();\n}\n\nconst subscribeNothing = (): (() => void) => () => {};\nconst noVersion = (): number => 0;\n\n/**\n * Context provided by `Field`. `null` outside a Field (controls then use only their own props).\n */\nexport const FieldContext: React.Context<FieldContextValue | null> =\n React.createContext<FieldContextValue | null>(null);\nFieldContext.displayName = 'FieldContext';\n\n/** The surrounding Field's context value, or `null` when the control is not inside a Field. */\nexport function useFieldContext(): FieldContextValue | null {\n return React.useContext(FieldContext);\n}\n\n/** The labelling/validation props a control receives from its consumer and from its Field. */\nexport interface FieldControlProps {\n id?: string;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n 'aria-describedby'?: string;\n 'aria-invalid'?: React.AriaAttributes['aria-invalid'];\n 'aria-required'?: React.AriaAttributes['aria-required'];\n required?: boolean;\n}\n\n/** Options of {@link useFieldControl}. */\nexport interface UseFieldControlOptions {\n /**\n * Whether the focusable element can be the target of `<label htmlFor>` (`<input>`, `<button>`,\n * `<select>`, `<textarea>`). Non-labelable controls (`role=\"radiogroup\"`/`role=\"group\"` divs)\n * are named with `aria-labelledby` instead.\n * @default true\n */\n labelable?: boolean;\n /**\n * Whether the focusable element is a native form control (Input, Select, Textarea, Slider,\n * SpinButton's input), so the Field's `required` becomes the native `required` attribute and\n * constraint validation runs. Controls built on buttons or divs validate through `HiddenInput`.\n * @default false\n */\n nativeRequired?: boolean;\n}\n\n/**\n * Merges a control's own labelling props with its surrounding `Field`. Spread the result onto the\n * **focusable element** (for a control whose root wraps it, not onto the root). Consumer values\n * are merged, never overwritten, and only defined keys are returned, so spreading never clears an\n * attribute.\n *\n * - `id`: the consumer's id; else, when the Field already gave `controlId` to its first child\n * (`controlIdAssigned`) — so this control is nested inside that child or a later sibling — a\n * generated id of its own; else the Field's `controlId`, which only one control takes when the\n * Field provides a `controlIdClaim` (several controls inside a plain `<div>`: the first one\n * takes it, the others get a generated id of their own). The first child itself receives\n * `controlId` as its `id` prop from Field, so it keeps it.\n * - `aria-labelledby`: when the consumer set an `aria-label`, only the consumer's\n * `aria-labelledby`; otherwise, when the control is not labelable **or** its id is not the\n * Field's `controlId` (it carries its own id, or is not the Field's first child, so\n * `<label htmlFor>` does not reach it), the consumer's ids plus the Field's `labelId`.\n * - `aria-describedby`: the consumer's ids, then the Field's validation message\n * (`validationMessageId`, else `errorId`), then its hint (deduplicated). A warning, success or\n * neutral message describes the control too.\n * - `aria-invalid` / `aria-required`: the consumer's value, else `true` when the Field is\n * invalid/required (`aria-required`: not when the consumer passes `required={false}`). Only the\n * `error` validation state makes the Field invalid.\n * - `required`: only with `nativeRequired` — the consumer's value, else the Field's `required`.\n * - `aria-label`: passed through.\n *\n * @example\n * const fieldProps = useFieldControl({ id, 'aria-label': ariaLabel, 'aria-labelledby': labelledBy,\n * 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired });\n * return <button role=\"switch\" {...fieldProps} />;\n */\nexport function useFieldControl(\n props: FieldControlProps,\n options: UseFieldControlOptions = {},\n): FieldControlProps {\n const field = useFieldContext();\n // Always called (stable hook order); used only when the Field's controlId is taken. It is also\n // the control's token in the Field's controlIdClaim.\n const fallbackId = useId('field-control');\n const { labelable = true, nativeRequired = false } = options;\n\n // A control without an id of its own wants the Field's controlId unless Field gave it to its\n // first child. With a claim, only the first such control takes it.\n const wantsControlId = field !== null && props.id === undefined && !field.controlIdAssigned;\n const claim = wantsControlId ? field.controlIdClaim : undefined;\n // Re-render when the controlId is freed, so the next control takes it over.\n React.useSyncExternalStore(\n claim ? claim.subscribe : subscribeNothing,\n claim ? claim.getVersion : noVersion,\n claim ? claim.getVersion : noVersion,\n );\n const holdsControlId = wantsControlId && (claim ? claim.claim(fallbackId) : true);\n // Layout effects do nothing on the server, and React 19 no longer warns about them there.\n React.useLayoutEffect(() => claim?.mount(fallbackId), [claim, fallbackId]);\n React.useLayoutEffect(() => {\n if (claim && holdsControlId) claim.hold(fallbackId);\n }, [claim, holdsControlId, fallbackId]);\n // After every commit (all layout effects of the commit, and so every mount, have run): a\n // control rendered without controlId frees one held by a render that never committed.\n React.useEffect(() => {\n if (claim && !holdsControlId) claim.releaseStale();\n });\n\n const id =\n props.id ?? (field === null ? undefined : holdsControlId ? field.controlId : fallbackId);\n\n let labelledBy = props['aria-labelledby'];\n if (field && props['aria-label'] === undefined && (!labelable || id !== field.controlId)) {\n labelledBy = joinIds(labelledBy, field.labelId);\n }\n\n // The message in any validation state (a context built before 0.6 has only `errorId`).\n const describedBy = field\n ? joinIds(props['aria-describedby'], field.validationMessageId ?? field.errorId, field.hintId)\n : props['aria-describedby'];\n const invalid = props['aria-invalid'] ?? (field?.invalid || undefined);\n // A consumer `required={false}` (native controls pass `required`) also keeps the Field's\n // required state out of aria-required, so the announced state matches native validation.\n const ariaRequired =\n props['aria-required'] ?? (props.required === false ? undefined : field?.required || undefined);\n const required = nativeRequired ? (props.required ?? (field?.required || undefined)) : undefined;\n\n const result: FieldControlProps = {};\n if (id !== undefined) result.id = id;\n if (props['aria-label'] !== undefined) result['aria-label'] = props['aria-label'];\n if (labelledBy !== undefined) result['aria-labelledby'] = labelledBy;\n if (describedBy !== undefined) result['aria-describedby'] = describedBy;\n if (invalid !== undefined) result['aria-invalid'] = invalid;\n if (ariaRequired !== undefined) result['aria-required'] = ariaRequired;\n if (required !== undefined) result.required = required;\n return result;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use client";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../lib/children.cjs"),n=require("../lib/dev.cjs"),r=require("./useEventCallback.cjs"),i=require("./useId.cjs"),a=require("./useTypeahead.cjs"),o=require("./useMergedRefs.cjs");let s=require("react");s=e.__toESM(s,1);var c=[],l=new Set;function u(){return c}function d(e,t){if(e===t)return!0;if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}function f(e,t){return e.value===t.value&&e.label===t.label&&e.textValue===t.textValue&&!!e.disabled==!!t.disabled&&!!e.hidden==!!t.hidden}function p(e,t){return e===t||e.length===t.length&&e.every((e,n)=>f(e,t[n]))}function m(e,t){return e===t||e.length===t.length&&e.every((e,n)=>e.value===t[n].value)}function h(e,t){let n=e.element.current,r=t.element.current;if(n&&r&&n!==r&&n.isConnected&&r.isConnected){let e=n.compareDocumentPosition(r);if(e&Node.DOCUMENT_POSITION_FOLLOWING)return-1;if(e&Node.DOCUMENT_POSITION_PRECEDING)return 1}return e.seq-t.seq}var g=class{indexByValue=new Map;registrations=new Set;stateListeners=new Set;itemListeners=new Set;nextIndex=0;nextSeq=0;dirty=!1;flushScheduled=!1;itemsStale=!1;itemsSnapshot=c;sorted=[];observer=null;observed=null;watching=!1;connected=!1;registrationMode=!0;active=null;selected;hidden=l;constructor(e){this.selected=new Set(e)}subscribe=e=>(this.stateListeners.add(e),()=>{this.stateListeners.delete(e)});subscribeItems=e=>(this.itemListeners.add(e),()=>{this.itemListeners.delete(e)});isActive=e=>this.active===e;isSelected=e=>this.selected.has(e);isHidden=e=>this.hidden.has(e);getIndex=e=>{let t=this.indexByValue.get(e);return t===void 0&&(t=this.nextIndex++,this.indexByValue.set(e,t)),t};register=(e,t)=>{let n={item:e,element:t,seq:this.nextSeq++};return this.registrations.add(n),this.markDirty(),()=>{this.registrations.delete(n),this.markDirty()}};getItems=()=>{if(this.itemsStale){this.itemsStale=!1;let e=Array.from(this.registrations).sort(h);this.sorted=e;let t=new Set,n=[];for(let{item:r}of e)t.has(r.value)||(t.add(r.value),n.push(r));p(n,this.itemsSnapshot)||(this.itemsSnapshot=n)}return this.itemsSnapshot};getElement(e){for(let{item:t,element:n}of this.registrations){let r=n.current;if(t.value===e&&r&&r.isConnected)return r}return null}setRegistrationMode(e){e!==this.registrationMode&&(this.registrationMode=e,this.syncObserver())}connect(){this.connected=!0,this.syncObserver()}disconnect(){this.connected=!1,this.syncObserver()}flush(){this.dirty&&(this.dirty=!1,this.itemsStale=!0,this.registrationMode&&this.warnDuplicates(),this.syncObserver(),this.notifyItems())}checkOrder(){if(!this.registrationMode||this.dirty||this.itemsStale)return;let e=this.sorted;for(let t=1;t<e.length;t++){let n=e[t-1].element.current,r=e[t].element.current;if(n&&r&&n!==r&&n.isConnected&&r.isConnected&&n.compareDocumentPosition(r)&Node.DOCUMENT_POSITION_PRECEDING){this.itemsStale=!0,this.notifyItems();return}}}checkOrderAfterCommit(){let e=this.observer!==null&&this.observer.takeRecords().length>0,t=this.watching;this.watching=this.observed!==null,(!t||e)&&this.checkOrder()}setRenderState(e,t,n){let r=!1;if(e!==this.active&&(this.active=e,r=!0),d(t,this.selected)||(this.selected=t,r=!0),d(n,this.hidden)||(this.hidden=n,r=!0),r)for(let e of Array.from(this.stateListeners))e()}notifyItems(){for(let e of Array.from(this.itemListeners))e()}onMutation=()=>{this.checkOrder()};syncObserver(){let e=this.connected&&this.registrationMode&&typeof MutationObserver<`u`?this.commonAncestor():null;e!==this.observed&&(this.observer?.disconnect(),this.observed=e,this.watching=!1,e&&(n.isDev&&v(e),this.observer??=new MutationObserver(this.onMutation),this.observer.observe(e,{childList:!0,subtree:!0})))}commonAncestor(){let e=null,t=0;for(let{element:n}of this.registrations){let r=n.current;if(r&&r.isConnected){if(t++,e===null){e=r.parentNode;continue}for(;e&&!e.contains(r);)e=e.parentNode;if(!e)return null}}return t>1?e:null}markDirty(){this.dirty=!0,!this.flushScheduled&&(this.flushScheduled=!0,queueMicrotask(()=>{this.flushScheduled=!1,this.flush()}))}warnDuplicates(){let e=new Set;for(let{item:t}of this.registrations)e.has(t.value)&&_(t.value),e.add(t.value)}};function _(e){n.warnOnce(`useListbox:duplicate:${e}`,`Listbox: several options share the value "${e}". Option values must be unique within a listbox; only the first one can be highlighted and selected.`)}function v(e){let t=e.ownerDocument;!t||e!==t.body&&e!==t.documentElement||n.warnOnce(`useListbox:single-container`,`Listbox: the options of one listbox are rendered in more than one container (for example an inline list kept mounted next to a portaled one). All options must live in a single container at a time: render the list inline only while closed and in the portal only while open.`)}var y=s.createContext(null);y.displayName=`ListboxContext`;var b=null;function x(){return b??={listboxId:`wave-listbox-inert`,store:new g([]),select:()=>{},highlight:()=>{}},b}function S(e){return(0,s.useContext)(y)||(n.reportMissingContext(e,`a listbox (Combobox or Dropdown)`),x())}var C=Symbol.for(`@mortenbrudvik/waveui/listbox-element-kind`);function w(e,t){return Object.defineProperty(e,C,{value:t,configurable:!0}),e}function T(e){if(typeof e!=`function`&&typeof e!=`object`||e===null)return;let t=e[C];return t===`option`||t===`group`?t:void 0}function E(e){if(typeof e==`string`)return e;if(typeof e==`number`||typeof e==`bigint`)return String(e);if(e==null||typeof e==`boolean`)return``;if(s.isValidElement(e))return typeof e.type==`string`||e.type===s.Fragment?E(e.props.children):``;if(typeof e==`object`&&Symbol.iterator in e){let t=``;for(let n of e)t+=E(n);return t}return``}function D(e,t){return typeof e.label==`string`?e.label:typeof e.textValue==`string`?e.textValue:e.children===null||e.children===void 0?t:E(e.children).trim()||void 0}function O(e,n){s.Children.forEach(e,e=>{if(!s.isValidElement(e))return;let r=t.getElementType(e);if(r===s.Fragment){O(e.props.children,n);return}let i=T(r);if(i===`group`){O(e.props.children,n);return}if(i!==`option`)return;let{value:a}=e.props;if(typeof a!=`string`||n.has(a))return;let o=D(e.props,a);o!==void 0&&n.set(a,o)})}function k(e){let t=new Map;return O(e,t),t}function A(e,t,n,r){let i=e.length;if(i===0)return null;let a=t===null?-1:e.indexOf(t);if(a===-1)return n>0?e[0]:e[i-1];let o=a+n;return(o<0||o>=i)&&(o=r&&Math.abs(n)===1?(o+i)%i:Math.max(0,Math.min(i-1,o))),e[o]}function ee(e){return(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement)&&e.value!==``}var j=new Set([`x`,`v`,`z`,`y`]);function M(e){let t=e.currentTarget;if((t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&(t.readOnly||t.disabled))return!1;let{key:n}=e;return n===`Backspace`||n===`Delete`||n===`Process`||n===`Unidentified`?!0:n.length===1?!(e.metaKey||e.ctrlKey&&!a.isAltGraphCharacter(e))||j.has(n.toLowerCase()):!1}function N(e){e.preventDefault()}function P(e){let{open:t,onOpenChange:n,mode:o,multiple:c=!1,selectedValues:d,onSelect:f,items:p,filter:h,loop:v=!1,typeahead:y=o===`select-only`,autoHighlight:b=`selected`,highlightOnFilter:x=!1,idPrefix:S,onClearDraft:C}=e,w=i.useId(S??`listbox`),[T]=(0,s.useState)(()=>new g(d)),E=p!==void 0,D=(0,s.useSyncExternalStore)(T.subscribeItems,E?u:T.getItems,u),O=p??D,k=(0,s.useMemo)(()=>O.filter(e=>!e.hidden&&(!h||h(e))),[O,h]),j=(0,s.useMemo)(()=>k.filter(e=>!e.disabled).map(e=>e.value),[k]),P=(0,s.useMemo)(()=>new Set(j),[j]),F=(0,s.useMemo)(()=>{let e=new Map;for(let t of O)e.has(t.value)||e.set(t.value,t);return e},[O]),I=(0,s.useMemo)(()=>{if(k.length===O.length)return l;let e=new Set(k.map(e=>e.value)),t=new Set;for(let n of O)e.has(n.value)||t.add(n.value);return t},[O,k]),L=new Set(d),[R,z]=(0,s.useState)(null),B=t?R:null,[V,H]=(0,s.useState)(()=>({open:t,items:k}));if(x){let e=!m(V.items,k);(e||V.open!==t)&&(H({open:t,items:k}),t&&e&&(B=j[0]??null))}B!==null&&!P.has(B)&&(B=null),B!==R&&z(B);let U=j.find(e=>L.has(e))??null,W=null;b===`selected`?W=U??j[0]??null:b===`first`&&(W=j[0]??null);let G=t?B??W:null,K=(0,s.useCallback)(e=>`${w}-opt-${T.getIndex(e)}`,[w,T]),q=G===null?void 0:K(G),J=(0,s.useCallback)(e=>F.get(e),[F]),Y=(0,s.useRef)(null),X=(0,s.useCallback)(e=>{Y.current=null,z(e)},[]),te=X,Z=r.useEventCallback((e,t,r)=>{let i=F.get(e)??r;i&&!i.disabled&&(f(e,i),c&&t===`select`?X(e):(X(null),n(!1,t)))}),Q=r.useEventCallback((e,t)=>{Z(e,`select`,t)}),$=r.useEventCallback(e=>{t&&P.has(e)&&e!==G&&(Y.current=e,z(e))}),{onTypeahead:ne}=a.useTypeahead({getItems:()=>k.map(e=>({value:e.value,text:e.textValue??e.label,disabled:e.disabled})),onMatch:e=>{X(e),t||n(!0,`keyboard`)}}),re=r.useEventCallback(e=>{if(e.defaultPrevented||e.nativeEvent.isComposing)return;o===`editable`&&t&&M(e)&&X(x?j[0]??null:null);let r=a.isAltGraphCharacter(e);if(e.ctrlKey&&!r||e.metaKey)return;let{key:i,altKey:s}=e,l=o===`select-only`,u=j[0]??null,d=j[j.length-1]??null,f=e=>{X(e),t||n(!0,`keyboard`)},p=e=>X(A(j,G,e,v)),m=()=>{G===null?c||n(!1,`keyboard`):Z(G,`select`)};switch(i){case`ArrowDown`:e.preventDefault(),s?t||n(!0,`keyboard`):t?p(1):f(U??u);return;case`ArrowUp`:if(e.preventDefault(),s){if(!t)return;l&&!c&&G!==null?Z(G,`select`):n(!1,`keyboard`)}else t?p(-1):f(U??d);return;case`Home`:case`End`:if(!l)return;e.preventDefault(),f(i===`Home`?u:d);return;case`PageUp`:case`PageDown`:if(!l||!t)return;e.preventDefault(),p(i===`PageDown`?10:-10);return;case`Enter`:if(!l){t&&G!==null&&(e.preventDefault(),Z(G,`select`));return}e.preventDefault(),t?m():n(!0,`keyboard`);return;case`Escape`:t?(e.preventDefault(),n(!1,`escape`)):!l&&C&&ee(e.currentTarget)&&(e.preventDefault(),C());return;case`Tab`:if(!t)return;l&&!c&&G!==null?Z(G,`tab`):n(!1,`tab`);return}if(i!==` `||l){if(!y||s&&!r||i.length!==1){i===` `&&l&&(e.preventDefault(),t?m():n(!0,`keyboard`));return}if(ne(e,t?G:U)){e.preventDefault();return}if(i===` `){e.preventDefault(),t?m():n(!0,`keyboard`);return}l&&!t&&(e.preventDefault(),n(!0,`keyboard`))}}),ie=r.useEventCallback(e=>{o===`select-only`&&e.key===` `&&e.preventDefault()});return(0,s.useLayoutEffect)(()=>{T.setRegistrationMode(!E),T.flush(),T.checkOrderAfterCommit(),T.setRenderState(G,L,I)}),(0,s.useLayoutEffect)(()=>(T.connect(),()=>T.disconnect()),[T]),(0,s.useLayoutEffect)(()=>{let e=G!==null&&G===Y.current;Y.current=null,!(G===null||e)&&(T.getElement(G)??(typeof document<`u`?document.getElementById(K(G)):null))?.scrollIntoView?.({block:`nearest`})},[G,K,T]),(0,s.useEffect)(()=>{if(!p)return;let e=new Set;for(let t of p)e.has(t.value)&&_(t.value),e.add(t.value)},[p]),{listboxId:w,activeValue:G,activeDescendantId:q,items:k,getItem:J,getOptionId:K,setActiveValue:te,onKeyDown:re,onKeyUp:ie,getComboboxProps:()=>{let e={role:`combobox`,"aria-expanded":t,"aria-controls":w,"aria-haspopup":`listbox`};return q!==void 0&&(e[`aria-activedescendant`]=q),o===`editable`&&(e[`aria-autocomplete`]=`list`),e},getListboxProps:()=>{let e={id:w,role:`listbox`,tabIndex:-1,onMouseDown:N};return c&&(e[`aria-multiselectable`]=!0),e},context:(0,s.useMemo)(()=>({listboxId:w,store:T,select:Q,highlight:$}),[w,T,Q,$])}}var F=1,I=2,L=4;function R(e,t){return(e.isActive(t)?F:0)|(e.isSelected(t)?I:0)|(e.isHidden(t)?L:0)}function z(e,t){let{store:n,listboxId:r,select:i,highlight:a}=S(`Option`),{value:c,label:l,textValue:u,disabled:d=!1,hidden:p=!1}=e,m=`${r}-opt-${n.getIndex(c)}`,h=(0,s.useSyncExternalStore)(n.subscribe,()=>R(n,c),()=>R(n,c)),g=(h&F)!==0,_=(h&I)!==0,v=p||(h&L)!==0,y=(0,s.useRef)(null),b=(0,s.useCallback)(e=>{y.current=e},[]),x=o.useMergedRefs(t,b),C=(0,s.useRef)(null);(0,s.useLayoutEffect)(()=>{let e=l===void 0&&u===void 0?y.current?.textContent?.trim():void 0,t={value:c,label:l??u??(e||c)};u!==void 0&&(t.textValue=u),d&&(t.disabled=!0),p&&(t.hidden=!0);let r=C.current;r&&r.store===n&&f(r.item,t)||(r?.unregister(),C.current={store:n,item:t,unregister:n.register(t,y)})}),(0,s.useLayoutEffect)(()=>()=>{C.current?.unregister(),C.current=null},[]);let w={id:m,role:`option`,"aria-selected":_,onClick:(0,s.useCallback)(()=>{if(d)return;let e={value:c,label:l??u??c};u!==void 0&&(e.textValue=u),i(c,e)},[i,c,l,u,d]),onPointerMove:(0,s.useCallback)(()=>{!d&&!n.isActive(c)&&a(c)},[a,n,c,d]),ref:x};return d&&(w[`aria-disabled`]=!0,w[`data-disabled`]=``),v&&(w.hidden=!0),g&&(w[`data-active`]=``),_&&(w[`data-selected`]=``),{id:m,selected:_,active:g,disabled:d,hidden:v,optionProps:w}}exports.ListboxContext=y,exports.collectOptionLabels=k,exports.markListboxElement=w,exports.useListbox=P,exports.useListboxOption=z;
1
+ "use client";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../lib/children.cjs"),n=require("../lib/dev.cjs"),r=require("./useEventCallback.cjs"),i=require("./useId.cjs"),a=require("./useTypeahead.cjs"),o=require("./useMergedRefs.cjs"),s=require("./useActiveDescendant.cjs");let c=require("react");c=e.__toESM(c,1);var l=[],u=new Set;function d(){return l}function f(e,t){if(e===t)return!0;if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}function p(e,t){return e.value===t.value&&e.label===t.label&&e.textValue===t.textValue&&!!e.disabled==!!t.disabled&&!!e.hidden==!!t.hidden}function m(e,t){return e===t||e.length===t.length&&e.every((e,n)=>p(e,t[n]))}function h(e,t){let n=e.element.current,r=t.element.current;if(n&&r&&n!==r&&n.isConnected&&r.isConnected){let e=n.compareDocumentPosition(r);if(e&Node.DOCUMENT_POSITION_FOLLOWING)return-1;if(e&Node.DOCUMENT_POSITION_PRECEDING)return 1}return e.seq-t.seq}var g=class{indexByValue=new Map;registrations=new Set;stateListeners=new Set;itemListeners=new Set;nextIndex=0;nextSeq=0;dirty=!1;flushScheduled=!1;itemsStale=!1;itemsSnapshot=l;sorted=[];observer=null;observed=null;watching=!1;connected=!1;registrationMode=!0;active=null;selected;hidden=u;constructor(e){this.selected=new Set(e)}subscribe=e=>(this.stateListeners.add(e),()=>{this.stateListeners.delete(e)});subscribeItems=e=>(this.itemListeners.add(e),()=>{this.itemListeners.delete(e)});isActive=e=>this.active===e;isSelected=e=>this.selected.has(e);isHidden=e=>this.hidden.has(e);getIndex=e=>{let t=this.indexByValue.get(e);return t===void 0&&(t=this.nextIndex++,this.indexByValue.set(e,t)),t};register=(e,t)=>{let n={item:e,element:t,seq:this.nextSeq++};return this.registrations.add(n),this.markDirty(),()=>{this.registrations.delete(n),this.markDirty()}};getItems=()=>{if(this.itemsStale){this.itemsStale=!1;let e=Array.from(this.registrations).sort(h);this.sorted=e;let t=new Set,n=[];for(let{item:r}of e)t.has(r.value)||(t.add(r.value),n.push(r));m(n,this.itemsSnapshot)||(this.itemsSnapshot=n)}return this.itemsSnapshot};getElement=e=>{for(let{item:t,element:n}of this.registrations){let r=n.current;if(t.value===e&&r&&r.isConnected)return r}return null};setRegistrationMode(e){e!==this.registrationMode&&(this.registrationMode=e,this.syncObserver())}connect(){this.connected=!0,this.syncObserver()}disconnect(){this.connected=!1,this.syncObserver()}flush(){this.dirty&&(this.dirty=!1,this.itemsStale=!0,this.registrationMode&&this.warnDuplicates(),this.syncObserver(),this.notifyItems())}checkOrder(){if(!this.registrationMode||this.dirty||this.itemsStale)return;let e=this.sorted;for(let t=1;t<e.length;t++){let n=e[t-1].element.current,r=e[t].element.current;if(n&&r&&n!==r&&n.isConnected&&r.isConnected&&n.compareDocumentPosition(r)&Node.DOCUMENT_POSITION_PRECEDING){this.itemsStale=!0,this.notifyItems();return}}}checkOrderAfterCommit(){let e=this.observer!==null&&this.observer.takeRecords().length>0,t=this.watching;this.watching=this.observed!==null,(!t||e)&&this.checkOrder()}setRenderState(e,t,n){let r=!1;if(e!==this.active&&(this.active=e,r=!0),f(t,this.selected)||(this.selected=t,r=!0),f(n,this.hidden)||(this.hidden=n,r=!0),r)for(let e of Array.from(this.stateListeners))e()}notifyItems(){for(let e of Array.from(this.itemListeners))e()}onMutation=()=>{this.checkOrder()};syncObserver(){let e=this.connected&&this.registrationMode&&typeof MutationObserver<`u`?this.commonAncestor():null;e!==this.observed&&(this.observer?.disconnect(),this.observed=e,this.watching=!1,e&&(n.isDev&&v(e),this.observer??=new MutationObserver(this.onMutation),this.observer.observe(e,{childList:!0,subtree:!0})))}commonAncestor(){let e=null,t=0;for(let{element:n}of this.registrations){let r=n.current;if(r&&r.isConnected){if(t++,e===null){e=r.parentNode;continue}for(;e&&!e.contains(r);)e=e.parentNode;if(!e)return null}}return t>1?e:null}markDirty(){this.dirty=!0,!this.flushScheduled&&(this.flushScheduled=!0,queueMicrotask(()=>{this.flushScheduled=!1,this.flush()}))}warnDuplicates(){let e=new Set;for(let{item:t}of this.registrations)e.has(t.value)&&_(t.value),e.add(t.value)}};function _(e){n.warnOnce(`useListbox:duplicate:${e}`,`Listbox: several options share the value "${e}". Option values must be unique within a listbox; only the first one can be highlighted and selected.`)}function v(e){let t=e.ownerDocument;!t||e!==t.body&&e!==t.documentElement||n.warnOnce(`useListbox:single-container`,`Listbox: the options of one listbox are rendered in more than one container (for example an inline list kept mounted next to a portaled one). All options must live in a single container at a time: render the list inline only while closed and in the portal only while open.`)}var y=c.createContext(null);y.displayName=`ListboxContext`;var b=null;function x(){return b??={listboxId:`wave-listbox-inert`,store:new g([]),multiselect:!1,mode:`select-only`,select:()=>{},highlight:()=>{},press:()=>{}},b}function S(e){return(0,c.useContext)(y)||(n.reportMissingContext(e,`a listbox (Listbox, Combobox, Dropdown or a ListboxProvider)`),x())}var C=Symbol.for(`@mortenbrudvik/waveui/listbox-element-kind`);function w(e,t){return Object.defineProperty(e,C,{value:t,configurable:!0}),e}function T(e){if(typeof e!=`function`&&typeof e!=`object`||e===null)return;let t=e[C];return t===`option`||t===`group`?t:void 0}function E(e){if(typeof e==`string`)return e;if(typeof e==`number`||typeof e==`bigint`)return String(e);if(e==null||typeof e==`boolean`)return``;if(c.isValidElement(e))return typeof e.type==`string`||e.type===c.Fragment?E(e.props.children):``;if(typeof e==`object`&&Symbol.iterator in e){let t=``;for(let n of e)t+=E(n);return t}return``}function D(e,t){return typeof e.label==`string`?e.label:typeof e.textValue==`string`?e.textValue:e.children===null||e.children===void 0?t:E(e.children).trim()||void 0}function O(e,n){c.Children.forEach(e,e=>{if(!c.isValidElement(e))return;let r=t.getElementType(e);if(r===c.Fragment){O(e.props.children,n);return}let i=T(r);if(i===`group`){O(e.props.children,n);return}if(i!==`option`)return;let{value:a}=e.props;if(typeof a!=`string`||n.has(a))return;let o=D(e.props,a);o!==void 0&&n.set(a,o)})}function k(e){let t=new Map;return O(e,t),t}function A(e){return(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement)&&e.value!==``}var j=new Set([`x`,`v`,`z`,`y`]);function M(e){let t=e.currentTarget;if((t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&(t.readOnly||t.disabled))return!1;let{key:n}=e;return n===`Backspace`||n===`Delete`||n===`Process`||n===`Unidentified`?!0:n.length===1?!(e.metaKey||e.ctrlKey&&!a.isAltGraphCharacter(e))||j.has(n.toLowerCase()):!1}function N(e){e.preventDefault()}function P(e){let{open:t,onOpenChange:o,mode:l,multiselect:f=!1,selectedValues:p,onSelect:m,items:h,filter:v,loop:y=!1,typeahead:b=l!==`editable`,disabledOptionsFocusable:x=!1,autoHighlight:S=`selected`,highlightOnFilter:C=!1,onActiveValueChange:w,idPrefix:T,onClearDraft:E}=e,D=l===`standalone`,O=D?void 0:o,[k,j]=(0,c.useState)(null),P=i.useId(T??`listbox`),[F]=(0,c.useState)(()=>new g(p)),I=h!==void 0,L=(0,c.useSyncExternalStore)(F.subscribeItems,I?d:F.getItems,d),R=h??L,z=(0,c.useMemo)(()=>R.filter(e=>!e.hidden&&(!v||v(e))),[R,v]),B=(0,c.useMemo)(()=>x?z.map(e=>e.value):z.filter(e=>!e.disabled).map(e=>e.value),[z,x]),V=(0,c.useMemo)(()=>{let e=new Map;for(let t of R)e.has(t.value)||e.set(t.value,t);return e},[R]),H=(0,c.useMemo)(()=>{if(z.length===R.length)return u;let e=new Set(z.map(e=>e.value)),t=new Set;for(let n of R)e.has(n.value)||t.add(n.value);return t},[R,z]),U=new Set(p),W=B.find(e=>U.has(e))??null,G=null;S===`selected`?G=W??B[0]??null:S===`first`&&(G=B[0]??null);let K=(0,c.useCallback)(e=>`${P}-opt-${F.getIndex(e)}`,[P,F]),ee=(0,c.useCallback)(e=>V.get(e),[V]),q=s.useActiveDescendant({items:B,getId:K,enabled:t,fallback:G,loop:y,activateFirstOnChange:C,getElement:F.getElement,onActiveValueChange:w}),{activeValue:J,activeDescendantId:Y}=q,X=r.useEventCallback((e,t,n,r)=>{let i=V.get(e)??r;i&&!i.disabled&&(m(e,{item:i,event:n}),D||f&&t===`select`?q.setActiveValue(e):(q.setActiveValue(null),O?.(!1,{reason:t,event:n})))}),Z=r.useEventCallback((e,t,n)=>{X(e,`select`,n,t)}),Q=r.useEventCallback(e=>{k?k.focus({preventScroll:!0}):D&&n.warnOnce(`useListbox:list-ref`,"useListbox: the list element is unknown, so a pointer press cannot focus the list. Pass the `ref` of `getListboxProps()` to the list element, merged with your own ref."),B.includes(e)?q.setActiveValue(e,{scroll:!1}):!t&&G!==null&&q.setActiveValue(G,{scroll:!1})}),$=(0,c.useRef)(null),{onTypeahead:te}=a.useTypeahead({getItems:()=>z.map(e=>({value:e.value,text:e.textValue??e.label,disabled:!x&&e.disabled})),onMatch:e=>{q.setActiveValue(e);let n=$.current;!t&&n&&O?.(!0,{reason:`keyboard`,event:n})}}),ne=r.useEventCallback(e=>{if(e.defaultPrevented||e.nativeEvent.isComposing)return;$.current=e.nativeEvent,l===`editable`&&t&&M(e)&&q.setActiveValue(C?B[0]??null:null);let n=a.isAltGraphCharacter(e);if(e.ctrlKey&&!n||e.metaKey)return;let{key:r,altKey:i}=e;if(D&&(r===`Tab`||r===`Escape`||i&&r.startsWith(`Arrow`)))return;let o=l===`select-only`,s=l===`editable`,c=B[0]??null,u=B[B.length-1]??null,d=n=>{q.setActiveValue(n),t||O?.(!0,{reason:`keyboard`,event:e.nativeEvent})},p=()=>{J===null?f||O?.(!1,{reason:`keyboard`,event:e.nativeEvent}):X(J,`select`,e.nativeEvent)};switch(r){case`ArrowDown`:e.preventDefault(),i?t||O?.(!0,{reason:`keyboard`,event:e.nativeEvent}):t?q.next():d(W??c);return;case`ArrowUp`:if(e.preventDefault(),i){if(!t)return;o&&!f&&J!==null&&!V.get(J)?.disabled?X(J,`select`,e.nativeEvent):O?.(!1,{reason:`keyboard`,event:e.nativeEvent})}else t?q.prev():d(W??u);return;case`Home`:case`End`:if(s)return;e.preventDefault(),d(r===`Home`?c:u);return;case`PageUp`:case`PageDown`:if(s||!t)return;e.preventDefault(),q.move(r===`PageDown`?10:-10);return;case`Enter`:if(s){t&&J!==null&&(e.preventDefault(),X(J,`select`,e.nativeEvent));return}e.preventDefault(),t?p():O?.(!0,{reason:`keyboard`,event:e.nativeEvent});return;case`Escape`:t?(e.preventDefault(),O?.(!1,{reason:`escape`,event:e.nativeEvent})):s&&E&&A(e.currentTarget)&&(e.preventDefault(),E());return;case`Tab`:if(!t)return;o&&!f&&J!==null&&!V.get(J)?.disabled?X(J,`tab`,e.nativeEvent):O?.(!1,{reason:`tab`,event:e.nativeEvent});return}if(!(r===` `&&s)){if(!b||i&&!n||r.length!==1){r===` `&&!s&&(e.preventDefault(),t?p():O?.(!0,{reason:`keyboard`,event:e.nativeEvent}));return}if(te(e,t?J:W)){e.preventDefault();return}if(r===` `){e.preventDefault(),t?p():O?.(!0,{reason:`keyboard`,event:e.nativeEvent});return}o&&!t&&(e.preventDefault(),O?.(!0,{reason:`keyboard`,event:e.nativeEvent}))}}),re=r.useEventCallback(e=>{l===`select-only`&&e.key===` `&&e.preventDefault()});(0,c.useLayoutEffect)(()=>{F.setRegistrationMode(!I),F.flush(),F.checkOrderAfterCommit(),F.setRenderState(J,U,H)}),(0,c.useLayoutEffect)(()=>(F.connect(),()=>F.disconnect()),[F]),(0,c.useEffect)(()=>{if(!h)return;let e=new Set;for(let t of h)e.has(t.value)&&_(t.value),e.add(t.value)},[h]);let ie=(0,c.useMemo)(()=>({listboxId:P,store:F,multiselect:f,mode:l,select:Z,highlight:q.highlight,press:Q}),[P,F,f,l,Z,q.highlight,Q]);return{listboxId:P,activeValue:J,activeDescendantId:Y,items:z,getItem:ee,getOptionId:K,setActiveValue:q.setActiveValue,onKeyDown:ne,onKeyUp:re,getComboboxProps:()=>{let e={role:`combobox`,"aria-expanded":t,"aria-controls":P,"aria-haspopup":`listbox`};return Y!==void 0&&(e[`aria-activedescendant`]=Y),l===`editable`&&(e[`aria-autocomplete`]=`list`),e},getListboxProps:()=>{let e=D?{id:P,role:`listbox`,tabIndex:0,ref:j}:{id:P,role:`listbox`,tabIndex:-1,onMouseDown:N};return D&&Y!==void 0&&(e[`aria-activedescendant`]=Y),f&&(e[`aria-multiselectable`]=!0),e},context:ie}}var F=1,I=2,L=4;function R(e,t){return(e.isActive(t)?F:0)|(e.isSelected(t)?I:0)|(e.isHidden(t)?L:0)}function z(e,t){let{store:n,listboxId:r,multiselect:i,mode:a,select:s,highlight:l,press:u}=S(`Option`),{value:d,label:f,textValue:m,disabled:h=!1,hidden:g=!1}=e,_=`${r}-opt-${n.getIndex(d)}`,v=(0,c.useSyncExternalStore)(n.subscribe,()=>R(n,d),()=>R(n,d)),y=(v&F)!==0,b=(v&I)!==0,x=g||(v&L)!==0,C=(0,c.useRef)(null),w=(0,c.useCallback)(e=>{C.current=e},[]),T=o.useMergedRefs(t,w),E=(0,c.useRef)(null);(0,c.useLayoutEffect)(()=>{let e=f===void 0&&m===void 0?C.current?.textContent?.trim():void 0,t={value:d,label:f??m??(e||d)};m!==void 0&&(t.textValue=m),h&&(t.disabled=!0),g&&(t.hidden=!0);let r=E.current;r&&r.store===n&&p(r.item,t)||(r?.unregister(),E.current={store:n,item:t,unregister:n.register(t,C)})}),(0,c.useLayoutEffect)(()=>()=>{E.current?.unregister(),E.current=null},[]);let D=(0,c.useCallback)(e=>{if(h)return;let t={value:d,label:f??m??d};m!==void 0&&(t.textValue=m),s(d,t,e.nativeEvent)},[s,d,f,m,h]),O=(0,c.useCallback)(()=>{!h&&!n.isActive(d)&&l(d)},[l,n,d,h]),k=(0,c.useCallback)(e=>{e.button===0&&(e.preventDefault(),u(d))},[u,d]),A={id:_,role:`option`,"aria-selected":b,onClick:D,onPointerMove:O,ref:T};return a===`standalone`&&(A.onMouseDown=k),h&&(A[`aria-disabled`]=!0,A[`data-disabled`]=``),x&&(A.hidden=!0),y&&(A[`data-active`]=``),b&&(A[`data-selected`]=``),{id:_,selected:b,active:y,disabled:h,hidden:x,multiselect:i,optionProps:A}}exports.ListboxContext=y,exports.collectOptionLabels=k,exports.markListboxElement=w,exports.useListbox=P,exports.useListboxOption=z;
2
2
  //# sourceMappingURL=useListbox.cjs.map