@navikt/ds-react 8.10.4 → 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 (244) 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 +11 -13
  11. package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
  12. package/cjs/data/drag-and-drop/root/DragAndDrop.context.d.ts +4 -2
  13. package/cjs/data/drag-and-drop/root/DragAndDrop.context.js.map +1 -1
  14. package/cjs/data/drag-and-drop/root/DragAndDropRoot.js +44 -46
  15. package/cjs/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
  16. package/cjs/data/drag-and-drop/types.d.ts +0 -4
  17. package/cjs/data/stories/Data.test-data.d.ts +2 -5
  18. package/cjs/data/stories/Data.test-data.js +30 -39
  19. package/cjs/data/stories/Data.test-data.js.map +1 -1
  20. package/cjs/data/table/base-cell/DataTableBaseCell.d.ts +15 -15
  21. package/cjs/data/table/base-cell/DataTableBaseCell.js +4 -8
  22. package/cjs/data/table/base-cell/DataTableBaseCell.js.map +1 -1
  23. package/cjs/data/table/column-header/DataTableColumnHeader.d.ts +24 -6
  24. package/cjs/data/table/column-header/DataTableColumnHeader.js +22 -27
  25. package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  26. package/cjs/data/table/column-header/useTableColumnResize.d.ts +19 -29
  27. package/cjs/data/table/column-header/useTableColumnResize.js +24 -22
  28. package/cjs/data/table/column-header/useTableColumnResize.js.map +1 -1
  29. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +1 -1
  30. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js +2 -2
  31. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -1
  32. package/cjs/data/table/helpers/collectTableRowEntries.d.ts +9 -7
  33. package/cjs/data/table/helpers/collectTableRowEntries.js +18 -10
  34. package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -1
  35. package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +13 -11
  36. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +43 -53
  37. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  38. package/cjs/data/table/helpers/selection/getSingleSelectProps.d.ts +9 -8
  39. package/cjs/data/table/helpers/selection/getSingleSelectProps.js +23 -10
  40. package/cjs/data/table/helpers/selection/getSingleSelectProps.js.map +1 -1
  41. package/cjs/data/table/helpers/selection/selection.types.d.ts +19 -19
  42. package/cjs/data/table/helpers/selection/selection.utils.d.ts +21 -0
  43. package/cjs/data/table/helpers/selection/selection.utils.js +46 -0
  44. package/cjs/data/table/helpers/selection/selection.utils.js.map +1 -0
  45. package/cjs/data/table/helpers/table-focus.d.ts +0 -3
  46. package/cjs/data/table/helpers/table-focus.js +38 -8
  47. package/cjs/data/table/helpers/table-focus.js.map +1 -1
  48. package/cjs/data/table/hooks/useColumnOptions.d.ts +16 -5
  49. package/cjs/data/table/hooks/useColumnOptions.js +26 -8
  50. package/cjs/data/table/hooks/useColumnOptions.js.map +1 -1
  51. package/cjs/data/table/hooks/useGridCache.js +2 -2
  52. package/cjs/data/table/hooks/useGridCache.js.map +1 -1
  53. package/cjs/data/table/hooks/useTableDetailsPanel.d.ts +10 -13
  54. package/cjs/data/table/hooks/useTableDetailsPanel.js +7 -6
  55. package/cjs/data/table/hooks/useTableDetailsPanel.js.map +1 -1
  56. package/cjs/data/table/hooks/useTableItems.d.ts +31 -17
  57. package/cjs/data/table/hooks/useTableItems.js +10 -20
  58. package/cjs/data/table/hooks/useTableItems.js.map +1 -1
  59. package/cjs/data/table/hooks/useTableKeyboardNav.d.ts +1 -6
  60. package/cjs/data/table/hooks/useTableKeyboardNav.js +6 -5
  61. package/cjs/data/table/hooks/useTableKeyboardNav.js.map +1 -1
  62. package/cjs/data/table/hooks/useTableSelection.d.ts +6 -6
  63. package/cjs/data/table/hooks/useTableSelection.js +13 -13
  64. package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
  65. package/cjs/data/table/hooks/useTableSort.d.ts +2 -2
  66. package/cjs/data/table/hooks/useTableSort.js +4 -5
  67. package/cjs/data/table/hooks/useTableSort.js.map +1 -1
  68. package/cjs/data/table/root/DataTable.types.d.ts +22 -13
  69. package/cjs/data/table/root/DataTableRoot.context.d.ts +13 -7
  70. package/cjs/data/table/root/DataTableRoot.context.js.map +1 -1
  71. package/cjs/data/table/root/DataTableRoot.d.ts +49 -72
  72. package/cjs/data/table/root/DataTableRoot.js +56 -72
  73. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  74. package/cjs/data/table/root/DataTableRoot.legacy.d.ts +2 -7
  75. package/cjs/data/table/root/DataTableRoot.legacy.js +17 -3
  76. package/cjs/data/table/root/DataTableRoot.legacy.js.map +1 -1
  77. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js +4 -4
  78. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -1
  79. package/cjs/data/table/tbody/DataTableTbody.js +4 -2
  80. package/cjs/data/table/tbody/DataTableTbody.js.map +1 -1
  81. package/cjs/data/table/tr/DataTableTr.d.ts +5 -3
  82. package/cjs/data/table/tr/DataTableTr.js +36 -23
  83. package/cjs/data/table/tr/DataTableTr.js.map +1 -1
  84. package/cjs/table/ColumnHeader.js +2 -1
  85. package/cjs/table/ColumnHeader.js.map +1 -1
  86. package/esm/data/data-grid/index.d.ts +2 -0
  87. package/esm/data/data-grid/index.js +3 -0
  88. package/esm/data/data-grid/index.js.map +1 -0
  89. package/esm/data/data-grid/root/DataGridRoot.context.d.ts +11 -0
  90. package/esm/data/data-grid/root/DataGridRoot.context.js +7 -0
  91. package/esm/data/data-grid/root/DataGridRoot.context.js.map +1 -0
  92. package/esm/data/data-grid/root/DataGridRoot.d.ts +38 -0
  93. package/esm/data/data-grid/root/DataGridRoot.js +32 -0
  94. package/esm/data/data-grid/root/DataGridRoot.js.map +1 -0
  95. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +11 -13
  96. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
  97. package/esm/data/drag-and-drop/root/DragAndDrop.context.d.ts +4 -2
  98. package/esm/data/drag-and-drop/root/DragAndDrop.context.js.map +1 -1
  99. package/esm/data/drag-and-drop/root/DragAndDropRoot.js +44 -46
  100. package/esm/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
  101. package/esm/data/drag-and-drop/types.d.ts +0 -4
  102. package/esm/data/stories/Data.test-data.d.ts +2 -5
  103. package/esm/data/stories/Data.test-data.js +30 -39
  104. package/esm/data/stories/Data.test-data.js.map +1 -1
  105. package/esm/data/table/base-cell/DataTableBaseCell.d.ts +15 -15
  106. package/esm/data/table/base-cell/DataTableBaseCell.js +4 -8
  107. package/esm/data/table/base-cell/DataTableBaseCell.js.map +1 -1
  108. package/esm/data/table/column-header/DataTableColumnHeader.d.ts +24 -6
  109. package/esm/data/table/column-header/DataTableColumnHeader.js +23 -28
  110. package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  111. package/esm/data/table/column-header/useTableColumnResize.d.ts +19 -29
  112. package/esm/data/table/column-header/useTableColumnResize.js +24 -22
  113. package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
  114. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +1 -1
  115. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js +2 -2
  116. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -1
  117. package/esm/data/table/helpers/collectTableRowEntries.d.ts +9 -7
  118. package/esm/data/table/helpers/collectTableRowEntries.js +18 -10
  119. package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -1
  120. package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +13 -11
  121. package/esm/data/table/helpers/selection/getMultipleSelectProps.js +43 -53
  122. package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  123. package/esm/data/table/helpers/selection/getSingleSelectProps.d.ts +9 -8
  124. package/esm/data/table/helpers/selection/getSingleSelectProps.js +23 -10
  125. package/esm/data/table/helpers/selection/getSingleSelectProps.js.map +1 -1
  126. package/esm/data/table/helpers/selection/selection.types.d.ts +19 -19
  127. package/esm/data/table/helpers/selection/selection.utils.d.ts +21 -0
  128. package/esm/data/table/helpers/selection/selection.utils.js +43 -0
  129. package/esm/data/table/helpers/selection/selection.utils.js.map +1 -0
  130. package/esm/data/table/helpers/table-focus.d.ts +0 -3
  131. package/esm/data/table/helpers/table-focus.js +38 -8
  132. package/esm/data/table/helpers/table-focus.js.map +1 -1
  133. package/esm/data/table/hooks/useColumnOptions.d.ts +16 -5
  134. package/esm/data/table/hooks/useColumnOptions.js +26 -8
  135. package/esm/data/table/hooks/useColumnOptions.js.map +1 -1
  136. package/esm/data/table/hooks/useGridCache.js +2 -2
  137. package/esm/data/table/hooks/useGridCache.js.map +1 -1
  138. package/esm/data/table/hooks/useTableDetailsPanel.d.ts +10 -13
  139. package/esm/data/table/hooks/useTableDetailsPanel.js +7 -6
  140. package/esm/data/table/hooks/useTableDetailsPanel.js.map +1 -1
  141. package/esm/data/table/hooks/useTableItems.d.ts +31 -17
  142. package/esm/data/table/hooks/useTableItems.js +11 -18
  143. package/esm/data/table/hooks/useTableItems.js.map +1 -1
  144. package/esm/data/table/hooks/useTableKeyboardNav.d.ts +1 -6
  145. package/esm/data/table/hooks/useTableKeyboardNav.js +7 -6
  146. package/esm/data/table/hooks/useTableKeyboardNav.js.map +1 -1
  147. package/esm/data/table/hooks/useTableSelection.d.ts +6 -6
  148. package/esm/data/table/hooks/useTableSelection.js +13 -13
  149. package/esm/data/table/hooks/useTableSelection.js.map +1 -1
  150. package/esm/data/table/hooks/useTableSort.d.ts +2 -2
  151. package/esm/data/table/hooks/useTableSort.js +4 -5
  152. package/esm/data/table/hooks/useTableSort.js.map +1 -1
  153. package/esm/data/table/root/DataTable.types.d.ts +22 -13
  154. package/esm/data/table/root/DataTableRoot.context.d.ts +13 -7
  155. package/esm/data/table/root/DataTableRoot.context.js.map +1 -1
  156. package/esm/data/table/root/DataTableRoot.d.ts +49 -72
  157. package/esm/data/table/root/DataTableRoot.js +58 -74
  158. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  159. package/esm/data/table/root/DataTableRoot.legacy.d.ts +2 -7
  160. package/esm/data/table/root/DataTableRoot.legacy.js +17 -3
  161. package/esm/data/table/root/DataTableRoot.legacy.js.map +1 -1
  162. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js +4 -4
  163. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -1
  164. package/esm/data/table/tbody/DataTableTbody.js +4 -2
  165. package/esm/data/table/tbody/DataTableTbody.js.map +1 -1
  166. package/esm/data/table/tr/DataTableTr.d.ts +5 -3
  167. package/esm/data/table/tr/DataTableTr.js +35 -23
  168. package/esm/data/table/tr/DataTableTr.js.map +1 -1
  169. package/esm/table/ColumnHeader.js +2 -1
  170. package/esm/table/ColumnHeader.js.map +1 -1
  171. package/package.json +8 -7
  172. package/src/data/data-grid/index.ts +3 -0
  173. package/src/data/data-grid/root/DataGridRoot.context.ts +16 -0
  174. package/src/data/data-grid/root/DataGridRoot.tsx +71 -0
  175. package/src/data/drag-and-drop/drag-handler/DragAndDropDragHandler.tsx +11 -17
  176. package/src/data/drag-and-drop/root/DragAndDrop.context.tsx +4 -2
  177. package/src/data/drag-and-drop/root/DragAndDropRoot.tsx +63 -52
  178. package/src/data/drag-and-drop/types.ts +0 -5
  179. package/src/data/stories/Data.test-data.tsx +52 -44
  180. package/src/data/table/agent-component-review.md +175 -0
  181. package/src/data/table/base-cell/DataTableBaseCell.tsx +31 -21
  182. package/src/data/table/column-header/DataTableColumnHeader.tsx +63 -58
  183. package/src/data/table/column-header/useTableColumnResize.ts +55 -71
  184. package/src/data/table/details-panel-row/DataTableDetailsPanelRow.tsx +7 -3
  185. package/src/data/table/helpers/collectTableRowEntries.ts +32 -19
  186. package/src/data/table/helpers/selection/getMultipleSelectProps.ts +65 -85
  187. package/src/data/table/helpers/selection/getSingleSelectProps.ts +35 -17
  188. package/src/data/table/helpers/selection/selection.types.ts +19 -19
  189. package/src/data/table/helpers/selection/selection.utils.test.ts +161 -0
  190. package/src/data/table/helpers/selection/selection.utils.ts +73 -0
  191. package/src/data/table/helpers/table-focus.ts +63 -9
  192. package/src/data/table/hooks/__tests__/useTableItems.test.ts +48 -8
  193. package/src/data/table/hooks/useColumnOptions.ts +48 -14
  194. package/src/data/table/hooks/useGridCache.ts +3 -2
  195. package/src/data/table/hooks/useTableDetailsPanel.tsx +25 -25
  196. package/src/data/table/hooks/useTableItems.ts +51 -42
  197. package/src/data/table/hooks/useTableKeyboardNav.ts +7 -15
  198. package/src/data/table/hooks/useTableSelection.ts +26 -31
  199. package/src/data/table/hooks/useTableSort.ts +10 -9
  200. package/src/data/table/root/DataTable.types.ts +30 -25
  201. package/src/data/table/root/DataTableRoot.context.ts +19 -7
  202. package/src/data/table/root/DataTableRoot.legacy.tsx +22 -14
  203. package/src/data/table/root/DataTableRoot.tsx +271 -320
  204. package/src/data/table/sub-row-toggle/DataTableSubRowToggle.tsx +5 -4
  205. package/src/data/table/tbody/DataTableTbody.tsx +6 -2
  206. package/src/data/table/tr/DataTableTr.tsx +98 -35
  207. package/src/table/ColumnHeader.tsx +2 -1
  208. package/cjs/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.d.ts +0 -22
  209. package/cjs/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.js +0 -35
  210. package/cjs/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.js.map +0 -1
  211. package/cjs/data/drag-and-drop-old/item/DataDragAndDropItem.d.ts +0 -27
  212. package/cjs/data/drag-and-drop-old/item/DataDragAndDropItem.js +0 -86
  213. package/cjs/data/drag-and-drop-old/item/DataDragAndDropItem.js.map +0 -1
  214. package/cjs/data/drag-and-drop-old/root/DataDragAndDrop.context.d.ts +0 -5
  215. package/cjs/data/drag-and-drop-old/root/DataDragAndDrop.context.js +0 -6
  216. package/cjs/data/drag-and-drop-old/root/DataDragAndDrop.context.js.map +0 -1
  217. package/cjs/data/drag-and-drop-old/root/DataDragAndDropRoot.d.ts +0 -24
  218. package/cjs/data/drag-and-drop-old/root/DataDragAndDropRoot.js +0 -108
  219. package/cjs/data/drag-and-drop-old/root/DataDragAndDropRoot.js.map +0 -1
  220. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +0 -46
  221. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js +0 -112
  222. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js.map +0 -1
  223. package/esm/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.d.ts +0 -22
  224. package/esm/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.js +0 -29
  225. package/esm/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.js.map +0 -1
  226. package/esm/data/drag-and-drop-old/item/DataDragAndDropItem.d.ts +0 -27
  227. package/esm/data/drag-and-drop-old/item/DataDragAndDropItem.js +0 -50
  228. package/esm/data/drag-and-drop-old/item/DataDragAndDropItem.js.map +0 -1
  229. package/esm/data/drag-and-drop-old/root/DataDragAndDrop.context.d.ts +0 -5
  230. package/esm/data/drag-and-drop-old/root/DataDragAndDrop.context.js +0 -3
  231. package/esm/data/drag-and-drop-old/root/DataDragAndDrop.context.js.map +0 -1
  232. package/esm/data/drag-and-drop-old/root/DataDragAndDropRoot.d.ts +0 -24
  233. package/esm/data/drag-and-drop-old/root/DataDragAndDropRoot.js +0 -68
  234. package/esm/data/drag-and-drop-old/root/DataDragAndDropRoot.js.map +0 -1
  235. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +0 -46
  236. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js +0 -109
  237. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js.map +0 -1
  238. package/src/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.tsx +0 -104
  239. package/src/data/drag-and-drop-old/item/DataDragAndDropItem.tsx +0 -74
  240. package/src/data/drag-and-drop-old/root/DataDragAndDrop.context.tsx +0 -11
  241. package/src/data/drag-and-drop-old/root/DataDragAndDropRoot.tsx +0 -96
  242. package/src/data/table/helpers/selection/SelectionSubtreeHelper.test.ts +0 -66
  243. package/src/data/table/helpers/selection/SelectionSubtreeHelper.ts +0 -162
  244. package/src/data/table/hooks/__tests__/useTableSelection.test.ts +0 -488
@@ -1,107 +1,87 @@
1
+ import type { ChangeEventHandler, SetStateAction } from "react";
1
2
  import type { CheckboxInputProps } from "../../../../form/checkbox/checkbox-input/CheckboxInput";
2
- import { SelectionSubtreeHelper } from "./SelectionSubtreeHelper";
3
-
4
- type GetMultipleSelectPropsArgs = {
5
- selectedKeysSet: Set<string | number>;
6
- selectedKeys: (string | number)[];
7
- setSelectedKeys: (keys: (string | number)[]) => void;
8
- disabledKeysSet: Set<string | number>;
9
- visibleRowIds: (string | number)[];
10
- childRowIdsById?: Map<string | number, (string | number)[]>;
11
- };
12
-
13
- function getMultipleSelectProps({
3
+ import { consoleWarning } from "../../../../utils/helpers/consoleWarning";
4
+ import type { UseTableItemsReturn } from "../../hooks/useTableItems";
5
+ import type { TableRowEntryId } from "../../root/DataTable.types";
6
+ import type { SelectedKeysT, SelectionProps } from "./selection.types";
7
+ import { canSelectTableRow, mutateRowSelection } from "./selection.utils";
8
+
9
+ type GetMultipleSelectPropsArgs<T> = {
10
+ selectedKeysSet: Set<TableRowEntryId>;
11
+ selectedKeys: SelectedKeysT;
12
+ setSelectedKeys: (next: SetStateAction<SelectedKeysT>) => void;
13
+ tableItems: UseTableItemsReturn<T>;
14
+ } & Pick<SelectionProps<T>, "enableRowSelection">;
15
+
16
+ function getMultipleSelectProps<T>({
14
17
  selectedKeysSet,
15
18
  selectedKeys,
16
19
  setSelectedKeys,
17
- disabledKeysSet,
18
- visibleRowIds,
19
- childRowIdsById,
20
- }: GetMultipleSelectPropsArgs) {
21
- const subtreeHelper = new SelectionSubtreeHelper({
22
- childRowIdsById,
23
- disabledKeysSet,
24
- selectedKeysSet,
25
- });
26
-
27
- // Header selection traverses the visible roots and skips already visited
28
- // descendants, so expanded trees stay linear in the number of rows.
29
- const headerSelectableKeys = subtreeHelper.getSelectableKeys(visibleRowIds);
30
- const headerSelectableKeysSet = new Set(headerSelectableKeys);
31
-
32
- const selectedSelectableCount = headerSelectableKeys.filter((k) =>
33
- selectedKeysSet.has(k),
34
- ).length;
35
-
36
- const allSelectableSelected =
37
- headerSelectableKeys.length > 0 &&
38
- selectedSelectableCount === headerSelectableKeys.length;
39
-
40
- const indeterminate =
41
- selectedSelectableCount > 0 &&
42
- selectedSelectableCount < headerSelectableKeys.length;
43
-
44
- const selectedKeysNotInView = selectedKeys.filter(
45
- (k) => !headerSelectableKeysSet.has(k),
46
- );
47
- const disabledSelected = selectedKeys.filter((k) => disabledKeysSet.has(k));
48
- const preservedKeys = [
49
- ...new Set([...selectedKeysNotInView, ...disabledSelected]),
50
- ];
20
+ enableRowSelection,
21
+ tableItems,
22
+ }: GetMultipleSelectPropsArgs<T>) {
23
+ const selectableIdsSet: Set<TableRowEntryId> = new Set();
24
+
25
+ for (const [id, { rowData }] of tableItems.itemDetails) {
26
+ if (canSelectTableRow(enableRowSelection, { row: rowData, id })) {
27
+ selectableIdsSet.add(id);
28
+ }
29
+ }
51
30
 
52
- const isGroupFullySelected = (key: string | number) => {
53
- const groupStats = subtreeHelper.getSelectionStats(key);
31
+ let selectedOnPageCount = 0;
32
+ for (const id of selectableIdsSet) {
33
+ selectedKeysSet.has(id) && selectedOnPageCount++;
34
+ }
54
35
 
55
- return (
56
- groupStats.selectableCount > 0 &&
57
- groupStats.selectedCount === groupStats.selectableCount
58
- );
59
- };
36
+ const isAllSelected =
37
+ selectableIdsSet.size > 0 && selectedOnPageCount === selectableIdsSet.size;
38
+ const someSelected = selectedOnPageCount > 0;
60
39
 
61
- const handleToggleAll = () => {
62
- if (allSelectableSelected) {
63
- setSelectedKeys(preservedKeys);
64
- } else {
65
- setSelectedKeys([
66
- ...new Set([...preservedKeys, ...headerSelectableKeys]),
67
- ]);
40
+ const handleToggleRow = (key: TableRowEntryId, row: T) => {
41
+ if (!row) {
42
+ consoleWarning(
43
+ `Row data is undefined for key ${key}. This may cause issues with selection if enableRowSelection is used.`,
44
+ );
68
45
  }
69
- };
70
46
 
71
- const handleToggleRow = (key: string | number) => {
72
- if (disabledKeysSet.has(key)) {
73
- return;
47
+ const checked = !selectedKeysSet.has(key);
48
+ const nextSet = new Set(selectedKeysSet);
49
+ const changed = mutateRowSelection({
50
+ selectedRowIds: nextSet,
51
+ rowId: key,
52
+ checked,
53
+ childRowIdsById: tableItems.childRowIdsById,
54
+ itemDetails: tableItems.itemDetails,
55
+ enableRowSelection,
56
+ });
57
+ if (changed) {
58
+ setSelectedKeys([...nextSet]);
74
59
  }
60
+ };
75
61
 
76
- const groupKeys = subtreeHelper.getSelectableKeys([key]);
77
-
78
- if (isGroupFullySelected(key)) {
79
- const groupKeysSet = new Set(groupKeys);
80
- setSelectedKeys(
81
- selectedKeys.filter((selectedKey) => !groupKeysSet.has(selectedKey)),
82
- );
62
+ const toggleAllRowSelected: ChangeEventHandler<HTMLInputElement> = (
63
+ event,
64
+ ) => {
65
+ if (event.target.checked) {
66
+ const preserved = selectedKeys.filter((k) => !selectableIdsSet.has(k));
67
+ setSelectedKeys([...preserved, ...selectableIdsSet]);
83
68
  } else {
84
- setSelectedKeys([...new Set([...selectedKeys, ...groupKeys])]);
69
+ setSelectedKeys(selectedKeys.filter((k) => !selectableIdsSet.has(k)));
85
70
  }
86
71
  };
87
72
 
88
73
  return {
89
74
  getTheadCheckboxProps: (): CheckboxInputProps => ({
90
- onChange: handleToggleAll,
91
- checked: allSelectableSelected,
92
- indeterminate,
93
- disabled: headerSelectableKeys.length === 0,
75
+ checked: isAllSelected,
76
+ indeterminate: !isAllSelected && someSelected,
77
+ onChange: toggleAllRowSelected,
94
78
  }),
95
- getRowCheckboxProps: (key: string | number): CheckboxInputProps => {
96
- const groupStats = subtreeHelper.getSelectionStats(key);
97
-
79
+ getRowCheckboxProps: (key: TableRowEntryId, row: T): CheckboxInputProps => {
98
80
  return {
99
- onChange: () => handleToggleRow(key),
100
- checked: isGroupFullySelected(key),
101
- indeterminate:
102
- groupStats.selectedCount > 0 &&
103
- groupStats.selectedCount < groupStats.selectableCount,
104
- disabled: disabledKeysSet.has(key),
81
+ onChange: () => handleToggleRow(key, row),
82
+ checked: selectedKeysSet.has(key),
83
+ indeterminate: false,
84
+ disabled: !canSelectTableRow(enableRowSelection, { row, id: key }),
105
85
  };
106
86
  },
107
87
  toggleSelection: handleToggleRow,
@@ -1,33 +1,51 @@
1
1
  import type { RadioInputProps } from "../../../../form/radio/radio-input/RadioInput";
2
+ import { consoleWarning } from "../../../../utils/helpers/consoleWarning";
3
+ import type { TableRowEntryId } from "../../root/DataTable.types";
4
+ import type { SelectedKeysT, SelectionProps } from "./selection.types";
5
+ import { canSelectTableRow } from "./selection.utils";
2
6
 
3
- type GetSingleSelectPropsArgs = {
4
- selectedKeysSet: Set<string | number>;
5
- setSelectedKeys: (keys: (string | number)[]) => void;
6
- disabledKeysSet: Set<string | number>;
7
+ type GetSingleSelectPropsArgs<T> = {
8
+ selectedKeysSet: Set<TableRowEntryId>;
9
+ setSelectedKeys: (keys: SelectedKeysT) => void;
7
10
  name: string;
8
- };
11
+ } & Pick<SelectionProps<T>, "enableRowSelection">;
9
12
 
10
- function getSingleSelectProps({
13
+ function getSingleSelectProps<T>({
11
14
  selectedKeysSet,
12
15
  setSelectedKeys,
13
- disabledKeysSet,
14
16
  name,
15
- }: GetSingleSelectPropsArgs) {
16
- const handleSelectionChange = (key: string | number) => {
17
- if (disabledKeysSet.has(key)) {
17
+ enableRowSelection,
18
+ }: GetSingleSelectPropsArgs<T>) {
19
+ const handleSelectionChange = (key: TableRowEntryId, row: T) => {
20
+ if (!row) {
21
+ consoleWarning(
22
+ `Row data is undefined for key ${key}. This may cause issues with selection if enableRowSelection is used.`,
23
+ );
24
+ }
25
+ if (!canSelectTableRow(enableRowSelection, { row, id: key })) {
18
26
  return;
19
27
  }
28
+
20
29
  setSelectedKeys([key]);
21
30
  };
22
31
 
23
32
  return {
24
- getRowRadioProps: (key: string | number): RadioInputProps => ({
25
- checked: selectedKeysSet.has(key),
26
- onChange: () => handleSelectionChange(key),
27
- disabled: disabledKeysSet.has(key),
28
- value: key,
29
- name,
30
- }),
33
+ getRowRadioProps: (key: TableRowEntryId, row: T): RadioInputProps => {
34
+ const isSelectionDisabled = !canSelectTableRow(enableRowSelection, {
35
+ row,
36
+ id: key,
37
+ });
38
+
39
+ return {
40
+ checked: selectedKeysSet.has(key),
41
+ onChange: isSelectionDisabled
42
+ ? () => null
43
+ : () => handleSelectionChange(key, row),
44
+ disabled: isSelectionDisabled,
45
+ value: key,
46
+ name,
47
+ };
48
+ },
31
49
  toggleSelection: handleSelectionChange,
32
50
  };
33
51
  }
@@ -1,9 +1,11 @@
1
1
  import type { CheckboxInputProps } from "../../../../form/checkbox/checkbox-input/CheckboxInput";
2
2
  import type { RadioInputProps } from "../../../../form/radio/radio-input/RadioInput";
3
+ import type { TableRowEntryId } from "../../root/DataTable.types";
3
4
 
4
- type SelectedKeysT = (string | number)[];
5
+ type SelectedKeysT = TableRowEntryId[];
5
6
 
6
- type SelectionProps = {
7
+ // TODO: Remove `any` if possible
8
+ type SelectionProps<T = any> = {
7
9
  /**
8
10
  * Enables selection of rows.
9
11
  *
@@ -14,7 +16,7 @@ type SelectionProps = {
14
16
  *
15
17
  * @default "none"
16
18
  */
17
- selectionMode?: "none" | "single" | "multiple";
19
+ selectionMode: "none" | "single" | "multiple";
18
20
  /**
19
21
  * Controlled selected keys. Should be used in conjunction with `onSelectionChange`.
20
22
  */
@@ -28,45 +30,43 @@ type SelectionProps = {
28
30
  */
29
31
  onSelectionChange?: (keys: SelectedKeysT) => void;
30
32
  /**
31
- * Keys that should be disabled for selection. These keys will not be selectable and will be styled as disabled.
33
+ * Callback to determine if a row should be enabled for selection.
32
34
  *
33
35
  *
34
- * TODO: Consider making this optionally a callback with (rowData:T) => boolean, to allow for more dynamic disabling of selection based on row data.
36
+ * If set to a boolean, it will enable selection for all rows when true, and disable selection for all rows when false.
35
37
  */
36
- disabledSelectionKeys?: SelectedKeysT;
38
+ enableRowSelection?:
39
+ | (({ row, id }: { row: T; id: TableRowEntryId }) => boolean)
40
+ | boolean;
37
41
  /**
38
- * If true, stops clicking a row from toggling its selection state. This can be used if you want to only allow selection through the checkboxes/radios, and not have the entire row be clickable for selection.
39
- *
40
- * @default false
42
+ * Determines if selection is triggered by clicking the row or the selection control (checkbox/radio).
43
+ * @default "row"
41
44
  */
42
- disableRowSelectionOnClick?: boolean;
45
+ selectionTrigger?: "row" | "control";
43
46
  };
44
47
 
45
48
  type NoneSelection = {
46
49
  selectionMode: "none";
47
50
  selectedKeys: SelectedKeysT;
48
- disabledSelectionKeys: SelectedKeysT;
49
51
  };
50
52
 
51
53
  type SingleSelection = {
52
54
  selectionMode: "single";
53
55
  selectedKeys: SelectedKeysT;
54
- disabledSelectionKeys: SelectedKeysT;
55
- getRowRadioProps: (key: string | number) => RadioInputProps;
56
- toggleSelection: (key: string | number) => void;
56
+ getRowRadioProps: (key: TableRowEntryId, row: any) => RadioInputProps;
57
+ toggleSelection: (key: TableRowEntryId, row: any) => void;
57
58
  };
58
59
 
59
60
  type MultipleSelection = {
60
61
  selectionMode: "multiple";
61
62
  selectedKeys: SelectedKeysT;
62
- disabledSelectionKeys: SelectedKeysT;
63
63
  getTheadCheckboxProps: () => CheckboxInputProps;
64
- getRowCheckboxProps: (key: string | number) => CheckboxInputProps;
65
- toggleSelection: (key: string | number) => void;
64
+ getRowCheckboxProps: (key: TableRowEntryId, row: any) => CheckboxInputProps;
65
+ toggleSelection: (key: TableRowEntryId, row: any) => void;
66
66
  };
67
67
 
68
68
  type TableSelectionBase = {
69
- isRowSelected: (rowId: string | number) => boolean;
69
+ isRowSelected: (rowId: TableRowEntryId) => boolean;
70
70
  };
71
71
 
72
72
  type TableSelection = TableSelectionBase &
@@ -75,8 +75,8 @@ type TableSelection = TableSelectionBase &
75
75
  export type {
76
76
  MultipleSelection,
77
77
  NoneSelection,
78
+ SelectedKeysT,
78
79
  SelectionProps,
79
80
  SingleSelection,
80
81
  TableSelection,
81
- SelectedKeysT,
82
82
  };
@@ -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 };
@@ -121,16 +121,70 @@ function prepareCellFocus(cell: Element): HTMLElement | null {
121
121
  /**
122
122
  * Applies focus and scroll to an element.
123
123
  */
124
+ function getStickyOffsets(element: HTMLElement): {
125
+ stickyOffsetStart: number;
126
+ stickyOffsetEnd: number;
127
+ stickyHeaderHeight: number;
128
+ } {
129
+ const table = element.closest(".aksel-data-table");
130
+
131
+ if (!table) {
132
+ return {
133
+ stickyOffsetStart: 0,
134
+ stickyOffsetEnd: 0,
135
+ stickyHeaderHeight: 0,
136
+ };
137
+ }
138
+
139
+ const stickyHeader = table.querySelector<HTMLElement>(
140
+ `.aksel-data-table__tr[data-sticky="true"]`,
141
+ );
142
+
143
+ const stickyNodesStart = table.querySelectorAll<HTMLElement>(
144
+ `.aksel-data-table__column-header[data-sticky="start"]`,
145
+ );
146
+
147
+ const stickyNodesEnd = table.querySelectorAll<HTMLElement>(
148
+ `.aksel-data-table__column-header[data-sticky="end"]`,
149
+ );
150
+
151
+ return {
152
+ stickyOffsetStart: Array.from(stickyNodesStart).reduce(
153
+ (offset, node) => offset + node.getBoundingClientRect().width,
154
+ 0,
155
+ ),
156
+ stickyOffsetEnd: Array.from(stickyNodesEnd).reduce(
157
+ (offset, node) => offset + node.getBoundingClientRect().width,
158
+ 0,
159
+ ),
160
+ stickyHeaderHeight: stickyHeader?.getBoundingClientRect().height ?? 0,
161
+ };
162
+ }
163
+
124
164
  function applyFocusAndScroll(element: HTMLElement): void {
125
- element.focus({
126
- preventScroll: true,
127
- });
128
-
129
- element.scrollIntoView({
130
- behavior: "smooth",
131
- block: "nearest",
132
- inline: "nearest",
133
- });
165
+ const { stickyOffsetStart, stickyOffsetEnd, stickyHeaderHeight } =
166
+ getStickyOffsets(element);
167
+
168
+ const originalScrollMarginInline = element.style.scrollMarginInline;
169
+ const originalScrollMarginBlockStart = element.style.scrollMarginBlockStart;
170
+
171
+ element.style.scrollMarginInline = `${stickyOffsetStart}px ${stickyOffsetEnd}px`;
172
+ element.style.scrollMarginBlockStart = `${stickyHeaderHeight}px`;
173
+
174
+ try {
175
+ element.focus({
176
+ preventScroll: true,
177
+ });
178
+
179
+ element.scrollIntoView({
180
+ behavior: "auto",
181
+ block: "nearest",
182
+ inline: "nearest",
183
+ });
184
+ } finally {
185
+ element.style.scrollMarginBlockStart = originalScrollMarginBlockStart;
186
+ element.style.scrollMarginInline = originalScrollMarginInline;
187
+ }
134
188
  }
135
189
 
136
190
  /**