@navikt/ds-react 8.10.5 → 8.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/cjs/data/data-grid/index.d.ts +2 -0
  2. package/cjs/data/data-grid/index.js +7 -0
  3. package/cjs/data/data-grid/index.js.map +1 -0
  4. package/cjs/data/data-grid/root/DataGridRoot.context.d.ts +11 -0
  5. package/cjs/data/data-grid/root/DataGridRoot.context.js +11 -0
  6. package/cjs/data/data-grid/root/DataGridRoot.context.js.map +1 -0
  7. package/cjs/data/data-grid/root/DataGridRoot.d.ts +38 -0
  8. package/cjs/data/data-grid/root/DataGridRoot.js +68 -0
  9. package/cjs/data/data-grid/root/DataGridRoot.js.map +1 -0
  10. package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +0 -1
  11. package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
  12. package/cjs/data/drag-and-drop/root/DragAndDropRoot.d.ts +5 -5
  13. package/cjs/data/drag-and-drop/root/DragAndDropRoot.js +4 -27
  14. package/cjs/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
  15. package/cjs/data/stories/Data.test-data.d.ts +2 -5
  16. package/cjs/data/stories/Data.test-data.js +30 -38
  17. package/cjs/data/stories/Data.test-data.js.map +1 -1
  18. package/cjs/data/table/base-cell/DataTableBaseCell.d.ts +15 -15
  19. package/cjs/data/table/base-cell/DataTableBaseCell.js +4 -8
  20. package/cjs/data/table/base-cell/DataTableBaseCell.js.map +1 -1
  21. package/cjs/data/table/column-header/DataTableColumnHeader.d.ts +24 -6
  22. package/cjs/data/table/column-header/DataTableColumnHeader.js +22 -27
  23. package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  24. package/cjs/data/table/column-header/useTableColumnResize.d.ts +19 -29
  25. package/cjs/data/table/column-header/useTableColumnResize.js +24 -22
  26. package/cjs/data/table/column-header/useTableColumnResize.js.map +1 -1
  27. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +1 -1
  28. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js +2 -2
  29. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -1
  30. package/cjs/data/table/helpers/collectTableRowEntries.d.ts +2 -2
  31. package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +13 -11
  32. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +43 -53
  33. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  34. package/cjs/data/table/helpers/selection/getSingleSelectProps.d.ts +9 -8
  35. package/cjs/data/table/helpers/selection/getSingleSelectProps.js +23 -10
  36. package/cjs/data/table/helpers/selection/getSingleSelectProps.js.map +1 -1
  37. package/cjs/data/table/helpers/selection/selection.types.d.ts +19 -19
  38. package/cjs/data/table/helpers/selection/selection.utils.d.ts +21 -0
  39. package/cjs/data/table/helpers/selection/selection.utils.js +46 -0
  40. package/cjs/data/table/helpers/selection/selection.utils.js.map +1 -0
  41. package/cjs/data/table/hooks/useColumnOptions.d.ts +16 -5
  42. package/cjs/data/table/hooks/useColumnOptions.js +26 -8
  43. package/cjs/data/table/hooks/useColumnOptions.js.map +1 -1
  44. package/cjs/data/table/hooks/useTableDetailsPanel.d.ts +10 -13
  45. package/cjs/data/table/hooks/useTableDetailsPanel.js +6 -5
  46. package/cjs/data/table/hooks/useTableDetailsPanel.js.map +1 -1
  47. package/cjs/data/table/hooks/useTableItems.d.ts +29 -15
  48. package/cjs/data/table/hooks/useTableItems.js +2 -12
  49. package/cjs/data/table/hooks/useTableItems.js.map +1 -1
  50. package/cjs/data/table/hooks/useTableKeyboardNav.d.ts +1 -6
  51. package/cjs/data/table/hooks/useTableKeyboardNav.js +1 -4
  52. package/cjs/data/table/hooks/useTableKeyboardNav.js.map +1 -1
  53. package/cjs/data/table/hooks/useTableSelection.d.ts +6 -6
  54. package/cjs/data/table/hooks/useTableSelection.js +13 -13
  55. package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
  56. package/cjs/data/table/hooks/useTableSort.d.ts +2 -2
  57. package/cjs/data/table/hooks/useTableSort.js +4 -5
  58. package/cjs/data/table/hooks/useTableSort.js.map +1 -1
  59. package/cjs/data/table/root/DataTable.types.d.ts +22 -10
  60. package/cjs/data/table/root/DataTableRoot.context.d.ts +13 -7
  61. package/cjs/data/table/root/DataTableRoot.context.js.map +1 -1
  62. package/cjs/data/table/root/DataTableRoot.d.ts +49 -72
  63. package/cjs/data/table/root/DataTableRoot.js +54 -66
  64. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  65. package/cjs/data/table/root/DataTableRoot.legacy.d.ts +2 -7
  66. package/cjs/data/table/root/DataTableRoot.legacy.js +17 -3
  67. package/cjs/data/table/root/DataTableRoot.legacy.js.map +1 -1
  68. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js +4 -4
  69. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -1
  70. package/cjs/data/table/tbody/DataTableTbody.js +4 -2
  71. package/cjs/data/table/tbody/DataTableTbody.js.map +1 -1
  72. package/cjs/data/table/tr/DataTableTr.d.ts +5 -3
  73. package/cjs/data/table/tr/DataTableTr.js +36 -23
  74. package/cjs/data/table/tr/DataTableTr.js.map +1 -1
  75. package/cjs/table/ColumnHeader.js +2 -1
  76. package/cjs/table/ColumnHeader.js.map +1 -1
  77. package/esm/data/data-grid/index.d.ts +2 -0
  78. package/esm/data/data-grid/index.js +3 -0
  79. package/esm/data/data-grid/index.js.map +1 -0
  80. package/esm/data/data-grid/root/DataGridRoot.context.d.ts +11 -0
  81. package/esm/data/data-grid/root/DataGridRoot.context.js +7 -0
  82. package/esm/data/data-grid/root/DataGridRoot.context.js.map +1 -0
  83. package/esm/data/data-grid/root/DataGridRoot.d.ts +38 -0
  84. package/esm/data/data-grid/root/DataGridRoot.js +32 -0
  85. package/esm/data/data-grid/root/DataGridRoot.js.map +1 -0
  86. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +0 -1
  87. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
  88. package/esm/data/drag-and-drop/root/DragAndDropRoot.d.ts +5 -5
  89. package/esm/data/drag-and-drop/root/DragAndDropRoot.js +4 -27
  90. package/esm/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
  91. package/esm/data/stories/Data.test-data.d.ts +2 -5
  92. package/esm/data/stories/Data.test-data.js +30 -38
  93. package/esm/data/stories/Data.test-data.js.map +1 -1
  94. package/esm/data/table/base-cell/DataTableBaseCell.d.ts +15 -15
  95. package/esm/data/table/base-cell/DataTableBaseCell.js +4 -8
  96. package/esm/data/table/base-cell/DataTableBaseCell.js.map +1 -1
  97. package/esm/data/table/column-header/DataTableColumnHeader.d.ts +24 -6
  98. package/esm/data/table/column-header/DataTableColumnHeader.js +23 -28
  99. package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  100. package/esm/data/table/column-header/useTableColumnResize.d.ts +19 -29
  101. package/esm/data/table/column-header/useTableColumnResize.js +24 -22
  102. package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
  103. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +1 -1
  104. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js +2 -2
  105. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -1
  106. package/esm/data/table/helpers/collectTableRowEntries.d.ts +2 -2
  107. package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +13 -11
  108. package/esm/data/table/helpers/selection/getMultipleSelectProps.js +43 -53
  109. package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  110. package/esm/data/table/helpers/selection/getSingleSelectProps.d.ts +9 -8
  111. package/esm/data/table/helpers/selection/getSingleSelectProps.js +23 -10
  112. package/esm/data/table/helpers/selection/getSingleSelectProps.js.map +1 -1
  113. package/esm/data/table/helpers/selection/selection.types.d.ts +19 -19
  114. package/esm/data/table/helpers/selection/selection.utils.d.ts +21 -0
  115. package/esm/data/table/helpers/selection/selection.utils.js +43 -0
  116. package/esm/data/table/helpers/selection/selection.utils.js.map +1 -0
  117. package/esm/data/table/hooks/useColumnOptions.d.ts +16 -5
  118. package/esm/data/table/hooks/useColumnOptions.js +26 -8
  119. package/esm/data/table/hooks/useColumnOptions.js.map +1 -1
  120. package/esm/data/table/hooks/useTableDetailsPanel.d.ts +10 -13
  121. package/esm/data/table/hooks/useTableDetailsPanel.js +6 -5
  122. package/esm/data/table/hooks/useTableDetailsPanel.js.map +1 -1
  123. package/esm/data/table/hooks/useTableItems.d.ts +29 -15
  124. package/esm/data/table/hooks/useTableItems.js +3 -10
  125. package/esm/data/table/hooks/useTableItems.js.map +1 -1
  126. package/esm/data/table/hooks/useTableKeyboardNav.d.ts +1 -6
  127. package/esm/data/table/hooks/useTableKeyboardNav.js +1 -4
  128. package/esm/data/table/hooks/useTableKeyboardNav.js.map +1 -1
  129. package/esm/data/table/hooks/useTableSelection.d.ts +6 -6
  130. package/esm/data/table/hooks/useTableSelection.js +13 -13
  131. package/esm/data/table/hooks/useTableSelection.js.map +1 -1
  132. package/esm/data/table/hooks/useTableSort.d.ts +2 -2
  133. package/esm/data/table/hooks/useTableSort.js +4 -5
  134. package/esm/data/table/hooks/useTableSort.js.map +1 -1
  135. package/esm/data/table/root/DataTable.types.d.ts +22 -10
  136. package/esm/data/table/root/DataTableRoot.context.d.ts +13 -7
  137. package/esm/data/table/root/DataTableRoot.context.js.map +1 -1
  138. package/esm/data/table/root/DataTableRoot.d.ts +49 -72
  139. package/esm/data/table/root/DataTableRoot.js +56 -68
  140. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  141. package/esm/data/table/root/DataTableRoot.legacy.d.ts +2 -7
  142. package/esm/data/table/root/DataTableRoot.legacy.js +17 -3
  143. package/esm/data/table/root/DataTableRoot.legacy.js.map +1 -1
  144. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js +4 -4
  145. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -1
  146. package/esm/data/table/tbody/DataTableTbody.js +4 -2
  147. package/esm/data/table/tbody/DataTableTbody.js.map +1 -1
  148. package/esm/data/table/tr/DataTableTr.d.ts +5 -3
  149. package/esm/data/table/tr/DataTableTr.js +35 -23
  150. package/esm/data/table/tr/DataTableTr.js.map +1 -1
  151. package/esm/table/ColumnHeader.js +2 -1
  152. package/esm/table/ColumnHeader.js.map +1 -1
  153. package/package.json +3 -3
  154. package/src/data/data-grid/index.ts +3 -0
  155. package/src/data/data-grid/root/DataGridRoot.context.ts +16 -0
  156. package/src/data/data-grid/root/DataGridRoot.tsx +71 -0
  157. package/src/data/drag-and-drop/drag-handler/DragAndDropDragHandler.tsx +0 -1
  158. package/src/data/drag-and-drop/root/DragAndDropRoot.tsx +15 -49
  159. package/src/data/stories/Data.test-data.tsx +52 -43
  160. package/src/data/table/agent-component-review.md +175 -0
  161. package/src/data/table/base-cell/DataTableBaseCell.tsx +31 -21
  162. package/src/data/table/column-header/DataTableColumnHeader.tsx +61 -58
  163. package/src/data/table/column-header/useTableColumnResize.ts +55 -71
  164. package/src/data/table/details-panel-row/DataTableDetailsPanelRow.tsx +3 -3
  165. package/src/data/table/helpers/collectTableRowEntries.ts +1 -2
  166. package/src/data/table/helpers/selection/getMultipleSelectProps.ts +65 -85
  167. package/src/data/table/helpers/selection/getSingleSelectProps.ts +35 -17
  168. package/src/data/table/helpers/selection/selection.types.ts +19 -19
  169. package/src/data/table/helpers/selection/selection.utils.test.ts +161 -0
  170. package/src/data/table/helpers/selection/selection.utils.ts +73 -0
  171. package/src/data/table/hooks/__tests__/useTableItems.test.ts +2 -1
  172. package/src/data/table/hooks/useColumnOptions.ts +48 -14
  173. package/src/data/table/hooks/useTableDetailsPanel.tsx +22 -25
  174. package/src/data/table/hooks/useTableItems.ts +32 -24
  175. package/src/data/table/hooks/useTableKeyboardNav.ts +1 -13
  176. package/src/data/table/hooks/useTableSelection.ts +26 -31
  177. package/src/data/table/hooks/useTableSort.ts +10 -9
  178. package/src/data/table/root/DataTable.types.ts +30 -22
  179. package/src/data/table/root/DataTableRoot.context.ts +19 -7
  180. package/src/data/table/root/DataTableRoot.legacy.tsx +22 -14
  181. package/src/data/table/root/DataTableRoot.tsx +244 -293
  182. package/src/data/table/sub-row-toggle/DataTableSubRowToggle.tsx +5 -4
  183. package/src/data/table/tbody/DataTableTbody.tsx +6 -2
  184. package/src/data/table/tr/DataTableTr.tsx +98 -35
  185. package/src/table/ColumnHeader.tsx +2 -1
  186. package/cjs/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.d.ts +0 -22
  187. package/cjs/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.js +0 -35
  188. package/cjs/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.js.map +0 -1
  189. package/cjs/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.d.ts +0 -27
  190. package/cjs/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.js +0 -86
  191. package/cjs/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.js.map +0 -1
  192. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.d.ts +0 -5
  193. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.js +0 -6
  194. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.js.map +0 -1
  195. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.d.ts +0 -24
  196. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.js +0 -108
  197. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.js.map +0 -1
  198. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +0 -46
  199. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js +0 -112
  200. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js.map +0 -1
  201. package/esm/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.d.ts +0 -22
  202. package/esm/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.js +0 -29
  203. package/esm/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.js.map +0 -1
  204. package/esm/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.d.ts +0 -27
  205. package/esm/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.js +0 -50
  206. package/esm/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.js.map +0 -1
  207. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.d.ts +0 -5
  208. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.js +0 -3
  209. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.js.map +0 -1
  210. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.d.ts +0 -24
  211. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.js +0 -68
  212. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.js.map +0 -1
  213. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +0 -46
  214. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js +0 -109
  215. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js.map +0 -1
  216. package/src/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.tsx +0 -104
  217. package/src/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.tsx +0 -74
  218. package/src/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.tsx +0 -11
  219. package/src/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.tsx +0 -94
  220. package/src/data/table/helpers/selection/SelectionSubtreeHelper.test.ts +0 -66
  221. package/src/data/table/helpers/selection/SelectionSubtreeHelper.ts +0 -162
  222. package/src/data/table/hooks/__tests__/useTableSelection.test.ts +0 -488
  223. package/src/data/table/root/agent-feature-gap.md +0 -96
@@ -0,0 +1,161 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import type { TableRowEntryId } from "../../root/DataTable.types";
3
+ import type { ItemDetail } from "../collectTableRowEntries";
4
+ import { mutateRowSelection } from "./selection.utils";
5
+
6
+ function makeItemDetails<T>(
7
+ entries: { id: TableRowEntryId; rowData: T }[],
8
+ ): Map<TableRowEntryId, ItemDetail<T>> {
9
+ return new Map(
10
+ entries.map(({ id, rowData }) => [
11
+ id,
12
+ { id, rowData, level: 0, parentId: null, children: [] },
13
+ ]),
14
+ );
15
+ }
16
+
17
+ describe("mutateRowSelection", () => {
18
+ test("adds rowId to set when checked", () => {
19
+ const set = new Set<TableRowEntryId>();
20
+ const itemDetails = makeItemDetails([{ id: "a", rowData: {} }]);
21
+ const changed = mutateRowSelection({
22
+ selectedRowIds: set,
23
+ rowId: "a",
24
+ checked: true,
25
+ childRowIdsById: new Map(),
26
+ itemDetails,
27
+ });
28
+ expect(set.has("a")).toBe(true);
29
+ expect(changed).toBe(true);
30
+ });
31
+
32
+ test("removes rowId from set when unchecked", () => {
33
+ const set = new Set<TableRowEntryId>(["a"]);
34
+ const itemDetails = makeItemDetails([{ id: "a", rowData: {} }]);
35
+ const changed = mutateRowSelection({
36
+ selectedRowIds: set,
37
+ rowId: "a",
38
+ checked: false,
39
+ childRowIdsById: new Map(),
40
+ itemDetails,
41
+ });
42
+ expect(set.has("a")).toBe(false);
43
+ expect(changed).toBe(true);
44
+ });
45
+
46
+ test("returns false and does not mutate when row is already in desired state", () => {
47
+ const set = new Set<TableRowEntryId>(["a"]);
48
+ const itemDetails = makeItemDetails([{ id: "a", rowData: {} }]);
49
+ const changed = mutateRowSelection({
50
+ selectedRowIds: set,
51
+ rowId: "a",
52
+ checked: true,
53
+ childRowIdsById: new Map(),
54
+ itemDetails,
55
+ });
56
+ expect(set.size).toBe(1);
57
+ expect(changed).toBe(false);
58
+ });
59
+
60
+ test("recursively selects all children", () => {
61
+ const set = new Set<TableRowEntryId>();
62
+ const itemDetails = makeItemDetails([
63
+ { id: "parent", rowData: {} },
64
+ { id: "child1", rowData: {} },
65
+ { id: "child2", rowData: {} },
66
+ ]);
67
+ const childRowIdsById = new Map<TableRowEntryId, TableRowEntryId[]>([
68
+ ["parent", ["child1", "child2"]],
69
+ ]);
70
+ mutateRowSelection({
71
+ selectedRowIds: set,
72
+ rowId: "parent",
73
+ checked: true,
74
+ childRowIdsById,
75
+ itemDetails,
76
+ });
77
+ expect(set).toEqual(new Set(["parent", "child1", "child2"]));
78
+ });
79
+
80
+ test("recursively deselects all children", () => {
81
+ const set = new Set<TableRowEntryId>(["parent", "child1", "child2"]);
82
+ const itemDetails = makeItemDetails([
83
+ { id: "parent", rowData: {} },
84
+ { id: "child1", rowData: {} },
85
+ { id: "child2", rowData: {} },
86
+ ]);
87
+ const childRowIdsById = new Map<TableRowEntryId, TableRowEntryId[]>([
88
+ ["parent", ["child1", "child2"]],
89
+ ]);
90
+ mutateRowSelection({
91
+ selectedRowIds: set,
92
+ rowId: "parent",
93
+ checked: false,
94
+ childRowIdsById,
95
+ itemDetails,
96
+ });
97
+ expect(set.size).toBe(0);
98
+ });
99
+
100
+ test("handles deeply nested children", () => {
101
+ const set = new Set<TableRowEntryId>();
102
+ const itemDetails = makeItemDetails([
103
+ { id: "a", rowData: {} },
104
+ { id: "a1", rowData: {} },
105
+ { id: "a1a", rowData: {} },
106
+ ]);
107
+ const childRowIdsById = new Map<TableRowEntryId, TableRowEntryId[]>([
108
+ ["a", ["a1"]],
109
+ ["a1", ["a1a"]],
110
+ ]);
111
+ mutateRowSelection({
112
+ selectedRowIds: set,
113
+ rowId: "a",
114
+ checked: true,
115
+ childRowIdsById,
116
+ itemDetails,
117
+ });
118
+ expect(set).toEqual(new Set(["a", "a1", "a1a"]));
119
+ });
120
+
121
+ test("skips disabled rows and their children", () => {
122
+ const set = new Set<TableRowEntryId>();
123
+ const itemDetails = makeItemDetails([
124
+ { id: "parent", rowData: { disabled: false } },
125
+ { id: "child1", rowData: { disabled: true } },
126
+ { id: "child1a", rowData: { disabled: false } },
127
+ { id: "child2", rowData: { disabled: false } },
128
+ ]);
129
+ const childRowIdsById = new Map<TableRowEntryId, TableRowEntryId[]>([
130
+ ["parent", ["child1", "child2"]],
131
+ ["child1", ["child1a"]],
132
+ ]);
133
+ mutateRowSelection({
134
+ selectedRowIds: set,
135
+ rowId: "parent",
136
+ checked: true,
137
+ childRowIdsById,
138
+ itemDetails,
139
+ enableRowSelection: ({ row }) => !row.disabled,
140
+ });
141
+ expect(set.has("parent")).toBe(true);
142
+ expect(set.has("child1")).toBe(false);
143
+ expect(set.has("child1a")).toBe(true);
144
+ expect(set.has("child2")).toBe(true);
145
+ });
146
+
147
+ test("returns false when all matching rows were already disabled", () => {
148
+ const set = new Set<TableRowEntryId>();
149
+ const itemDetails = makeItemDetails([{ id: "a", rowData: {} }]);
150
+ const changed = mutateRowSelection({
151
+ selectedRowIds: set,
152
+ rowId: "a",
153
+ checked: true,
154
+ childRowIdsById: new Map(),
155
+ itemDetails,
156
+ enableRowSelection: false,
157
+ });
158
+ expect(set.size).toBe(0);
159
+ expect(changed).toBe(false);
160
+ });
161
+ });
@@ -0,0 +1,73 @@
1
+ import type { TableRowEntryId } from "../../root/DataTable.types";
2
+ import type { ItemDetail } from "../collectTableRowEntries";
3
+ import type { SelectionProps } from "./selection.types";
4
+
5
+ function canSelectTableRow<T>(
6
+ enableRowSelection: SelectionProps<T>["enableRowSelection"],
7
+ args: { row: T; id: TableRowEntryId },
8
+ ): boolean {
9
+ if (typeof enableRowSelection === "function") {
10
+ return enableRowSelection(args);
11
+ }
12
+ return enableRowSelection ?? true;
13
+ }
14
+
15
+ type MutateRowSelectionArgs<T> = {
16
+ selectedRowIds: Set<TableRowEntryId>;
17
+ rowId: TableRowEntryId;
18
+ checked: boolean;
19
+ childRowIdsById: Map<TableRowEntryId, TableRowEntryId[]>;
20
+ itemDetails: Map<TableRowEntryId, ItemDetail<T>>;
21
+ enableRowSelection?: SelectionProps<T>["enableRowSelection"];
22
+ };
23
+
24
+ /**
25
+ * Traverses the row and its children and updates selected-state directly on given selectedRowIds set.
26
+ * Returns true if any changes were made to the set, false otherwise.
27
+ */
28
+ function mutateRowSelection<T>({
29
+ selectedRowIds,
30
+ rowId,
31
+ checked,
32
+ childRowIdsById,
33
+ itemDetails,
34
+ enableRowSelection,
35
+ }: MutateRowSelectionArgs<T>): boolean {
36
+ let changed = false;
37
+ const item = itemDetails.get(rowId);
38
+
39
+ if (
40
+ item &&
41
+ canSelectTableRow(enableRowSelection, { row: item.rowData, id: rowId })
42
+ ) {
43
+ if (checked && !selectedRowIds.has(rowId)) {
44
+ selectedRowIds.add(rowId);
45
+ changed = true;
46
+ } else if (!checked && selectedRowIds.has(rowId)) {
47
+ selectedRowIds.delete(rowId);
48
+ changed = true;
49
+ }
50
+ }
51
+
52
+ const children = childRowIdsById.get(rowId);
53
+ if (children) {
54
+ for (const childId of children) {
55
+ if (
56
+ mutateRowSelection({
57
+ selectedRowIds,
58
+ rowId: childId,
59
+ checked,
60
+ childRowIdsById,
61
+ itemDetails,
62
+ enableRowSelection,
63
+ })
64
+ ) {
65
+ changed = true;
66
+ }
67
+ }
68
+ }
69
+
70
+ return changed;
71
+ }
72
+
73
+ export { canSelectTableRow, mutateRowSelection };
@@ -1,5 +1,6 @@
1
1
  import { renderHook } from "@testing-library/react";
2
2
  import { describe, expect, test } from "vitest";
3
+ import type { TableRowEntryId } from "../../root/DataTable.types";
3
4
  import { useTableItems } from "../useTableItems";
4
5
 
5
6
  type TestRow = {
@@ -140,7 +141,7 @@ describe("useTableItems", () => {
140
141
  },
141
142
  }),
142
143
  {
143
- initialProps: { expandedIds: [] as (string | number)[] },
144
+ initialProps: { expandedIds: [] as TableRowEntryId[] },
144
145
  },
145
146
  );
146
147
 
@@ -3,38 +3,57 @@ import type {
3
3
  ColumnDefinition,
4
4
  ColumnDefinitions,
5
5
  } from "../root/DataTable.types";
6
- import type { SelectionProps } from "./useTableSelection";
6
+ import { ACTION_CELL_WIDTH } from "../tr/DataTableTr";
7
7
 
8
8
  type UseColumnOptions = {
9
9
  stickyColumns?: {
10
- first?: "1";
11
- last?: "1";
10
+ start?: "1";
11
+ end?: "1";
12
12
  };
13
- selectionMode: SelectionProps["selectionMode"];
13
+ hasSelection: boolean;
14
+ hasDetailsPanel: boolean;
15
+ layout: "fixed" | "auto";
16
+ };
17
+
18
+ type StickyStartState = {
19
+ selection: boolean;
20
+ expansion: boolean;
21
+ selectionOffset: number;
22
+ firstColumnOffset: number;
14
23
  };
15
24
 
16
25
  type UseColumnOptionsResult<T> = {
17
26
  columns: {
18
27
  isSticky: "start" | "end" | false;
28
+ isStickyLast?: boolean;
29
+ stickyLeftOffset?: number;
19
30
  colDef: ColumnDefinition<T>;
20
31
  }[];
21
- stickySelection: boolean;
32
+ stickyStart: StickyStartState;
33
+ totalColSpan: number;
22
34
  };
23
35
 
24
36
  function useColumnOptions<T>(
25
37
  columnDefinitions: ColumnDefinitions<T>,
26
38
  options: UseColumnOptions,
27
39
  ): UseColumnOptionsResult<T> {
28
- const { stickyColumns, selectionMode } = options;
40
+ const { stickyColumns, hasSelection, hasDetailsPanel, layout } = options;
41
+
42
+ const hasStickyStart = stickyColumns?.start === "1";
29
43
 
30
- const hasSelection = selectionMode !== "none";
44
+ const stickyExpansion = hasStickyStart && hasDetailsPanel;
45
+ const stickySelection = hasStickyStart && hasSelection;
46
+
47
+ const stickySelectionOffset = stickyExpansion ? ACTION_CELL_WIDTH : 0;
48
+ const stickyFirstColumnOffset =
49
+ (stickyExpansion ? ACTION_CELL_WIDTH : 0) +
50
+ (stickySelection ? ACTION_CELL_WIDTH : 0);
31
51
 
32
52
  const columns = useMemo(() => {
33
53
  return columnDefinitions.map((colDef, index) => {
34
- const isFirstSticky =
35
- stickyColumns?.first === "1" && index === 0 && !hasSelection;
54
+ const isFirstSticky = hasStickyStart && index === 0;
36
55
  const isLastSticky =
37
- stickyColumns?.last === "1" && index === columnDefinitions.length - 1;
56
+ stickyColumns?.end === "1" && index === columnDefinitions.length - 1;
38
57
 
39
58
  return {
40
59
  isSticky: isFirstSticky
@@ -42,21 +61,36 @@ function useColumnOptions<T>(
42
61
  : isLastSticky
43
62
  ? ("end" as const)
44
63
  : (false as const),
64
+ isStickyLast: isFirstSticky && !isLastSticky,
65
+ stickyLeftOffset: isFirstSticky ? stickyFirstColumnOffset : undefined,
45
66
  colDef,
46
67
  };
47
68
  });
48
69
  }, [
49
70
  columnDefinitions,
50
- hasSelection,
51
- stickyColumns?.first,
52
- stickyColumns?.last,
71
+ hasStickyStart,
72
+ stickyColumns,
73
+ stickyFirstColumnOffset,
53
74
  ]);
54
75
 
76
+ const totalColSpan =
77
+ columns.length +
78
+ (layout === "fixed" ? 1 : 0) +
79
+ (hasSelection ? 1 : 0) +
80
+ (hasDetailsPanel ? 1 : 0);
81
+
55
82
  return {
56
- stickySelection: selectionMode !== "none" && stickyColumns?.first === "1",
83
+ stickyStart: {
84
+ selection: stickySelection,
85
+ expansion: stickyExpansion,
86
+ selectionOffset: stickySelectionOffset,
87
+ firstColumnOffset: stickyFirstColumnOffset,
88
+ },
57
89
  columns,
90
+ totalColSpan,
58
91
  };
59
92
  }
60
93
 
61
94
  export { useColumnOptions };
95
+ export type { StickyStartState };
62
96
  export type { UseColumnOptionsResult };
@@ -1,14 +1,15 @@
1
1
  import React, { useCallback } from "react";
2
2
  import { createStrictContext } from "../../../utils/helpers";
3
3
  import { useControllableState } from "../../../utils/hooks";
4
- import { useTableItemsContext } from "./useTableItems";
4
+ import type { TableRowEntryId } from "../root/DataTable.types";
5
+ import { useDataTableContext } from "../root/DataTableRoot.context";
5
6
 
6
7
  type DetailsPanelProps<T> = {
7
8
  /**
8
- * Renders a details panel below the row when expanded.
9
+ * Function to get the content to show in the details panel for a given row.
9
10
  * When provided, an expand toggle column is added automatically.
10
11
  */
11
- getContent?: (rowData: T) => React.ReactNode;
12
+ getContent: (rowData: T) => React.ReactNode;
12
13
  /**
13
14
  * Determines whether a row can be expanded to show details panel content.
14
15
  * @default () => true
@@ -18,20 +19,16 @@ type DetailsPanelProps<T> = {
18
19
  * Controlled list of expanded row IDs.
19
20
  * Use with `onDetailsPanelChange` for controlled usage, or `defaultDetailsPanelRowIds` for uncontrolled.
20
21
  */
21
- expandedRowIds?: (string | number)[];
22
+ expandedRowIds?: TableRowEntryId[];
22
23
  /**
23
24
  * Initial list of expanded row IDs for uncontrolled usage.
24
25
  * @default []
25
26
  */
26
- defaultExpandedRowIds?: (string | number)[];
27
+ defaultExpandedRowIds?: TableRowEntryId[];
27
28
  /**
28
29
  * Called when the list of expanded row IDs changes.
29
- *
30
- *
31
- * TODO:
32
- * - Docs: This pattern is called "Master / Detail" in general terms
33
30
  */
34
- onExpandedRowIdsChange?: (ids: (string | number)[]) => void;
31
+ onExpandedRowIdsChange?: (ids: TableRowEntryId[]) => void; // TODO: Docs: This pattern is called "Master / Detail" in general terms
35
32
  /**
36
33
  * Returns the height (in px) or `"auto"` for a row's details panel.
37
34
  * When a number is returned, the panel scrolls within that fixed height.
@@ -46,9 +43,9 @@ type DetailsPanelProps<T> = {
46
43
  };
47
44
 
48
45
  type DataTableDetailsPanelContextT = {
49
- isExpanded: (id: string | number) => boolean;
50
- isDetailsPanelExpandable: (id: string | number) => boolean;
51
- toggleExpansion: (id: string | number) => void;
46
+ isExpanded: (id: TableRowEntryId) => boolean;
47
+ isDetailsPanelExpandable: (id: TableRowEntryId) => boolean;
48
+ toggleExpansion: (id: TableRowEntryId) => void;
52
49
  toggleAll: () => void;
53
50
  isAllExpanded: boolean;
54
51
  getDetailsPanelContent?: (row: unknown) => React.ReactNode;
@@ -68,7 +65,7 @@ const {
68
65
 
69
66
  function DataTableDetailsPanelProvider<T>({
70
67
  children,
71
- detailsPanel = {},
68
+ detailsPanel,
72
69
  }: { detailsPanel?: DetailsPanelProps<T> } & { children: React.ReactNode }) {
73
70
  const {
74
71
  expandedRowIds,
@@ -78,7 +75,7 @@ function DataTableDetailsPanelProvider<T>({
78
75
  isRowExpandable,
79
76
  getHeight,
80
77
  showExpandAll = false,
81
- } = detailsPanel;
78
+ } = detailsPanel || {};
82
79
 
83
80
  const [expandedIds, setExpandedIds] = useControllableState({
84
81
  value: expandedRowIds,
@@ -87,21 +84,21 @@ function DataTableDetailsPanelProvider<T>({
87
84
  });
88
85
 
89
86
  /* TODO: False is just fallback until auto and root is merged */
90
- const tableItemsContext = useTableItemsContext(false);
87
+ const tableContext = useDataTableContext(false);
91
88
 
92
- const { itemDetails } = tableItemsContext ?? {
89
+ const { itemDetails } = tableContext?.tableItems ?? {
93
90
  itemDetails: new Map<
94
- string | number,
95
- { rowData: T; id: string | number; level: number }
91
+ TableRowEntryId,
92
+ { rowData: T; id: TableRowEntryId; level: number }
96
93
  >(),
97
94
  };
98
95
 
99
96
  const expandableIds = React.useMemo(() => {
100
97
  if (!getContent) {
101
- return new Set<string | number>();
98
+ return new Set<TableRowEntryId>();
102
99
  }
103
100
 
104
- const ids = new Set<string | number>();
101
+ const ids = new Set<TableRowEntryId>();
105
102
 
106
103
  for (const { rowData, id, level } of itemDetails.values()) {
107
104
  /* We only allow Master - Details pattern on top level rows */
@@ -118,18 +115,18 @@ function DataTableDetailsPanelProvider<T>({
118
115
  }, [getContent, isRowExpandable, itemDetails]);
119
116
 
120
117
  const isDetailsPanelExpandableById = useCallback(
121
- (id: string | number) => expandableIds.has(id),
118
+ (id: TableRowEntryId) => expandableIds.has(id),
122
119
  [expandableIds],
123
120
  );
124
121
 
125
122
  const isExpanded = useCallback(
126
- (id: string | number) =>
123
+ (id: TableRowEntryId) =>
127
124
  isDetailsPanelExpandableById(id) && expandedIds.includes(id),
128
125
  [expandedIds, isDetailsPanelExpandableById],
129
126
  );
130
127
 
131
128
  const toggleExpansion = useCallback(
132
- (id: string | number) => {
129
+ (id: TableRowEntryId) => {
133
130
  if (!isDetailsPanelExpandableById(id)) {
134
131
  return;
135
132
  }
@@ -172,7 +169,7 @@ function DataTableDetailsPanelProvider<T>({
172
169
  );
173
170
  }
174
171
 
175
- function getDataTableDetailsPanelId(tableId: string, rowId: string | number) {
172
+ function getDataTableDetailsPanelId(tableId: string, rowId: TableRowEntryId) {
176
173
  return `${tableId}-expansion-${rowId}`;
177
174
  }
178
175
 
@@ -1,18 +1,34 @@
1
1
  import { useCallback, useMemo } from "react";
2
- import { createStrictContext } from "../../../utils/helpers";
3
2
  import { useControllableState } from "../../../utils/hooks";
4
3
  import {
5
4
  type ItemDetail,
6
- type TableRowEntryId,
7
5
  collectTableRowEntries,
8
6
  } from "../helpers/collectTableRowEntries";
7
+ import type { TableRowEntryId } from "../root/DataTable.types";
9
8
 
10
9
  type SubRowsProps<T> = {
11
- getRows?: (rowData: T) => T[];
12
- expandedRowIds?: (string | number)[];
13
- defaultExpandedRowIds?: (string | number)[];
10
+ /**
11
+ * Function to get sub-rows for a given row.
12
+ */
13
+ getRows: (rowData: T) => T[];
14
+ /**
15
+ * Controlled list of IDs of rows that should be expanded.
16
+ */
17
+ expandedRowIds?: TableRowEntryId[];
18
+ /**
19
+ * IDs of rows that should be initially expanded.
20
+ * Only used when `expandedRowIds` is not provided, i.e. when the expanded state is uncontrolled.
21
+ */
22
+ defaultExpandedRowIds?: TableRowEntryId[];
23
+ /**
24
+ * Called when the list of expanded row IDs changes.
25
+ */
26
+ onExpandedRowIdsChange?: (ids: TableRowEntryId[]) => void;
27
+ /**
28
+ * Function to get whether a row should be expandable.
29
+ * By default, all rows are expandable when `getRows` is provided.
30
+ */
14
31
  isRowExpandable?: (rowData: T) => boolean;
15
- onExpandedRowIdsChange?: (ids: (string | number)[]) => void;
16
32
  };
17
33
 
18
34
  type UseTableItemsArgs<T> = {
@@ -21,19 +37,19 @@ type UseTableItemsArgs<T> = {
21
37
  subRows?: SubRowsProps<T>;
22
38
  };
23
39
 
24
- type useTableItemsReturn<T> = {
40
+ type UseTableItemsReturn<T> = {
25
41
  items: T[];
26
42
  itemDetails: Map<TableRowEntryId, ItemDetail<T>>;
27
43
  /** Row ids for the rows currently rendered in the table body. */
28
44
  visibleRowIds: TableRowEntryId[];
29
45
  /** Direct child ids for each row, used to traverse selection groups lazily. */
30
46
  childRowIdsById: Map<TableRowEntryId, TableRowEntryId[]>;
31
- onExpandedRowIdsChange: (id: string | number) => void;
32
- isSubRowExpanded: (id: string | number) => boolean;
47
+ onExpandedRowIdsChange: (id: TableRowEntryId) => void;
48
+ isSubRowExpanded: (id: TableRowEntryId) => boolean;
33
49
  };
34
50
 
35
- function useTableItems<T>(args: UseTableItemsArgs<T>): useTableItemsReturn<T> {
36
- const { items, subRows = {}, getRowId } = args;
51
+ function useTableItems<T>(args: UseTableItemsArgs<T>): UseTableItemsReturn<T> {
52
+ const { items, subRows, getRowId } = args;
37
53
 
38
54
  const {
39
55
  expandedRowIds,
@@ -41,7 +57,7 @@ function useTableItems<T>(args: UseTableItemsArgs<T>): useTableItemsReturn<T> {
41
57
  getRows,
42
58
  onExpandedRowIdsChange,
43
59
  isRowExpandable,
44
- } = subRows;
60
+ } = subRows || {};
45
61
 
46
62
  const [nestedSubRowsExpandedIds, setNestedSubRowsExpandedIds] =
47
63
  useControllableState({
@@ -106,7 +122,7 @@ function useTableItems<T>(args: UseTableItemsArgs<T>): useTableItemsReturn<T> {
106
122
  }, [getRows, items, getRowId, isRowExpandable, expandedIdsSet]);
107
123
 
108
124
  const handleExpandedSubRowIdChange = useCallback(
109
- (id: string | number) => {
125
+ (id: TableRowEntryId) => {
110
126
  setNestedSubRowsExpandedIds((prev) =>
111
127
  prev.includes(id)
112
128
  ? prev.filter((expandedId) => expandedId !== id)
@@ -122,17 +138,9 @@ function useTableItems<T>(args: UseTableItemsArgs<T>): useTableItemsReturn<T> {
122
138
  visibleRowIds,
123
139
  childRowIdsById,
124
140
  onExpandedRowIdsChange: handleExpandedSubRowIdChange,
125
- isSubRowExpanded: (id: string | number) => expandedIdsSet.has(id),
141
+ isSubRowExpanded: (id: TableRowEntryId) => expandedIdsSet.has(id),
126
142
  };
127
143
  }
128
144
 
129
- const { Provider: TableItemsProvider, useContext: useTableItemsContext } =
130
- /* TODO: Can we type this better? */
131
- createStrictContext<Omit<useTableItemsReturn<any>, "childRowIdsById">>({
132
- name: "TableItemsContext",
133
- errorMessage:
134
- "useTableItemsContext must be used within a TableItemsProvider",
135
- });
136
-
137
- export { useTableItems, TableItemsProvider, useTableItemsContext };
138
- export type { ItemDetail, SubRowsProps };
145
+ export { useTableItems };
146
+ export type { ItemDetail, SubRowsProps, UseTableItemsReturn };
@@ -18,17 +18,9 @@ import { useGridCache } from "./useGridCache";
18
18
 
19
19
  type UseTableKeyboardNavOptions = {
20
20
  enabled: boolean;
21
- /**
22
- * Custom callback to determine if navigation should be blocked.
23
- * Called before default blocking logic.
24
- */
25
- shouldBlockNavigation?: (event: KeyboardEvent) => boolean;
26
21
  };
27
22
 
28
- function useTableKeyboardNav({
29
- enabled,
30
- shouldBlockNavigation: customBlockFn,
31
- }: UseTableKeyboardNavOptions) {
23
+ function useTableKeyboardNav({ enabled }: UseTableKeyboardNavOptions) {
32
24
  const [tableRef, setTableRef] = useState<HTMLTableElement | null>(null);
33
25
  const { getTableGrid, activeCell, setActiveCell } = useGridCache(
34
26
  tableRef,
@@ -110,10 +102,6 @@ function useTableKeyboardNav({
110
102
  * Checks if navigation should be blocked based on current focus context.
111
103
  */
112
104
  const handleTableKeyDown = useEventCallback((event: KeyboardEvent): void => {
113
- if (customBlockFn?.(event)) {
114
- return;
115
- }
116
-
117
105
  const action = getNavigationAction(event);
118
106
  if (!action) {
119
107
  return;