@mortenbrudvik/waveui 0.15.0 → 0.17.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.
- package/README.md +339 -40
- package/dist/components/data-display/List.cjs +1 -1
- package/dist/components/data-display/List.cjs.map +1 -1
- package/dist/components/data-display/List.mjs +405 -219
- package/dist/components/data-display/List.mjs.map +1 -1
- package/dist/components/data-display/List.registry.cjs +1 -1
- package/dist/components/data-display/List.registry.cjs.map +1 -1
- package/dist/components/data-display/List.registry.mjs +12 -6
- package/dist/components/data-display/List.registry.mjs.map +1 -1
- package/dist/components/input/Combobox.cjs +1 -1
- package/dist/components/input/Combobox.cjs.map +1 -1
- package/dist/components/input/Combobox.mjs +163 -148
- package/dist/components/input/Combobox.mjs.map +1 -1
- package/dist/components/input/Dropdown.cjs +1 -1
- package/dist/components/input/Dropdown.cjs.map +1 -1
- package/dist/components/input/Dropdown.mjs +132 -117
- package/dist/components/input/Dropdown.mjs.map +1 -1
- package/dist/components/input/Label.cjs +1 -1
- package/dist/components/input/Label.cjs.map +1 -1
- package/dist/components/input/Label.mjs +1 -0
- package/dist/components/input/Label.mjs.map +1 -1
- package/dist/components/input/Listbox.cjs +1 -1
- package/dist/components/input/Listbox.cjs.map +1 -1
- package/dist/components/input/Listbox.mjs +90 -78
- package/dist/components/input/Listbox.mjs.map +1 -1
- package/dist/components/input/Option.cjs +1 -1
- package/dist/components/input/Option.cjs.map +1 -1
- package/dist/components/input/Option.data.cjs +2 -0
- package/dist/components/input/Option.data.cjs.map +1 -0
- package/dist/components/input/Option.data.mjs +114 -0
- package/dist/components/input/Option.data.mjs.map +1 -0
- package/dist/components/input/Option.mjs +21 -17
- package/dist/components/input/Option.mjs.map +1 -1
- package/dist/components/layout/FlatTree.cjs +2 -0
- package/dist/components/layout/FlatTree.cjs.map +1 -0
- package/dist/components/layout/FlatTree.context.cjs +2 -0
- package/dist/components/layout/FlatTree.context.cjs.map +1 -0
- package/dist/components/layout/FlatTree.context.mjs +20 -0
- package/dist/components/layout/FlatTree.context.mjs.map +1 -0
- package/dist/components/layout/FlatTree.item.cjs +2 -0
- package/dist/components/layout/FlatTree.item.cjs.map +1 -0
- package/dist/components/layout/FlatTree.item.mjs +90 -0
- package/dist/components/layout/FlatTree.item.mjs.map +1 -0
- package/dist/components/layout/FlatTree.mjs +276 -0
- package/dist/components/layout/FlatTree.mjs.map +1 -0
- package/dist/components/layout/Tree.core.cjs +1 -1
- package/dist/components/layout/Tree.core.cjs.map +1 -1
- package/dist/components/layout/Tree.core.mjs +166 -165
- package/dist/components/layout/Tree.core.mjs.map +1 -1
- package/dist/components/layout/Tree.selection.cjs +1 -1
- package/dist/components/layout/Tree.selection.cjs.map +1 -1
- package/dist/components/layout/Tree.selection.mjs +11 -57
- package/dist/components/layout/Tree.selection.mjs.map +1 -1
- package/dist/components/layout/useFlatTree.cjs +2 -0
- package/dist/components/layout/useFlatTree.cjs.map +1 -0
- package/dist/components/layout/useFlatTree.mjs +83 -0
- package/dist/components/layout/useFlatTree.mjs.map +1 -0
- package/dist/components/portal/Portal.cjs +1 -1
- package/dist/components/portal/Portal.cjs.map +1 -1
- package/dist/components/portal/Portal.mjs +12 -11
- package/dist/components/portal/Portal.mjs.map +1 -1
- package/dist/components/provider/WaveProvider.cjs +1 -1
- package/dist/components/provider/WaveProvider.cjs.map +1 -1
- package/dist/components/provider/WaveProvider.mjs +59 -35
- package/dist/components/provider/WaveProvider.mjs.map +1 -1
- package/dist/components/table/DataGrid.cjs +1 -1
- package/dist/components/table/DataGrid.cjs.map +1 -1
- package/dist/components/table/DataGrid.context.cjs +2 -0
- package/dist/components/table/DataGrid.context.cjs.map +1 -0
- package/dist/components/table/DataGrid.context.mjs +74 -0
- package/dist/components/table/DataGrid.context.mjs.map +1 -0
- package/dist/components/table/DataGrid.header.cjs +2 -0
- package/dist/components/table/DataGrid.header.cjs.map +1 -0
- package/dist/components/table/DataGrid.header.mjs +168 -0
- package/dist/components/table/DataGrid.header.mjs.map +1 -0
- package/dist/components/table/DataGrid.mjs +488 -591
- package/dist/components/table/DataGrid.mjs.map +1 -1
- package/dist/components/table/DataGrid.sizing.cjs +2 -0
- package/dist/components/table/DataGrid.sizing.cjs.map +1 -0
- package/dist/components/table/DataGrid.sizing.mjs +187 -0
- package/dist/components/table/DataGrid.sizing.mjs.map +1 -0
- package/dist/components/table/DataGrid.store.cjs +2 -0
- package/dist/components/table/DataGrid.store.cjs.map +1 -0
- package/dist/components/table/DataGrid.store.mjs +108 -0
- package/dist/components/table/DataGrid.store.mjs.map +1 -0
- package/dist/components/table/DataGrid.window.cjs +2 -0
- package/dist/components/table/DataGrid.window.cjs.map +1 -0
- package/dist/components/table/DataGrid.window.mjs +165 -0
- package/dist/components/table/DataGrid.window.mjs.map +1 -0
- package/dist/components/table/Table.cjs +1 -1
- package/dist/components/table/Table.cjs.map +1 -1
- package/dist/components/table/Table.mjs +74 -72
- package/dist/components/table/Table.mjs.map +1 -1
- package/dist/components/table/Table.resize.cjs +2 -0
- package/dist/components/table/Table.resize.cjs.map +1 -0
- package/dist/components/table/Table.resize.mjs +98 -0
- package/dist/components/table/Table.resize.mjs.map +1 -0
- package/dist/components/table/useGridNavigation.cjs +1 -1
- package/dist/components/table/useGridNavigation.cjs.map +1 -1
- package/dist/components/table/useGridNavigation.mjs +343 -140
- package/dist/components/table/useGridNavigation.mjs.map +1 -1
- package/dist/components/table/useTableColumnSizing.cjs +2 -0
- package/dist/components/table/useTableColumnSizing.cjs.map +1 -0
- package/dist/components/table/useTableColumnSizing.mjs +76 -0
- package/dist/components/table/useTableColumnSizing.mjs.map +1 -0
- package/dist/components/table/useTableFeatures.cjs +2 -0
- package/dist/components/table/useTableFeatures.cjs.map +1 -0
- package/dist/components/table/useTableFeatures.mjs +110 -0
- package/dist/components/table/useTableFeatures.mjs.map +1 -0
- package/dist/components/table/useTableSelection.cjs +2 -0
- package/dist/components/table/useTableSelection.cjs.map +1 -0
- package/dist/components/table/useTableSelection.mjs +39 -0
- package/dist/components/table/useTableSelection.mjs.map +1 -0
- package/dist/components/table/useTableSort.cjs +2 -0
- package/dist/components/table/useTableSort.cjs.map +1 -0
- package/dist/components/table/useTableSort.mjs +58 -0
- package/dist/components/table/useTableSort.mjs.map +1 -0
- package/dist/components/typography/Text.cjs +1 -1
- package/dist/components/typography/Text.cjs.map +1 -1
- package/dist/components/typography/Text.mjs +11 -7
- package/dist/components/typography/Text.mjs.map +1 -1
- package/dist/hooks/useActiveDescendant.cjs +1 -1
- package/dist/hooks/useActiveDescendant.cjs.map +1 -1
- package/dist/hooks/useActiveDescendant.mjs +33 -25
- package/dist/hooks/useActiveDescendant.mjs.map +1 -1
- package/dist/hooks/useListbox.cjs +1 -1
- package/dist/hooks/useListbox.cjs.map +1 -1
- package/dist/hooks/useListbox.mjs +140 -130
- package/dist/hooks/useListbox.mjs.map +1 -1
- package/dist/hooks/useRovingTabIndex.cjs +1 -1
- package/dist/hooks/useRovingTabIndex.cjs.map +1 -1
- package/dist/hooks/useRovingTabIndex.mjs +1 -1
- package/dist/hooks/useRovingTabIndex.mjs.map +1 -1
- package/dist/hooks/useSelection.cjs +2 -0
- package/dist/hooks/useSelection.cjs.map +1 -0
- package/dist/hooks/useSelection.mjs +57 -0
- package/dist/hooks/useSelection.mjs.map +1 -0
- package/dist/hooks/useVirtualWindow.cjs +2 -0
- package/dist/hooks/useVirtualWindow.cjs.map +1 -0
- package/dist/hooks/useVirtualWindow.mjs +102 -0
- package/dist/hooks/useVirtualWindow.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1799 -63
- package/dist/index.d.ts +1799 -63
- package/dist/index.mjs +150 -136
- package/dist/lib/brand.cjs +2 -0
- package/dist/lib/brand.cjs.map +1 -0
- package/dist/lib/brand.mjs +358 -0
- package/dist/lib/brand.mjs.map +1 -0
- package/dist/lib/classNames.cjs +1 -1
- package/dist/lib/classNames.cjs.map +1 -1
- package/dist/lib/classNames.mjs +68 -49
- package/dist/lib/classNames.mjs.map +1 -1
- package/dist/lib/cn.cjs +1 -1
- package/dist/lib/cn.cjs.map +1 -1
- package/dist/lib/cn.mjs +5 -1
- package/dist/lib/cn.mjs.map +1 -1
- package/dist/lib/columnSizing.cjs +2 -0
- package/dist/lib/columnSizing.cjs.map +1 -0
- package/dist/lib/columnSizing.mjs +27 -0
- package/dist/lib/columnSizing.mjs.map +1 -0
- package/dist/lib/flatTree.cjs +2 -0
- package/dist/lib/flatTree.cjs.map +1 -0
- package/dist/lib/flatTree.mjs +118 -0
- package/dist/lib/flatTree.mjs.map +1 -0
- package/dist/lib/selection.cjs +2 -0
- package/dist/lib/selection.cjs.map +1 -0
- package/dist/lib/selection.mjs +28 -0
- package/dist/lib/selection.mjs.map +1 -0
- package/dist/lib/tokens.cjs +2 -0
- package/dist/lib/tokens.cjs.map +1 -0
- package/dist/lib/tokens.mjs +43 -0
- package/dist/lib/tokens.mjs.map +1 -0
- package/dist/lib/treeCascade.cjs +2 -0
- package/dist/lib/treeCascade.cjs.map +1 -0
- package/dist/lib/treeCascade.mjs +52 -0
- package/dist/lib/treeCascade.mjs.map +1 -0
- package/dist/lib/virtual.cjs +2 -0
- package/dist/lib/virtual.cjs.map +1 -0
- package/dist/lib/virtual.mjs +54 -0
- package/dist/lib/virtual.mjs.map +1 -0
- package/dist/lib/waveTokens.cjs +2 -0
- package/dist/lib/waveTokens.cjs.map +1 -0
- package/dist/lib/waveTokens.mjs +24 -0
- package/dist/lib/waveTokens.mjs.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +3 -1
- package/src/styles/tokens.css +210 -10
|
@@ -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","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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"}
|
|
1
|
+
{"version":3,"file":"useGridNavigation.mjs","names":["React","NON_TEXT_INPUT_TYPES","KEY_CONSUMING_ROLES","COMPOSITE_SELECTOR","role","WIDGET_CANDIDATE_SELECTOR","FOCUSABLE_SELECTOR","AUXILIARY_ATTRIBUTE","ROW_INDEX_ATTRIBUTE","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","getRowIndex","value","toCount","getGridRows","model","headerRowCount","headers","locateCell","at","getNearestRowCell","nearest","distance","placeActiveRow","mounted","activeRow","rowCount","each","current","getDeepActiveElement","active","doc","GridRowIndexes","listener","pending","getNoRow","collectCells","into","getWidgetEntry","widget","getAuxiliaryEntry","widgets","entry","isInteractionFocus","focused","target","interactionCell","cellReturns","returnFocusToGridCell","element","returnTo","useGridNavigation","options","pageSize","setGrid","modelRef","activeRowRef","pendingRef","rowIndexes","activeRowIndex","pendingRowIndex","activeCellRef","interactionCellRef","stamped","written","retained","pageSizeRef","getWidgets","precise","tabIndex","getTarget","only","checkRadioGroup","setTabIndex","syncCell","isActive","isInteraction","keepCell","isFocused","isStop","publish","sync","currentModel","table","targets","gone","placed","previous","interaction","cells","skipped","activate","previousActive","previousInteraction","focusCell","requestRow","resolvePending","focusInGrid","moveByRow","last","columns","from","start","step","single","key","position","candidate","observer","relevant","all","dirty","record","records","onKeyDown","event","widgetTarget","onCell","auxiliaryEntry","columnIndex","lastRow","ctrl","vertical","getArrowIntent","getDirection","destination","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/**\n * The data behind the rows of a windowed grid, whose rows are mounted only in part (see\n * {@link UseGridNavigationOptions.model}). Each mounted data row carries its 0-based index in the\n * data as `data-row-index`.\n */\nexport interface GridNavigationModel {\n /** How many data rows there are, mounted or not. */\n rowCount: number;\n /**\n * How many header rows are above the data rows: the first rows of the grid without a\n * `data-row-index` (they stay mounted). Other rows without one, such as the spacer rows of a\n * window, are not navigated.\n */\n headerRowCount: number;\n /**\n * Brings data row `index` into the mounted rows (scrolls the window to it). Called once for each\n * row a key moves focus to that is not mounted; focus moves to the row's cell in the commit that\n * mounts it. Ctrl+Home in a grid with header rows also calls it for row 0, so the first rows\n * show under the header.\n */\n scrollToRow(index: number): void;\n}\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 * The data of a windowed grid (DataGrid with a virtual window). ArrowUp/ArrowDown,\n * PageUp/PageDown and Ctrl+Home/Ctrl+End then move by the rows of the data: a target row that is\n * not mounted is asked for with `scrollToRow` and focused when it mounts. The active row is kept\n * by its index (see {@link UseGridNavigationResult.activeRowIndex}). Without it, the mounted\n * rows are the whole grid.\n */\n model?: GridNavigationModel;\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 * With a `model`: the `data-row-index` of the active cell's row, the row that holds the grid's\n * tab stop (or takes it back once it is mounted again), so the owner keeps that row mounted.\n * `null` on a header row, and always `null` without a model. A change re-renders the owner.\n */\n activeRowIndex: number | null;\n /**\n * With a `model`: the index of the data row a key moved focus to while it is not mounted (asked\n * for with `scrollToRow`); focus moves there in the commit that mounts it, so the owner keeps it\n * mounted too. `null` when no row is awaited, and always `null` without a model. A change\n * re-renders the owner.\n */\n pendingRowIndex: number | null;\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 * Marks an auxiliary widget (a column's resize handle): a widget of its cell that never decides the\n * cell's target and is entered with F2.\n */\nconst AUXILIARY_ATTRIBUTE = 'data-grid-auxiliary';\n\n/** The attribute of a windowed grid's data rows that holds their 0-based index in the data. */\nconst ROW_INDEX_ATTRIBUTE = 'data-row-index';\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). A row's\n * index changes in place when rows keyed by id are shown at other indexes (items inserted above).\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 AUXILIARY_ATTRIBUTE,\n ROW_INDEX_ATTRIBUTE,\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 data row index `row` carries, or `null` (a header or spacer row, or no row). */\nfunction getRowIndex(row: Element | null): number | null {\n const value = row?.getAttribute(ROW_INDEX_ATTRIBUTE);\n return value != null && /^\\d+$/.test(value) ? Number(value) : null;\n}\n\n/** A count of a model: a whole number, at least 0 (0 when it is not finite). */\nfunction toCount(value: number): number {\n return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;\n}\n\n/**\n * The rows keys move between. With a model: the first `headerRowCount` rows without a row index\n * that are not `aria-hidden` (the header rows) and every row with one (the data rows); other rows,\n * the spacer rows of a window, are left out.\n */\nfunction getGridRows(\n grid: HTMLTableElement,\n model: GridNavigationModel | undefined,\n): HTMLTableRowElement[] {\n if (!model) return getRows(grid);\n const headerRowCount = toCount(model.headerRowCount);\n let headers = 0;\n return getRows(grid).filter((row) => {\n if (getRowIndex(row) !== null) return true;\n if (headers >= headerRowCount || row.getAttribute('aria-hidden') === 'true') return false;\n headers += 1;\n return true;\n });\n}\n\n/**\n * With a model, the active cell by its row and visual column, so the tab stop survives the row\n * unmounting: the row's element while it is mounted, and its data row index (`null`: a header row).\n */\ninterface ActiveRow {\n row: HTMLTableRowElement | null;\n index: number | null;\n column: number;\n}\n\n/** With a model, a data row a key moved to that is not mounted yet. */\ninterface PendingRow {\n index: number;\n /** The visual column to focus there (`Infinity`: the row's last cell). */\n column: number;\n}\n\n/** The row and visual column of `cell` among `rows` (column 0 for a cell outside them). */\nfunction locateCell(rows: readonly HTMLTableRowElement[], cell: HTMLTableCellElement): ActiveRow {\n const row = cell.parentElement as HTMLTableRowElement;\n const at = rows.indexOf(row);\n return {\n row: at < 0 ? null : row,\n index: getRowIndex(row),\n column: at < 0 ? 0 : Math.max(getColumnMap(rows)[at].indexOf(cell), 0),\n };\n}\n\n/** The cell at visual `column` of the mounted data row nearest to `index` (`null`: none). */\nfunction getNearestRowCell(\n rows: readonly HTMLTableRowElement[],\n index: number,\n column: number,\n): HTMLTableCellElement | null {\n let nearest = -1;\n let distance = Infinity;\n rows.forEach((row, at) => {\n const rowIndex = getRowIndex(row);\n if (rowIndex === null || Math.abs(rowIndex - index) >= distance) return;\n nearest = at;\n distance = Math.abs(rowIndex - index);\n });\n return nearest < 0 ? null : getCellAtColumn(getColumnMap(rows)[nearest], column);\n}\n\n/**\n * With a model, where `activeRow` is now and which cell holds the tab stop for it; `null` when the\n * rule without a model applies (the first cell once the active cell is gone): a header row that is\n * gone, or a data row that left the data (`rowCount`). The row is its element while that is\n * mounted (its index follows it: rows keyed by id are shown at other indexes when items are\n * inserted above), else the mounted row of its index (a row mounted again). While neither is mounted, a stand-in holds the tab stop:\n * `current` (the active cell, while it is mounted), else the cell of the nearest mounted data row\n * (`cell` is `null` when none is mounted).\n */\nfunction placeActiveRow(\n rows: readonly HTMLTableRowElement[],\n activeRow: ActiveRow,\n rowCount: number,\n current: HTMLTableCellElement | null,\n): { activeRow: ActiveRow; cell: HTMLTableCellElement | null } | null {\n const mounted = activeRow.row !== null && rows.includes(activeRow.row);\n const index = mounted ? getRowIndex(activeRow.row) : activeRow.index;\n if (index === null ? !mounted : index >= rowCount) return null;\n const row = mounted ? activeRow.row : (rows.find((each) => getRowIndex(each) === index) ?? null);\n const { column } = activeRow;\n if (!row) {\n // `index` is a data row index here (a header row is always `mounted`).\n return { activeRow, cell: current ?? getNearestRowCell(rows, index as number, column) };\n }\n const cell =\n current?.parentElement === row\n ? current\n : getCellAtColumn(getColumnMap(rows)[rows.indexOf(row)], column);\n return { activeRow: { row, index, column }, cell };\n}\n\n/** The focused element, through open shadow roots (the body, or `null`, when focus is lost). */\nfunction getDeepActiveElement(doc: Document): Element | null {\n let active = doc.activeElement;\n while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;\n return active;\n}\n\n/**\n * The row indexes of {@link UseGridNavigationResult} (`activeRowIndex`, `pendingRowIndex`), read\n * by the owner's render through `useSyncExternalStore` and written by the handlers, the observer\n * and the layout effects. Without a model it stays at `null` and never notifies.\n */\nclass GridRowIndexes {\n private active: number | null = null;\n private pending: number | null = null;\n private readonly listeners = new Set<() => void>();\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getActive = (): number | null => this.active;\n\n getPending = (): number | null => this.pending;\n\n set(active: number | null, pending: number | null): void {\n if (active === this.active && pending === this.pending) return;\n this.active = active;\n this.pending = pending;\n for (const listener of Array.from(this.listeners)) listener();\n }\n}\n\n/** The server snapshot of the row indexes: no row. */\nconst getNoRow = (): null => null;\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 /** An auxiliary widget (`data-grid-auxiliary`): never the cell's target, entered with F2. */\n auxiliary: boolean;\n}\n\n/** The element a widget is entered at: the widget, or a nested composite's entry. */\nfunction getWidgetEntry(widget: CellWidget): HTMLElement | null {\n return widget.composite ? getCompositeEntry(widget.element) : widget.element;\n}\n\n/** The element F2 moves focus to: the entry of the cell's first auxiliary widget that has one. */\nfunction getAuxiliaryEntry(widgets: readonly CellWidget[]): HTMLElement | null {\n for (const widget of widgets) {\n const entry = widget.auxiliary ? getWidgetEntry(widget) : null;\n if (entry) return entry;\n }\n return null;\n}\n\n/**\n * Whether focus on `focused`, inside `cell` whose target is `target`, is interaction mode: on\n * anything but the cell and its target; and on its target too while the widgets of a cell with an\n * auxiliary widget are being interacted with (`interactionCell`), so Tab moves between all of them.\n */\nfunction isInteractionFocus(\n cell: HTMLTableCellElement,\n focused: EventTarget | null,\n target: HTMLElement,\n widgets: readonly CellWidget[],\n interactionCell: HTMLTableCellElement | null,\n): boolean {\n if (focused === cell) return false;\n if (focused !== target) return true;\n return interactionCell === cell && widgets.some((widget) => widget.auxiliary);\n}\n\n/**\n * The grids of mounted {@link useGridNavigation} hooks, each with the function that returns focus\n * to one of its cells, as Escape does (see {@link returnFocusToGridCell}).\n */\nconst cellReturns = new WeakMap<Element, (cell: HTMLTableCellElement) => void>();\n\n/**\n * Ends interaction mode in the grid cell that holds `element` and moves focus to the cell's target\n * (the cell, or its target widget), as Escape does: for a widget that handles Escape, Enter or\n * Space itself to leave (a DataGrid column's resize handle). Returns whether a grid of\n * {@link useGridNavigation} holds `element` in one of its cells. Module-only.\n */\nexport function returnFocusToGridCell(element: Element): boolean {\n for (let el = element.parentElement; el; el = el.parentElement) {\n const returnTo = cellReturns.get(el);\n if (!returnTo) continue;\n const cell = getCell(el as HTMLTableElement, element);\n if (!cell) return false;\n returnTo(cell);\n return true;\n }\n return false;\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 * - **Auxiliary widgets** (a widget that carries `data-grid-auxiliary`, such as a column's resize\n * handle) are left out when the cell's target is chosen, so a header with a sort button and a\n * resize handle keeps the button as its target (Enter sorts), and a cell whose only widget is\n * auxiliary is its own target. Like every widget they are at `tabindex=\"-1\"` in navigation mode\n * and take part in interaction mode. F2 on a cell that has one, or on its target widget, enters\n * interaction mode at the first auxiliary widget (Enter on a cell that is its own target still\n * enters its first widget); Escape returns to the cell's target, the cell or its target widget.\n * While the widgets of such a cell are interacted with, its target widget is in interaction mode\n * too, so Tab moves between all of them and Escape on the target ends the mode. A cell without\n * auxiliary widgets behaves as described above.\n * - **Rows by their data** (a `model`: a windowed grid, whose rows are mounted in part): the rows\n * are the header rows and the data rows, which carry their index in the data as `data-row-index`\n * (other rows, such as the spacer rows of a window, are skipped). ArrowUp/ArrowDown,\n * PageUp/PageDown and Ctrl+Home/Ctrl+End go by that index over every row of the data, header\n * rows first, with the rules above: a target row that is not mounted is asked for with\n * `scrollToRow` (once per row) and focused in the commit that mounts it, and Ctrl+Home also\n * brings data row 0 back under the header rows. The active cell is kept by its row and column:\n * `activeRowIndex` and `pendingRowIndex` name the rows the owner keeps mounted, and while the\n * active row is not mounted anyway, a cell of the nearest mounted row holds the tab stop until\n * the row is back. ArrowLeft/ArrowRight and Home/End work in the row as above.\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 */\nexport function useGridNavigation(options: UseGridNavigationOptions = {}): UseGridNavigationResult {\n const { model } = options;\n const pageSize = Math.max(1, options.pageSize ?? 10);\n const [grid, setGrid] = React.useState<HTMLTableElement | null>(null);\n /** The model of the last commit (read by the handlers, the observer and the effects). */\n const modelRef = React.useRef(model);\n /** With a model: the active cell's row and column (see {@link ActiveRow}). */\n const activeRowRef = React.useRef<ActiveRow | null>(null);\n /** With a model: the data row a key moved to that is not mounted yet. */\n const pendingRef = React.useRef<PendingRow | null>(null);\n const [rowIndexes] = React.useState(() => new GridRowIndexes());\n const activeRowIndex = React.useSyncExternalStore(\n rowIndexes.subscribe,\n rowIndexes.getActive,\n getNoRow,\n );\n const pendingRowIndex = React.useSyncExternalStore(\n rowIndexes.subscribe,\n rowIndexes.getPending,\n getNoRow,\n );\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) {\n widgets.push({\n element: el,\n composite: true,\n auxiliary: el.hasAttribute(AUXILIARY_ATTRIBUTE),\n });\n }\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({\n element: el,\n composite: false,\n auxiliary: el.hasAttribute(AUXILIARY_ATTRIBUTE),\n });\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. Auxiliary widgets are not counted.\n */\n const getTarget = React.useCallback(\n (cell: HTMLTableCellElement, widgets: CellWidget[], checkRadioGroup: boolean): HTMLElement => {\n let only: CellWidget | null = null;\n for (const widget of widgets) {\n if (widget.auxiliary) continue;\n if (only) return cell;\n only = widget;\n }\n if (!only) return cell;\n const { element, composite } = only;\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 /** With a model: publishes the active and the pending row index to the owner's render. */\n const publish = React.useCallback(() => {\n rowIndexes.set(activeRowRef.current?.index ?? null, pendingRef.current?.index ?? null);\n }, [rowIndexes]);\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. With a model, an active cell that is gone\n * while its row index is in the data is replaced in its row instead (see `placeActiveRow`).\n */\n const sync = React.useCallback(\n (table: HTMLTableElement, cells?: Iterable<HTMLTableCellElement | null>) => {\n const currentModel = modelRef.current;\n const rows = getGridRows(table, currentModel);\n let active = activeCellRef.current;\n const targets = new Set<HTMLTableCellElement>();\n let gone =\n !active ||\n !active.isConnected ||\n !rows.includes(active.parentElement as HTMLTableRowElement);\n const placed =\n currentModel && activeRowRef.current\n ? placeActiveRow(\n rows,\n activeRowRef.current,\n toCount(currentModel.rowCount),\n gone ? null : active,\n )\n : null;\n if (placed) {\n activeRowRef.current = placed.activeRow;\n if (placed.cell) {\n gone = false;\n if (placed.cell !== active) {\n // The cell that held the tab stop (a stand-in) gives it up.\n if (active) targets.add(active);\n active = placed.cell;\n activeCellRef.current = active;\n targets.add(active);\n }\n }\n }\n if (gone) {\n active = rows[0]?.cells[0] ?? null;\n activeCellRef.current = active;\n if (active) targets.add(active);\n if (currentModel && !placed) activeRowRef.current = null;\n }\n if (currentModel && active && activeRowRef.current === null) {\n activeRowRef.current = locateCell(rows, 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 // With a model, the cells of rows keys skip (the spacer rows of a window) are never stamped.\n const skipped = currentModel ? new Set(rows) : null;\n for (const cell of targets) {\n if (!cell.isConnected || getCell(table, cell) !== cell) continue;\n if (\n skipped &&\n !skipped.has(cell.parentElement as HTMLTableRowElement) &&\n !stamped.has(cell)\n ) {\n continue;\n }\n syncCell(cell, cell === active, cell === interaction);\n }\n if (currentModel) publish();\n },\n [publish, stamped, syncCell],\n );\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`. With a model, its row is\n * the active row, and a row that was awaited is not any more (focus moved).\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 const currentModel = modelRef.current;\n if (currentModel) {\n activeRowRef.current = locateCell(getGridRows(table, currentModel), cell);\n pendingRef.current = null;\n }\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 /**\n * With a model: awaits data row `index`, which is not mounted. The model scrolls to it (once per\n * row: a key pressed again before it mounts replaces the target, and asks again only for another\n * row), and focus moves to its cell at `column` once it is mounted (`resolvePending`).\n */\n const requestRow = React.useCallback(\n (index: number, column: number) => {\n const currentModel = modelRef.current;\n if (!currentModel) return;\n const previous = pendingRef.current;\n pendingRef.current = { index, column };\n publish();\n if (previous?.index !== index) currentModel.scrollToRow(index);\n },\n [publish],\n );\n\n /**\n * With a model: focuses the awaited row's cell once the row is mounted, from the layout effect of\n * the owner's commit that mounts it or from the observer (a row mounted by another component's\n * commit). The request is dropped when the row left the data, and when focus went to an element\n * outside the grid meanwhile (focus stays there).\n */\n const resolvePending = React.useCallback(\n (table: HTMLTableElement) => {\n const pending = pendingRef.current;\n const currentModel = modelRef.current;\n if (!pending || !currentModel) return;\n if (pending.index >= toCount(currentModel.rowCount)) {\n pendingRef.current = null;\n publish();\n return;\n }\n const rows = getGridRows(table, currentModel);\n const at = rows.findIndex((row) => getRowIndex(row) === pending.index);\n if (at < 0) return;\n pendingRef.current = null;\n const target = getCellAtColumn(getColumnMap(rows)[at], pending.column);\n const focused = getDeepActiveElement(table.ownerDocument);\n const focusInGrid =\n focused === null || focused === table.ownerDocument.body || table.contains(focused);\n if (target && focusInGrid) focusCell(table, target);\n else publish();\n },\n [focusCell, publish],\n );\n\n /**\n * With a model: moves focus by the rows of the data for `key` (ArrowUp/ArrowDown, PageUp/PageDown,\n * or Home/End with Ctrl held), from the awaited row while there is one, else from `cell`. The\n * header rows come first, then the data rows by index. Up/Down and PageUp/PageDown keep the\n * visual column; Ctrl+Home goes to the first cell of the grid and Ctrl+End to the last cell of the\n * last data row. A target row that is not mounted is awaited (`requestRow`).\n */\n const moveByRow = React.useCallback(\n (\n table: HTMLTableElement,\n cell: HTMLTableCellElement,\n rows: HTMLTableRowElement[],\n key: string,\n ) => {\n const currentModel = modelRef.current;\n if (!currentModel) return;\n const headers = rows.filter((row) => getRowIndex(row) === null);\n /** Where each mounted data row is in `rows`, by its index. */\n const mounted = new Map<number, number>();\n rows.forEach((row, at) => {\n const index = getRowIndex(row);\n if (index !== null) mounted.set(index, at);\n });\n const rowCount = toCount(currentModel.rowCount);\n const last = headers.length + rowCount - 1;\n const columns = getColumnMap(rows);\n const pending = pendingRef.current;\n /** The row the move starts from (header rows, then data rows) and its visual column. */\n let from: number;\n let column: number;\n if (pending) {\n from = headers.length + Math.min(pending.index, rowCount - 1);\n column = pending.column;\n } else {\n const row = cell.parentElement as HTMLTableRowElement;\n const index = getRowIndex(row);\n from = index === null ? headers.indexOf(row) : headers.length + index;\n column = Math.max(columns[rows.indexOf(row)].indexOf(cell), 0);\n }\n let start: number;\n let step: 1 | -1 = 1;\n /** Ctrl+Home and Ctrl+End go to one row (no row a spanning cell covers is skipped). */\n let single = false;\n switch (key) {\n case 'ArrowDown':\n start = from + 1;\n break;\n case 'ArrowUp':\n start = from - 1;\n step = -1;\n break;\n case 'PageDown':\n start = Math.min(from + pageSizeRef.current, last);\n break;\n case 'PageUp':\n start = Math.max(from - pageSizeRef.current, 0);\n step = -1;\n break;\n case 'Home':\n start = 0;\n column = 0;\n single = true;\n break;\n default:\n start = last;\n column = Infinity;\n single = true;\n break;\n }\n for (let position = start; position >= 0 && position <= last; position += step) {\n const at =\n position < headers.length\n ? rows.indexOf(headers[position])\n : (mounted.get(position - headers.length) ?? -1);\n if (at < 0) {\n requestRow(position - headers.length, column);\n break;\n }\n const candidate = getCellAtColumn(columns[at], column);\n // The focused cell itself is passed over (a cell spanning rows), except while a row is\n // awaited: the move starts from that row, so a key back to the focused cell targets it\n // (focusing it drops the request).\n if (candidate && (pending || candidate !== cell)) {\n focusCell(table, candidate);\n break;\n }\n if (single) break;\n }\n // Focus on a header row: the first data rows come back under it, as the top of the grid.\n if (key === 'Home' && headers.length > 0 && rowCount > 0) currentModel.scrollToRow(0);\n },\n [focusCell, requestRow],\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 // With a model, an awaited row mounted by a commit of another component.\n resolvePending(grid);\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, resolvePending, sync, written]);\n\n // A widget that handles its leaving keys itself returns through `returnFocusToGridCell`.\n React.useLayoutEffect(() => {\n if (!grid) return;\n const returnTo = (cell: HTMLTableCellElement) => focusCell(grid, cell);\n cellReturns.set(grid, returnTo);\n return () => {\n if (cellReturns.get(grid) === returnTo) cellReturns.delete(grid);\n };\n }, [grid, focusCell]);\n\n // After every commit: the model of this render, and focus on an awaited row that the commit\n // mounted. Without a model, no row is kept by its index.\n React.useLayoutEffect(() => {\n modelRef.current = model;\n if (!model) {\n activeRowRef.current = null;\n pendingRef.current = null;\n rowIndexes.set(null, null);\n return;\n }\n if (grid) resolvePending(grid);\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 (\n isInteractionFocus(cell, event.target, widgetTarget, widgets, interactionCellRef.current)\n ) {\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 // F2 on a cell with an auxiliary widget, or on its target widget, enters the auxiliary one.\n const auxiliaryEntry = event.key === 'F2' ? getAuxiliaryEntry(widgets) : null;\n if (auxiliaryEntry) {\n event.preventDefault();\n activate(table, cell, true);\n auxiliaryEntry.focus();\n return;\n }\n\n if ((event.key === 'Enter' || event.key === 'F2') && onCell) {\n let entry: HTMLElement | null = null;\n for (const widget of widgets) {\n entry = getWidgetEntry(widget);\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 = getGridRows(table, modelRef.current);\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 // With a model, Up/Down, PageUp/PageDown and Ctrl+Home/Ctrl+End go by the rows of the data.\n if (modelRef.current && (vertical || ctrl)) {\n moveByRow(table, cell, rows, event.key);\n return;\n }\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, moveByRow],\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 let interaction = false;\n if (target !== cell) {\n const widgets = getWidgets(cell, true);\n interaction = isInteractionFocus(\n cell,\n target,\n getTarget(cell, widgets, false),\n widgets,\n interactionCellRef.current,\n );\n }\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, activeRowIndex, pendingRowIndex };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("../../hooks/useControllable.cjs"),t=require("../../lib/columnSizing.cjs"),n=require("./useTableFeatures.cjs");let r=require("react");var i={};function a(a={}){let{columnSizingOptions:o,columnWidths:s,defaultColumnWidths:c,onColumnWidthsChange:l}=a,u=(0,r.useRef)(void 0),[d,f,p]=e.useControllable(s,c??i,e=>{let t=u.current;t&&l?.(e,t)}),m=p||c!==void 0||d!==i,h=(0,r.useCallback)((e,n,r,a)=>{if(!e.includes(n)||!Number.isFinite(r))return;let s=t.clampColumnWidth(r,t.getColumnEntry(o,n));u.current={columnId:n,width:s,event:a??new Event(`resize`)};try{f(r=>{let a=t.resolveColumnWidths(e,o,r);return(m||r!==i)&&a[n]===s?r:(a[n]=s,a)})}finally{u.current=void 0}},[f,o,m]);return(0,r.useMemo)(()=>n.createTableFeaturePlugin({feature:`columnSizing`,extend:e=>{let n=[];for(let t of e.columns)t.columnId!==void 0&&n.push(t.columnId);let r=t.resolveColumnWidths(n,o,d),i=t.totalWidth(n,r),a=(e,t,r)=>h(n,e,t,r),s=e=>t.initialColumnWidth(t.getColumnEntry(o,e)),c=e=>t.getColumnEntry(r,e)??s(e),l={columnWidths:r,isSized:m,getColumnWidth:e=>t.getColumnEntry(r,e),setColumnWidth:a,getTableProps:()=>({style:m?{tableLayout:`fixed`,width:i}:{tableLayout:`fixed`,width:`100%`,minWidth:i}}),getColumnProps:e=>({style:{width:c(e)}}),getResizeHandleProps:e=>({width:c(e),minWidth:t.getColumnEntry(o,e)?.minWidth??80,onResize:(t,n)=>a(e,t,n?.event),onReset:({event:t})=>{m&&a(e,s(e),t)}})};return{...e,columnSizing:l}}}),[d,m,o,h])}exports.useTableColumnSizing=a;
|
|
2
|
+
//# sourceMappingURL=useTableColumnSizing.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableColumnSizing.cjs","names":["useRef","useControllable","useCallback","clampColumnWidth","getColumnEntry","resolveColumnWidths","useMemo","createTableFeaturePlugin","totalWidth","initialColumnWidth","require_useControllable","require_columnSizing","require_useTableFeatures","react","NO_WIDTHS","useTableColumnSizing","columnWidthsProp","options","detailsRef","widths","setWidths","isControlled","defaultColumnWidths","details","onColumnWidthsChange","next","isSized","resize","ids","columnId","width","clamped","columnSizingOptions","event","previous","column","state","resolved","total","setColumnWidth","id","initialWidthOf","widthOf","columnSizing"],"sources":["../../../src/components/table/useTableColumnSizing.ts"],"sourcesContent":["import { useCallback, useMemo, useRef } from 'react';\nimport { useControllable } from '../../hooks/useControllable';\nimport {\n clampColumnWidth,\n DEFAULT_MIN_COLUMN_WIDTH,\n getColumnEntry,\n initialColumnWidth,\n resolveColumnWidths,\n totalWidth,\n type ColumnSizingOption,\n} from '../../lib/columnSizing';\nimport {\n createTableFeaturePlugin,\n type TableColumnSizingState,\n type TableFeaturePlugin,\n} from './useTableFeatures';\n\nconst NO_WIDTHS: Readonly<Record<string, number>> = {};\n\n/** What {@link UseTableColumnSizingOptions.onColumnWidthsChange} gets besides the new widths. */\nexport interface TableColumnWidthsChangeDetails {\n /** The column that was resized. */\n columnId: string;\n /** Its new width in pixels, held at or above its `minWidth`. */\n width: number;\n /**\n * The event behind the change (the drag, the key press); when none was passed, an untrusted\n * `resize` event with no target.\n */\n event: Event;\n}\n\n/** Options of {@link useTableColumnSizing}. */\nexport interface UseTableColumnSizingOptions {\n /**\n * Per column, by `columnId`: `minWidth` (`@default 80`), and the width the column starts at,\n * `defaultWidth` or else `idealWidth` (else 150). Ids that are not columns are ignored. May be\n * written inline.\n */\n columnSizingOptions?: Record<string, ColumnSizingOption>;\n /**\n * The column widths in pixels (controlled), by `columnId`: a column without an entry has its\n * initial width. Pair it with `onColumnWidthsChange`.\n */\n columnWidths?: Record<string, number>;\n /** The column widths at first render (uncontrolled). */\n defaultColumnWidths?: Record<string, number>;\n /**\n * Called with the width of every column and the details whenever a width changes (every step of\n * a drag or key press). The details are always passed; they are typed optional through 0.x, as\n * every `details` argument. Setting the width a column already has does not call it, once a\n * width is state.\n */\n onColumnWidthsChange?: (\n widths: Record<string, number>,\n details?: TableColumnWidthsChangeDetails,\n ) => void;\n}\n\n/**\n * The column sizing plugin of `useTableFeatures`: the width of every column, held at or above its\n * minimum, and the props that lay a static `Table` out with them. Fills `state.columnSizing`.\n * Spread `getTableProps()` on the `Table`, render a `<colgroup>` with a `<col>` per column and\n * spread `getColumnProps(columnId)` on it, and give each `Table.ResizeHandle` the\n * `getResizeHandleProps(columnId)`. The widths are controlled with `columnWidths` or uncontrolled\n * with `defaultColumnWidths`. Until a width is state (a resize, or `columnWidths` or\n * `defaultColumnWidths` is given) the table is 100% wide and at least as wide as the columns; from\n * then on it is exactly as wide as they are, so a dragged edge follows the pointer. There is no\n * maximum width, and no fitting of the columns to the container. Call it unconditionally and pass\n * the plugin to `useTableFeatures`.\n *\n * @example\n * const { columnSizing } = useTableFeatures({ items, columns }, [\n * useTableColumnSizing({ columnSizingOptions: { name: { minWidth: 120, defaultWidth: 240 } } }),\n * ]);\n * <Table {...columnSizing.getTableProps()}>\n * <colgroup>\n * {columns.map((c) => <col key={c.columnId} {...columnSizing.getColumnProps(c.columnId!)} />)}\n * </colgroup>\n * </Table>\n */\nexport function useTableColumnSizing<T>(\n options: UseTableColumnSizingOptions = {},\n): TableFeaturePlugin<T> {\n const {\n columnSizingOptions,\n columnWidths: columnWidthsProp,\n defaultColumnWidths,\n onColumnWidthsChange,\n } = options;\n\n // The details of the change being made, set around the setter: useControllable calls its\n // onChange synchronously inside it, and only on change.\n const detailsRef = useRef<TableColumnWidthsChangeDetails | undefined>(undefined);\n const [widths, setWidths, isControlled] = useControllable<Record<string, number>>(\n columnWidthsProp,\n defaultColumnWidths ?? NO_WIDTHS,\n (next) => {\n const details = detailsRef.current;\n if (details) onColumnWidthsChange?.(next, details);\n },\n );\n // Until a width is state, an uncontrolled table without defaults holds `NO_WIDTHS` itself.\n const isSized = isControlled || defaultColumnWidths !== undefined || widths !== NO_WIDTHS;\n\n const resize = useCallback(\n (ids: readonly string[], columnId: string, width: number, event: Event | undefined) => {\n if (!ids.includes(columnId) || !Number.isFinite(width)) return;\n const clamped = clampColumnWidth(width, getColumnEntry(columnSizingOptions, columnId));\n detailsRef.current = { columnId, width: clamped, event: event ?? new Event('resize') };\n try {\n setWidths((previous) => {\n const next = resolveColumnWidths(ids, columnSizingOptions, previous);\n // The first width taken as state is a change even when it equals the initial one (an\n // earlier change of the same event already took one).\n const sized = isSized || previous !== NO_WIDTHS;\n if (sized && next[columnId] === clamped) return previous;\n next[columnId] = clamped;\n return next;\n });\n } finally {\n detailsRef.current = undefined;\n }\n },\n [setWidths, columnSizingOptions, isSized],\n );\n\n return useMemo(\n () =>\n createTableFeaturePlugin<T>({\n feature: 'columnSizing',\n extend: (state) => {\n const ids: string[] = [];\n for (const column of state.columns) {\n if (column.columnId !== undefined) ids.push(column.columnId);\n }\n const resolved = resolveColumnWidths(ids, columnSizingOptions, widths);\n const total = totalWidth(ids, resolved);\n const setColumnWidth: TableColumnSizingState['setColumnWidth'] = (id, width, event) =>\n resize(ids, id, width, event);\n const initialWidthOf = (id: string) =>\n initialColumnWidth(getColumnEntry(columnSizingOptions, id));\n const widthOf = (id: string) => getColumnEntry(resolved, id) ?? initialWidthOf(id);\n const columnSizing: TableColumnSizingState = {\n columnWidths: resolved,\n isSized,\n getColumnWidth: (id) => getColumnEntry(resolved, id),\n setColumnWidth,\n getTableProps: () => ({\n style: isSized\n ? { tableLayout: 'fixed', width: total }\n : { tableLayout: 'fixed', width: '100%', minWidth: total },\n }),\n getColumnProps: (id) => ({ style: { width: widthOf(id) } }),\n getResizeHandleProps: (id) => ({\n width: widthOf(id),\n minWidth:\n getColumnEntry(columnSizingOptions, id)?.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH,\n onResize: (width, details) => setColumnWidth(id, width, details?.event),\n // A table without width state is in its initial layout: nothing to reset.\n onReset: ({ event }) => {\n if (isSized) setColumnWidth(id, initialWidthOf(id), event);\n },\n }),\n };\n return { ...state, columnSizing };\n },\n }),\n [widths, isSized, columnSizingOptions, resize],\n );\n}\n"],"mappings":"mKAiBA,IAAMc,EAA8C,CAAC,EAgErD,SAAgBC,EACd,EAAuC,CAAC,EACjB,CACvB,GAAM,CACJ,sBACA,aAAcC,EACd,sBACA,wBACEC,EAIEC,GAAA,EAAalB,EAAAA,OAAAA,CAAmD,IAAA,EAAS,EACzE,CAACmB,EAAQC,EAAWC,GAAgBpB,EAAAA,gBACxCe,EACAM,GAAuBR,EACtB,GAAS,CACR,IAAMS,EAAUL,EAAW,QACvBK,GAASC,IAAuBC,EAAMF,CAAO,CACnD,CACF,EAEMG,EAAUL,GAAgBC,IAAwB,IAAA,IAAaH,IAAWL,EAE1Ea,GAAA,EAASzB,EAAAA,YAAAA,EACZ,EAAwB,EAAkB,EAAe,IAA6B,CACrF,GAAI,CAAC0B,EAAI,SAASC,CAAQ,GAAK,CAAC,OAAO,SAASC,CAAK,EAAG,OACxD,IAAMC,EAAU5B,EAAAA,iBAAiB2B,EAAO1B,EAAAA,eAAe4B,EAAqBH,CAAQ,CAAC,EACrF,EAAW,QAAU,CAAE,WAAU,MAAOE,EAAS,MAAOE,GAAS,IAAI,MAAM,QAAQ,CAAE,EACrF,GAAI,CACFb,EAAW,GAAa,CACtB,IAAMK,EAAOpB,EAAAA,oBAAoBuB,EAAKI,EAAqBE,CAAQ,EAMnE,OAHcR,GAAWQ,IAAapB,IACzBW,EAAKI,KAAcE,EAAgBG,GAChD,EAAKL,GAAYE,EACVN,EACT,CAAC,CACH,QAAU,CACR,EAAW,QAAU,IAAA,EACvB,CACF,EACA,CAACL,EAAWY,EAAqBN,CAAO,CAC1C,EAEA,OAAA,EAAOpB,EAAAA,QAAAA,KAEHC,EAAAA,yBAA4B,CAC1B,QAAS,eACT,OAAS,GAAU,CACjB,IAAMqB,EAAgB,CAAC,EACvB,IAAK,IAAMO,KAAUC,EAAM,QACrBD,EAAO,WAAa,IAAA,IAAWP,EAAI,KAAKO,EAAO,QAAQ,EAE7D,IAAME,EAAWhC,EAAAA,oBAAoBuB,EAAKI,EAAqBb,CAAM,EAC/DmB,EAAQ9B,EAAAA,WAAWoB,EAAKS,CAAQ,EAChCE,GAA4D,EAAI,EAAO,IAC3EZ,EAAOC,EAAKY,EAAIV,EAAOG,CAAK,EACxBQ,EAAkB,GACtBhC,EAAAA,mBAAmBL,EAAAA,eAAe4B,EAAqBQ,CAAE,CAAC,EACtDE,EAAW,GAAetC,EAAAA,eAAeiC,EAAUG,CAAE,GAAKC,EAAeD,CAAE,EAC3EG,EAAuC,CAC3C,aAAcN,EACd,UACA,eAAiB,GAAOjC,EAAAA,eAAeiC,EAAUG,CAAE,EACnD,iBACA,mBAAsB,CACpB,MAAOd,EACH,CAAE,YAAa,QAAS,MAAOY,CAAM,EACrC,CAAE,YAAa,QAAS,MAAO,OAAQ,SAAUA,CAAM,CAC7D,GACA,eAAiB,IAAQ,CAAE,MAAO,CAAE,MAAOI,EAAQF,CAAE,CAAE,CAAE,GACzD,qBAAuB,IAAQ,CAC7B,MAAOE,EAAQF,CAAE,EACjB,SACEpC,EAAAA,eAAe4B,EAAqBQ,CAAE,CAAC,EAAE,UAAA,GAC3C,UAAW,EAAO,IAAYD,EAAeC,EAAIV,EAAOP,GAAS,KAAK,EAEtE,SAAU,CAAE,WAAY,CAClBG,GAASa,EAAeC,EAAIC,EAAeD,CAAE,EAAGP,CAAK,CAC3D,CACF,EACF,EACA,MAAO,CAAE,GAAGG,EAAO,cAAa,CAClC,CACF,CAAC,EACH,CAACjB,EAAQO,EAASM,EAAqBL,CAAM,CAC/C,CACF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useControllable as e } from "../../hooks/useControllable.mjs";
|
|
3
|
+
import { clampColumnWidth as t, getColumnEntry as n, initialColumnWidth as r, resolveColumnWidths as i, totalWidth as a } from "../../lib/columnSizing.mjs";
|
|
4
|
+
import { createTableFeaturePlugin as o } from "./useTableFeatures.mjs";
|
|
5
|
+
import { useCallback as s, useMemo as c, useRef as l } from "react";
|
|
6
|
+
//#region src/components/table/useTableColumnSizing.ts
|
|
7
|
+
var u = {};
|
|
8
|
+
function d(d = {}) {
|
|
9
|
+
let { columnSizingOptions: f, columnWidths: p, defaultColumnWidths: m, onColumnWidthsChange: h } = d, g = l(void 0), [_, v, y] = e(p, m ?? u, (e) => {
|
|
10
|
+
let t = g.current;
|
|
11
|
+
t && h?.(e, t);
|
|
12
|
+
}), b = y || m !== void 0 || _ !== u, x = s((e, r, a, o) => {
|
|
13
|
+
if (!e.includes(r) || !Number.isFinite(a)) return;
|
|
14
|
+
let s = t(a, n(f, r));
|
|
15
|
+
g.current = {
|
|
16
|
+
columnId: r,
|
|
17
|
+
width: s,
|
|
18
|
+
event: o ?? new Event("resize")
|
|
19
|
+
};
|
|
20
|
+
try {
|
|
21
|
+
v((t) => {
|
|
22
|
+
let n = i(e, f, t);
|
|
23
|
+
return (b || t !== u) && n[r] === s ? t : (n[r] = s, n);
|
|
24
|
+
});
|
|
25
|
+
} finally {
|
|
26
|
+
g.current = void 0;
|
|
27
|
+
}
|
|
28
|
+
}, [
|
|
29
|
+
v,
|
|
30
|
+
f,
|
|
31
|
+
b
|
|
32
|
+
]);
|
|
33
|
+
return c(() => o({
|
|
34
|
+
feature: "columnSizing",
|
|
35
|
+
extend: (e) => {
|
|
36
|
+
let t = [];
|
|
37
|
+
for (let n of e.columns) n.columnId !== void 0 && t.push(n.columnId);
|
|
38
|
+
let o = i(t, f, _), s = a(t, o), c = (e, n, r) => x(t, e, n, r), l = (e) => r(n(f, e)), u = (e) => n(o, e) ?? l(e), d = {
|
|
39
|
+
columnWidths: o,
|
|
40
|
+
isSized: b,
|
|
41
|
+
getColumnWidth: (e) => n(o, e),
|
|
42
|
+
setColumnWidth: c,
|
|
43
|
+
getTableProps: () => ({ style: b ? {
|
|
44
|
+
tableLayout: "fixed",
|
|
45
|
+
width: s
|
|
46
|
+
} : {
|
|
47
|
+
tableLayout: "fixed",
|
|
48
|
+
width: "100%",
|
|
49
|
+
minWidth: s
|
|
50
|
+
} }),
|
|
51
|
+
getColumnProps: (e) => ({ style: { width: u(e) } }),
|
|
52
|
+
getResizeHandleProps: (e) => ({
|
|
53
|
+
width: u(e),
|
|
54
|
+
minWidth: n(f, e)?.minWidth ?? 80,
|
|
55
|
+
onResize: (t, n) => c(e, t, n?.event),
|
|
56
|
+
onReset: ({ event: t }) => {
|
|
57
|
+
b && c(e, l(e), t);
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
...e,
|
|
63
|
+
columnSizing: d
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}), [
|
|
67
|
+
_,
|
|
68
|
+
b,
|
|
69
|
+
f,
|
|
70
|
+
x
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { d as useTableColumnSizing };
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=useTableColumnSizing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableColumnSizing.mjs","names":["NO_WIDTHS","useTableColumnSizing","columnWidthsProp","options","detailsRef","useRef","widths","setWidths","isControlled","useControllable","defaultColumnWidths","details","onColumnWidthsChange","next","isSized","resize","useCallback","ids","columnId","width","clamped","clampColumnWidth","getColumnEntry","columnSizingOptions","event","resolveColumnWidths","previous","useMemo","createTableFeaturePlugin","column","state","resolved","total","totalWidth","setColumnWidth","id","initialWidthOf","initialColumnWidth","widthOf","columnSizing"],"sources":["../../../src/components/table/useTableColumnSizing.ts"],"sourcesContent":["import { useCallback, useMemo, useRef } from 'react';\nimport { useControllable } from '../../hooks/useControllable';\nimport {\n clampColumnWidth,\n DEFAULT_MIN_COLUMN_WIDTH,\n getColumnEntry,\n initialColumnWidth,\n resolveColumnWidths,\n totalWidth,\n type ColumnSizingOption,\n} from '../../lib/columnSizing';\nimport {\n createTableFeaturePlugin,\n type TableColumnSizingState,\n type TableFeaturePlugin,\n} from './useTableFeatures';\n\nconst NO_WIDTHS: Readonly<Record<string, number>> = {};\n\n/** What {@link UseTableColumnSizingOptions.onColumnWidthsChange} gets besides the new widths. */\nexport interface TableColumnWidthsChangeDetails {\n /** The column that was resized. */\n columnId: string;\n /** Its new width in pixels, held at or above its `minWidth`. */\n width: number;\n /**\n * The event behind the change (the drag, the key press); when none was passed, an untrusted\n * `resize` event with no target.\n */\n event: Event;\n}\n\n/** Options of {@link useTableColumnSizing}. */\nexport interface UseTableColumnSizingOptions {\n /**\n * Per column, by `columnId`: `minWidth` (`@default 80`), and the width the column starts at,\n * `defaultWidth` or else `idealWidth` (else 150). Ids that are not columns are ignored. May be\n * written inline.\n */\n columnSizingOptions?: Record<string, ColumnSizingOption>;\n /**\n * The column widths in pixels (controlled), by `columnId`: a column without an entry has its\n * initial width. Pair it with `onColumnWidthsChange`.\n */\n columnWidths?: Record<string, number>;\n /** The column widths at first render (uncontrolled). */\n defaultColumnWidths?: Record<string, number>;\n /**\n * Called with the width of every column and the details whenever a width changes (every step of\n * a drag or key press). The details are always passed; they are typed optional through 0.x, as\n * every `details` argument. Setting the width a column already has does not call it, once a\n * width is state.\n */\n onColumnWidthsChange?: (\n widths: Record<string, number>,\n details?: TableColumnWidthsChangeDetails,\n ) => void;\n}\n\n/**\n * The column sizing plugin of `useTableFeatures`: the width of every column, held at or above its\n * minimum, and the props that lay a static `Table` out with them. Fills `state.columnSizing`.\n * Spread `getTableProps()` on the `Table`, render a `<colgroup>` with a `<col>` per column and\n * spread `getColumnProps(columnId)` on it, and give each `Table.ResizeHandle` the\n * `getResizeHandleProps(columnId)`. The widths are controlled with `columnWidths` or uncontrolled\n * with `defaultColumnWidths`. Until a width is state (a resize, or `columnWidths` or\n * `defaultColumnWidths` is given) the table is 100% wide and at least as wide as the columns; from\n * then on it is exactly as wide as they are, so a dragged edge follows the pointer. There is no\n * maximum width, and no fitting of the columns to the container. Call it unconditionally and pass\n * the plugin to `useTableFeatures`.\n *\n * @example\n * const { columnSizing } = useTableFeatures({ items, columns }, [\n * useTableColumnSizing({ columnSizingOptions: { name: { minWidth: 120, defaultWidth: 240 } } }),\n * ]);\n * <Table {...columnSizing.getTableProps()}>\n * <colgroup>\n * {columns.map((c) => <col key={c.columnId} {...columnSizing.getColumnProps(c.columnId!)} />)}\n * </colgroup>\n * </Table>\n */\nexport function useTableColumnSizing<T>(\n options: UseTableColumnSizingOptions = {},\n): TableFeaturePlugin<T> {\n const {\n columnSizingOptions,\n columnWidths: columnWidthsProp,\n defaultColumnWidths,\n onColumnWidthsChange,\n } = options;\n\n // The details of the change being made, set around the setter: useControllable calls its\n // onChange synchronously inside it, and only on change.\n const detailsRef = useRef<TableColumnWidthsChangeDetails | undefined>(undefined);\n const [widths, setWidths, isControlled] = useControllable<Record<string, number>>(\n columnWidthsProp,\n defaultColumnWidths ?? NO_WIDTHS,\n (next) => {\n const details = detailsRef.current;\n if (details) onColumnWidthsChange?.(next, details);\n },\n );\n // Until a width is state, an uncontrolled table without defaults holds `NO_WIDTHS` itself.\n const isSized = isControlled || defaultColumnWidths !== undefined || widths !== NO_WIDTHS;\n\n const resize = useCallback(\n (ids: readonly string[], columnId: string, width: number, event: Event | undefined) => {\n if (!ids.includes(columnId) || !Number.isFinite(width)) return;\n const clamped = clampColumnWidth(width, getColumnEntry(columnSizingOptions, columnId));\n detailsRef.current = { columnId, width: clamped, event: event ?? new Event('resize') };\n try {\n setWidths((previous) => {\n const next = resolveColumnWidths(ids, columnSizingOptions, previous);\n // The first width taken as state is a change even when it equals the initial one (an\n // earlier change of the same event already took one).\n const sized = isSized || previous !== NO_WIDTHS;\n if (sized && next[columnId] === clamped) return previous;\n next[columnId] = clamped;\n return next;\n });\n } finally {\n detailsRef.current = undefined;\n }\n },\n [setWidths, columnSizingOptions, isSized],\n );\n\n return useMemo(\n () =>\n createTableFeaturePlugin<T>({\n feature: 'columnSizing',\n extend: (state) => {\n const ids: string[] = [];\n for (const column of state.columns) {\n if (column.columnId !== undefined) ids.push(column.columnId);\n }\n const resolved = resolveColumnWidths(ids, columnSizingOptions, widths);\n const total = totalWidth(ids, resolved);\n const setColumnWidth: TableColumnSizingState['setColumnWidth'] = (id, width, event) =>\n resize(ids, id, width, event);\n const initialWidthOf = (id: string) =>\n initialColumnWidth(getColumnEntry(columnSizingOptions, id));\n const widthOf = (id: string) => getColumnEntry(resolved, id) ?? initialWidthOf(id);\n const columnSizing: TableColumnSizingState = {\n columnWidths: resolved,\n isSized,\n getColumnWidth: (id) => getColumnEntry(resolved, id),\n setColumnWidth,\n getTableProps: () => ({\n style: isSized\n ? { tableLayout: 'fixed', width: total }\n : { tableLayout: 'fixed', width: '100%', minWidth: total },\n }),\n getColumnProps: (id) => ({ style: { width: widthOf(id) } }),\n getResizeHandleProps: (id) => ({\n width: widthOf(id),\n minWidth:\n getColumnEntry(columnSizingOptions, id)?.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH,\n onResize: (width, details) => setColumnWidth(id, width, details?.event),\n // A table without width state is in its initial layout: nothing to reset.\n onReset: ({ event }) => {\n if (isSized) setColumnWidth(id, initialWidthOf(id), event);\n },\n }),\n };\n return { ...state, columnSizing };\n },\n }),\n [widths, isSized, columnSizingOptions, resize],\n );\n}\n"],"mappings":";;;;;;AAiBA,IAAMA,IAA8C,CAAC;AAgErD,SAAgBC,EACd,IAAuC,CAAC,GACjB;CACvB,IAAM,EACJ,wBACA,cAAcC,GACd,wBACA,4BACEC,GAIEC,IAAaC,EAAmD,KAAA,CAAS,GACzE,CAACC,GAAQC,GAAWC,KAAgBC,EACxCP,GACAQ,KAAuBV,IACtB,MAAS;EACR,IAAMW,IAAUP,EAAW;EAC3B,AAAIO,KAASC,IAAuBC,GAAMF,CAAO;CACnD,CACF,GAEMG,IAAUN,KAAgBE,MAAwB,KAAA,KAAaJ,MAAWN,GAE1Ee,IAASC,GACZ,GAAwB,GAAkB,GAAe,MAA6B;EACrF,IAAI,CAACC,EAAI,SAASC,CAAQ,KAAK,CAAC,OAAO,SAASC,CAAK,GAAG;EACxD,IAAMC,IAAUC,EAAiBF,GAAOG,EAAeC,GAAqBL,CAAQ,CAAC;EACrF,EAAW,UAAU;GAAE;GAAU,OAAOE;GAAS,OAAOI,KAAS,IAAI,MAAM,QAAQ;EAAE;EACrF,IAAI;GACF,GAAW,MAAa;IACtB,IAAMX,IAAOY,EAAoBR,GAAKM,GAAqBG,CAAQ;IAMnE,QAHcZ,KAAWY,MAAa1B,MACzBa,EAAKK,OAAcE,IAAgBM,KAChD,EAAKR,KAAYE,GACVP;GACT,CAAC;EACH,UAAU;GACR,EAAW,UAAU,KAAA;EACvB;CACF,GACA;EAACN;EAAWgB;EAAqBT;CAAO,CAC1C;CAEA,OAAOa,QAEHC,EAA4B;EAC1B,SAAS;EACT,SAAS,MAAU;GACjB,IAAMX,IAAgB,CAAC;GACvB,KAAK,IAAMY,KAAUC,EAAM,SACzB,AAAID,EAAO,aAAa,KAAA,KAAWZ,EAAI,KAAKY,EAAO,QAAQ;GAE7D,IAAME,IAAWN,EAAoBR,GAAKM,GAAqBjB,CAAM,GAC/D0B,IAAQC,EAAWhB,GAAKc,CAAQ,GAChCG,KAA4D,GAAI,GAAO,MAC3EnB,EAAOE,GAAKkB,GAAIhB,GAAOK,CAAK,GACxBY,KAAkB,MACtBC,EAAmBf,EAAeC,GAAqBY,CAAE,CAAC,GACtDG,KAAW,MAAehB,EAAeS,GAAUI,CAAE,KAAKC,EAAeD,CAAE,GAC3EI,IAAuC;IAC3C,cAAcR;IACd;IACA,iBAAiB,MAAOT,EAAeS,GAAUI,CAAE;IACnD;IACA,sBAAsB,EACpB,OAAOrB,IACH;KAAE,aAAa;KAAS,OAAOkB;IAAM,IACrC;KAAE,aAAa;KAAS,OAAO;KAAQ,UAAUA;IAAM,EAC7D;IACA,iBAAiB,OAAQ,EAAE,OAAO,EAAE,OAAOM,EAAQH,CAAE,EAAE,EAAE;IACzD,uBAAuB,OAAQ;KAC7B,OAAOG,EAAQH,CAAE;KACjB,UACEb,EAAeC,GAAqBY,CAAE,CAAC,EAAE,YAAA;KAC3C,WAAW,GAAO,MAAYD,EAAeC,GAAIhB,GAAOR,GAAS,KAAK;KAEtE,UAAU,EAAE,eAAY;MACtB,AAAIG,KAASoB,EAAeC,GAAIC,EAAeD,CAAE,GAAGX,CAAK;KAC3D;IACF;GACF;GACA,OAAO;IAAE,GAAGM;IAAO;GAAa;EAClC;CACF,CAAC,GACH;EAACxB;EAAQQ;EAASS;EAAqBR;CAAM,CAC/C;AACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("../../lib/dev.cjs");let t=require("react");function n(e){return e}function r(e){return e}var i=[],a=new Set,o=()=>{},s=(e,t)=>String(t),c={value:null,setColumnSort:o,toggleColumnSort:o,getSortDirection:()=>void 0},l={selectionMode:`multiple`,selectedItems:a,allRowsSelected:!1,someRowsSelected:!1,isRowSelected:()=>!1,toggleRow:o,selectRow:o,deselectRow:o,toggleAllRows:o,clearRows:o},u={columnWidths:{},isSized:!1,getColumnWidth:()=>void 0,setColumnWidth:o,getTableProps:()=>({style:{}}),getColumnProps:()=>({style:{}}),getResizeHandleProps:()=>({width:0,minWidth:0,onResize:o})};function d(e,t){for(let n=e.length-1;n>=0;--n){let i=r(e[n]);if(i.feature===t)return i}}function f(e){let t=new Set;for(let n of e){let{feature:e}=r(n);if(t.has(e))return e;t.add(e)}}function p(n,r=i){let{items:a,columns:o,getRowId:p=s}=n,m=d(r,`sort`),h=d(r,`selection`),g=d(r,`columnSizing`),_=m?.sortRows,v=(0,t.useMemo)(()=>a.map((e,t)=>({item:e,rowId:p(e,t),index:t})),[a,p]),y=(0,t.useMemo)(()=>_?_(v,o):v,[v,o,_]),b=(0,t.useCallback)(()=>y,[y]),x=(0,t.useMemo)(()=>{let e={items:a,columns:o,getRowId:p,getRows:b,sort:c,selection:l,columnSizing:u};for(let t of[m,h,g])t&&(e=t.extend(e));return e},[a,o,p,b,m,h,g]),S=f(r);(0,t.useEffect)(()=>{S!==void 0&&e.warnOnce(`useTableFeatures:duplicate-plugin`,`useTableFeatures: more than one plugin fills \`${S}\`. Pass each feature once; the last plugin of a feature is used.`)},[S]);let C=r.length,w=(0,t.useRef)(C);return(0,t.useEffect)(()=>{let t=w.current;w.current=C,t!==C&&e.warnOnce(`useTableFeatures:plugin-count`,`useTableFeatures: the number of plugins changed between renders (from ${t} to ${C}). Pass the same plugins on every render and change their options instead; the new plugins are applied.`)},[C]),(0,t.useEffect)(()=>{if(!e.isDev)return;let t=new Set;for(let{rowId:n}of v){if(t.has(n)){e.warnOnce(`useTableFeatures:duplicate-row-id`,`useTableFeatures: more than one item has the row id "${n}". Row ids must be unique: give getRowId an id that differs for each item.`);return}t.add(n)}},[v]),x}exports.createTableFeaturePlugin=n,exports.useTableFeatures=p;
|
|
2
|
+
//# sourceMappingURL=useTableFeatures.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTableFeatures.cjs","names":["useMemo","useCallback","useRef","isDev","require_dev","react","createTableFeaturePlugin","implementation","implementationOf","plugin","NO_PLUGINS","NO_SELECTED_IDS","doNothing","indexRowId","index","INERT_SORT","INERT_SELECTION","INERT_COLUMN_SIZING","pluginFor","plugins","feature","duplicateFeatureOf","seen","useTableFeatures","options","sortPlugin","selectionPlugin","columnSizingPlugin","sortRows","baseRows","items","getRowId","item","rows","columns","getRows","state","next","duplicateFeature","pluginCount","pluginCountRef","previous","rowId"],"sources":["../../../src/components/table/useTableFeatures.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport type { CSSProperties } from 'react';\nimport { isDev, warnOnce } from '../../lib/dev';\nimport type { SelectionMode } from '../../lib/types';\nimport type { DataGridColumn, DataGridSort, SortDirection } from './DataGrid';\n\n/** One row of {@link useTableFeatures}: an item of `items` with its id. */\nexport interface TableRowData<T> {\n /** The item. */\n item: T;\n /** The row's id, from `getRowId` (by default the index in `items`, as a string). */\n rowId: string;\n /** The item's index in `items`. Sorting moves the row, not this index. */\n index: number;\n}\n\n/** The sort of {@link useTableFeatures} (`state.sort`), filled by `useTableSort`. */\nexport interface TableSortState {\n /** The sorted column and its direction, or `null` while the rows are in the order of `items`. */\n value: DataGridSort | null;\n /** Sorts by the column `columnId` in `direction`. */\n setColumnSort(columnId: string, direction: SortDirection, event?: Event): void;\n /**\n * Sorts by the column `columnId`, as a sortable header of DataGrid does: ascending, or descending\n * when the column is already sorted ascending.\n */\n toggleColumnSort(columnId: string, event?: Event): void;\n /** The direction of the column `columnId` while the rows are sorted by it, else `undefined`. */\n getSortDirection(columnId: string): SortDirection | undefined;\n}\n\n/**\n * The selection of {@link useTableFeatures} (`state.selection`), filled by `useTableSelection`. The\n * selection is a set of row ids held apart from the rendered rows: rows that are not rendered keep\n * their state, and ids whose item is not in `items` (filtered out, on another page) stay selected.\n */\nexport interface TableSelectionState {\n /** `single` keeps at most one row selected; `multiple` selects rows independently. */\n selectionMode: SelectionMode;\n /** The selected row ids, ids whose item is not in `items` included. */\n selectedItems: ReadonlySet<string>;\n /**\n * Whether every selectable row of `items` is selected (and there is one): rows that\n * `isDisabled` marks do not count, rows that are not rendered do.\n */\n allRowsSelected: boolean;\n /**\n * Whether a row of `items` is selected while not every selectable one is (the mixed state of a\n * select-all checkbox). A selected disabled row counts; ids outside `items` do not.\n */\n someRowsSelected: boolean;\n /** Whether the row `id` is selected. */\n isRowSelected(id: string): boolean;\n /** Selects or deselects the row `id` (`multiple`); selects it (`single`). */\n toggleRow(id: string, event?: Event): void;\n /** Selects the row `id`; in `single` mode it replaces the selection. */\n selectRow(id: string, event?: Event): void;\n /** Deselects the row `id`. */\n deselectRow(id: string, event?: Event): void;\n /**\n * Selects every selectable row of `items` when not all of them are selected, else deselects\n * them. A disabled row keeps its state, and ids outside `items` stay selected. It does nothing in\n * `single` mode.\n */\n toggleAllRows(event?: Event): void;\n /** Empties the selection, ids outside `items` included. */\n clearRows(event?: Event): void;\n}\n\n/**\n * What a resize handle needs from the table state, as returned by\n * `columnSizing.getResizeHandleProps(columnId)`: the column's width and minimum, the callback\n * that sets a new width and the one that resets it. `Table.ResizeHandle` takes these props (and a few more).\n */\nexport interface TableResizeHandleBindings {\n /** The column's current width in pixels. */\n width: number;\n /** The narrowest the column can be, in pixels. */\n minWidth: number;\n /**\n * Sets the column's width in pixels (held at or above `minWidth`); `details.event` is the event\n * behind it (without `details`, an untrusted `resize` event is reported).\n */\n onResize(width: number, details?: { event: Event }): void;\n /**\n * Sets the column back to its initial width (a double click on the handle). It changes nothing\n * while no width is state, since the table is then in its initial layout.\n */\n onReset?(details: { event: Event }): void;\n}\n\n/**\n * The column widths of {@link useTableFeatures} (`state.columnSizing`), filled by\n * `useTableColumnSizing`. Without that plugin no width is set and the functions do nothing.\n */\nexport interface TableColumnSizingState {\n /**\n * The width of every column in pixels, by `columnId`: the width that is state, else the\n * column's initial width.\n */\n columnWidths: Readonly<Record<string, number>>;\n /** Whether a width is state (set, controlled or given as a default). */\n isSized: boolean;\n /** The width of the column `columnId` in pixels, or `undefined` when it is no column. */\n getColumnWidth(columnId: string): number | undefined;\n /**\n * Sets the width of the column `columnId` in pixels. Without `event`, the change's details carry\n * an untrusted `resize` event with no target.\n */\n setColumnWidth(columnId: string, width: number, event?: Event): void;\n /** Props for the table element: its fixed layout and width style. */\n getTableProps(): { style: CSSProperties };\n /** Props for the column's `<col>` element: its width style. */\n getColumnProps(columnId: string): { style: CSSProperties };\n /** Props for the resize handle of the column `columnId`. */\n getResizeHandleProps(columnId: string): TableResizeHandleBindings;\n}\n\n/** The state that {@link useTableFeatures} returns. */\nexport interface TableFeaturesState<T> {\n /** The items, as given. */\n items: readonly T[];\n /** The columns, as given. */\n columns: readonly DataGridColumn<T>[];\n /** The row id of an item: `getRowId` as given, else the index as a string. */\n getRowId: (item: T, index: number) => string;\n /**\n * The rows in display order: sorted while `useTableSort` sorts them, else in the order of\n * `items`. The same array is returned until `items`, `getRowId`, `columns` or the sort change.\n */\n getRows(): readonly TableRowData<T>[];\n /** The sort; without `useTableSort` it is `null` and its functions do nothing. */\n sort: TableSortState;\n /** The selection; without `useTableSelection` nothing is selected and its functions do nothing. */\n selection: TableSelectionState;\n /** The column widths; without `useTableColumnSizing` no width is set. */\n columnSizing: TableColumnSizingState;\n}\n\n/** Options of {@link useTableFeatures}. */\nexport interface UseTableFeaturesOptions<T> {\n /** The items, one row each. Every item counts for select-all, rendered or not. */\n items: readonly T[];\n /** The columns, from `createTableColumn`. The sort uses the `compare` of the sorted column. */\n columns: readonly DataGridColumn<T>[];\n /**\n * The row id of an item: unique, and the same for an item across renders (selection and the\n * selection callbacks use it). Pass a stable function (defined outside the component, or\n * memoised): the rows are rebuilt when it changes.\n * @default (item, index) => String(index)\n */\n getRowId?: (item: T, index: number) => string;\n}\n\ndeclare const tableFeaturePluginBrand: unique symbol;\n\n/**\n * A feature of {@link useTableFeatures}, returned by `useTableSort`, `useTableSelection` and\n * `useTableColumnSizing`, which hold the feature's state where they are called. The object is\n * opaque. It keeps its identity while the feature's state and the options it reads keep theirs; a\n * new one costs `useTableFeatures` a recompute of its state, and of the rows only when the sort\n * changed. `T` is the item type the plugin reads (a selection's `isDisabled`): a plugin that reads\n * no item, such as the sort, fits every table.\n */\nexport interface TableFeaturePlugin<T> {\n /** The brand of a plugin: only the plugin hooks create one. */\n readonly [tableFeaturePluginBrand]: (item: T) => void;\n}\n\n/** The slot of the state that a plugin fills. */\ntype TableFeatureName = 'sort' | 'selection' | 'columnSizing';\n\n/**\n * What a plugin does: the run-time content of a {@link TableFeaturePlugin}, read only by\n * {@link useTableFeatures} (module-only).\n */\nexport interface TableFeaturePluginImplementation<T> {\n /** The slot it fills; a second plugin for the same slot warns. */\n feature: TableFeatureName;\n /**\n * Puts the rows in display order (the sort plugin only). The rows are rebuilt when this function\n * changes, so it keeps its identity while the order it gives cannot change, whatever happens to\n * the plugin object.\n */\n sortRows?: (\n rows: readonly TableRowData<T>[],\n columns: readonly DataGridColumn<T>[],\n ) => readonly TableRowData<T>[];\n /** Returns the state with the plugin's slot filled. Pure. */\n extend: (state: TableFeaturesState<T>) => TableFeaturesState<T>;\n}\n\n/** Wraps a plugin's implementation as the opaque plugin (module-only, for the plugin hooks). */\nexport function createTableFeaturePlugin<T>(\n implementation: TableFeaturePluginImplementation<T>,\n): TableFeaturePlugin<T> {\n // The brand is a type only: the plugin is its implementation object.\n return implementation as unknown as TableFeaturePlugin<T>;\n}\n\nfunction implementationOf<T>(plugin: TableFeaturePlugin<T>): TableFeaturePluginImplementation<T> {\n return plugin as unknown as TableFeaturePluginImplementation<T>;\n}\n\nconst NO_PLUGINS: readonly never[] = [];\n\nconst NO_SELECTED_IDS: ReadonlySet<string> = new Set();\n\nconst doNothing = (): void => {};\n\nconst indexRowId = (_item: unknown, index: number): string => String(index);\n\nconst INERT_SORT: TableSortState = {\n value: null,\n setColumnSort: doNothing,\n toggleColumnSort: doNothing,\n getSortDirection: () => undefined,\n};\n\nconst INERT_SELECTION: TableSelectionState = {\n selectionMode: 'multiple',\n selectedItems: NO_SELECTED_IDS,\n allRowsSelected: false,\n someRowsSelected: false,\n isRowSelected: () => false,\n toggleRow: doNothing,\n selectRow: doNothing,\n deselectRow: doNothing,\n toggleAllRows: doNothing,\n clearRows: doNothing,\n};\n\nconst INERT_COLUMN_SIZING: TableColumnSizingState = {\n columnWidths: {},\n isSized: false,\n getColumnWidth: () => undefined,\n setColumnWidth: doNothing,\n getTableProps: () => ({ style: {} }),\n getColumnProps: () => ({ style: {} }),\n getResizeHandleProps: () => ({ width: 0, minWidth: 0, onResize: doNothing }),\n};\n\n/** The last plugin of `plugins` that fills `feature` (a feature given twice warns). */\nfunction pluginFor<T>(\n plugins: readonly TableFeaturePlugin<T>[],\n feature: TableFeatureName,\n): TableFeaturePluginImplementation<T> | undefined {\n for (let index = plugins.length - 1; index >= 0; index -= 1) {\n const implementation = implementationOf(plugins[index]!);\n if (implementation.feature === feature) return implementation;\n }\n return undefined;\n}\n\nfunction duplicateFeatureOf<T>(plugins: readonly TableFeaturePlugin<T>[]): string | undefined {\n const seen = new Set<TableFeatureName>();\n for (const plugin of plugins) {\n const { feature } = implementationOf(plugin);\n if (seen.has(feature)) return feature;\n seen.add(feature);\n }\n return undefined;\n}\n\n/**\n * Headless table state for a table built from the static `Table` parts: the rows of `items`, and\n * the features its plugins add (`useTableSort`, `useTableSelection`, `useTableColumnSizing`). Each\n * plugin fills its own part of the state (`sort`, `selection`, `columnSizing`), so the order of\n * the array does not matter. The state follows the data, not the rendered rows, so a table that\n * renders a page or a window of its rows keeps select-all and the sort over every item.\n *\n * Call each plugin hook unconditionally, in the array or before it, and pass the same plugins on\n * every render; to turn a feature off, change its options. Options may be written inline: a plugin\n * whose options change identity on every render (an inline `isDisabled`) only makes the state be\n * recomputed on every render, while `getRows()` keeps its array until `items`, `getRowId`,\n * `columns` or the sort change. A feature given twice (the last plugin of it is used), a plugin\n * count that changes between renders and repeated row ids are development warnings.\n *\n * @example\n * const { getRows, sort, selection } = useTableFeatures({ items, columns, getRowId }, [\n * useTableSort({ defaultSort: { columnId: 'name', direction: 'ascending' } }),\n * useTableSelection({ selectionMode: 'multiple' }),\n * ]);\n */\nexport function useTableFeatures<T>(\n options: UseTableFeaturesOptions<T>,\n plugins: readonly TableFeaturePlugin<T>[] = NO_PLUGINS,\n): TableFeaturesState<T> {\n const { items, columns, getRowId = indexRowId } = options;\n\n // One variable per feature, so the memos below have fixed dependencies: a new array literal on\n // every render changes nothing, and a plugin that changes identity (an inline option) costs a\n // recompute of the state, never a state update during render. The rows depend on the sort's\n // `sortRows` alone, which the sort plugin keeps while the sort is the same.\n const sortPlugin = pluginFor(plugins, 'sort');\n const selectionPlugin = pluginFor(plugins, 'selection');\n const columnSizingPlugin = pluginFor(plugins, 'columnSizing');\n const sortRows = sortPlugin?.sortRows;\n\n const baseRows = useMemo(\n () => items.map((item, index) => ({ item, rowId: getRowId(item, index), index })),\n [items, getRowId],\n );\n\n const rows = useMemo(\n () => (sortRows ? sortRows(baseRows, columns) : baseRows),\n [baseRows, columns, sortRows],\n );\n\n const getRows = useCallback(() => rows, [rows]);\n\n const state = useMemo(() => {\n let next: TableFeaturesState<T> = {\n items,\n columns,\n getRowId,\n getRows,\n sort: INERT_SORT,\n selection: INERT_SELECTION,\n columnSizing: INERT_COLUMN_SIZING,\n };\n for (const plugin of [sortPlugin, selectionPlugin, columnSizingPlugin]) {\n if (plugin) next = plugin.extend(next);\n }\n return next;\n }, [items, columns, getRowId, getRows, sortPlugin, selectionPlugin, columnSizingPlugin]);\n\n const duplicateFeature = duplicateFeatureOf(plugins);\n useEffect(() => {\n if (duplicateFeature === undefined) return;\n warnOnce(\n 'useTableFeatures:duplicate-plugin',\n `useTableFeatures: more than one plugin fills \\`${duplicateFeature}\\`. Pass each feature ` +\n 'once; the last plugin of a feature is used.',\n );\n }, [duplicateFeature]);\n\n const pluginCount = plugins.length;\n const pluginCountRef = useRef(pluginCount);\n useEffect(() => {\n const previous = pluginCountRef.current;\n pluginCountRef.current = pluginCount;\n if (previous === pluginCount) return;\n warnOnce(\n 'useTableFeatures:plugin-count',\n `useTableFeatures: the number of plugins changed between renders (from ${previous} to ` +\n `${pluginCount}). Pass the same plugins on every render and change their options ` +\n 'instead; the new plugins are applied.',\n );\n }, [pluginCount]);\n\n useEffect(() => {\n if (!isDev) return;\n const seen = new Set<string>();\n for (const { rowId } of baseRows) {\n if (seen.has(rowId)) {\n warnOnce(\n 'useTableFeatures:duplicate-row-id',\n `useTableFeatures: more than one item has the row id \"${rowId}\". Row ids must be ` +\n 'unique: give getRowId an id that differs for each item.',\n );\n return;\n }\n seen.add(rowId);\n }\n }, [baseRows]);\n\n return state;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { isDev as e, warnOnce as t } from "../../lib/dev.mjs";
|
|
3
|
+
import { useCallback as n, useEffect as r, useMemo as i, useRef as a } from "react";
|
|
4
|
+
//#region src/components/table/useTableFeatures.ts
|
|
5
|
+
function o(e) {
|
|
6
|
+
return e;
|
|
7
|
+
}
|
|
8
|
+
function s(e) {
|
|
9
|
+
return e;
|
|
10
|
+
}
|
|
11
|
+
var c = [], l = /* @__PURE__ */ new Set(), u = () => {}, d = (e, t) => String(t), f = {
|
|
12
|
+
value: null,
|
|
13
|
+
setColumnSort: u,
|
|
14
|
+
toggleColumnSort: u,
|
|
15
|
+
getSortDirection: () => void 0
|
|
16
|
+
}, p = {
|
|
17
|
+
selectionMode: "multiple",
|
|
18
|
+
selectedItems: l,
|
|
19
|
+
allRowsSelected: !1,
|
|
20
|
+
someRowsSelected: !1,
|
|
21
|
+
isRowSelected: () => !1,
|
|
22
|
+
toggleRow: u,
|
|
23
|
+
selectRow: u,
|
|
24
|
+
deselectRow: u,
|
|
25
|
+
toggleAllRows: u,
|
|
26
|
+
clearRows: u
|
|
27
|
+
}, m = {
|
|
28
|
+
columnWidths: {},
|
|
29
|
+
isSized: !1,
|
|
30
|
+
getColumnWidth: () => void 0,
|
|
31
|
+
setColumnWidth: u,
|
|
32
|
+
getTableProps: () => ({ style: {} }),
|
|
33
|
+
getColumnProps: () => ({ style: {} }),
|
|
34
|
+
getResizeHandleProps: () => ({
|
|
35
|
+
width: 0,
|
|
36
|
+
minWidth: 0,
|
|
37
|
+
onResize: u
|
|
38
|
+
})
|
|
39
|
+
};
|
|
40
|
+
function h(e, t) {
|
|
41
|
+
for (let n = e.length - 1; n >= 0; --n) {
|
|
42
|
+
let r = s(e[n]);
|
|
43
|
+
if (r.feature === t) return r;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function g(e) {
|
|
47
|
+
let t = /* @__PURE__ */ new Set();
|
|
48
|
+
for (let n of e) {
|
|
49
|
+
let { feature: e } = s(n);
|
|
50
|
+
if (t.has(e)) return e;
|
|
51
|
+
t.add(e);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function _(o, s = c) {
|
|
55
|
+
let { items: l, columns: u, getRowId: _ = d } = o, v = h(s, "sort"), y = h(s, "selection"), b = h(s, "columnSizing"), x = v?.sortRows, S = i(() => l.map((e, t) => ({
|
|
56
|
+
item: e,
|
|
57
|
+
rowId: _(e, t),
|
|
58
|
+
index: t
|
|
59
|
+
})), [l, _]), C = i(() => x ? x(S, u) : S, [
|
|
60
|
+
S,
|
|
61
|
+
u,
|
|
62
|
+
x
|
|
63
|
+
]), w = n(() => C, [C]), T = i(() => {
|
|
64
|
+
let e = {
|
|
65
|
+
items: l,
|
|
66
|
+
columns: u,
|
|
67
|
+
getRowId: _,
|
|
68
|
+
getRows: w,
|
|
69
|
+
sort: f,
|
|
70
|
+
selection: p,
|
|
71
|
+
columnSizing: m
|
|
72
|
+
};
|
|
73
|
+
for (let t of [
|
|
74
|
+
v,
|
|
75
|
+
y,
|
|
76
|
+
b
|
|
77
|
+
]) t && (e = t.extend(e));
|
|
78
|
+
return e;
|
|
79
|
+
}, [
|
|
80
|
+
l,
|
|
81
|
+
u,
|
|
82
|
+
_,
|
|
83
|
+
w,
|
|
84
|
+
v,
|
|
85
|
+
y,
|
|
86
|
+
b
|
|
87
|
+
]), E = g(s);
|
|
88
|
+
r(() => {
|
|
89
|
+
E !== void 0 && t("useTableFeatures:duplicate-plugin", `useTableFeatures: more than one plugin fills \`${E}\`. Pass each feature once; the last plugin of a feature is used.`);
|
|
90
|
+
}, [E]);
|
|
91
|
+
let D = s.length, O = a(D);
|
|
92
|
+
return r(() => {
|
|
93
|
+
let e = O.current;
|
|
94
|
+
O.current = D, e !== D && t("useTableFeatures:plugin-count", `useTableFeatures: the number of plugins changed between renders (from ${e} to ${D}). Pass the same plugins on every render and change their options instead; the new plugins are applied.`);
|
|
95
|
+
}, [D]), r(() => {
|
|
96
|
+
if (!e) return;
|
|
97
|
+
let n = /* @__PURE__ */ new Set();
|
|
98
|
+
for (let { rowId: e } of S) {
|
|
99
|
+
if (n.has(e)) {
|
|
100
|
+
t("useTableFeatures:duplicate-row-id", `useTableFeatures: more than one item has the row id "${e}". Row ids must be unique: give getRowId an id that differs for each item.`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
n.add(e);
|
|
104
|
+
}
|
|
105
|
+
}, [S]), T;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { o as createTableFeaturePlugin, _ as useTableFeatures };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=useTableFeatures.mjs.map
|