@navikt/ds-react 8.10.2 → 8.10.4

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 (200) hide show
  1. package/cjs/action-menu/ActionMenu.js +1 -1
  2. package/cjs/action-menu/ActionMenu.js.map +1 -1
  3. package/cjs/data/stories/Data.test-data.d.ts +24 -0
  4. package/cjs/data/stories/Data.test-data.js +1616 -0
  5. package/cjs/data/stories/Data.test-data.js.map +1 -0
  6. package/cjs/data/table/column-header/DataTableColumnHeader.d.ts +4 -1
  7. package/cjs/data/table/column-header/DataTableColumnHeader.js +4 -2
  8. package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  9. package/cjs/data/table/column-header/useTableColumnResize.d.ts +39 -14
  10. package/cjs/data/table/column-header/useTableColumnResize.js +37 -39
  11. package/cjs/data/table/column-header/useTableColumnResize.js.map +1 -1
  12. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +6 -0
  13. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js +32 -0
  14. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -0
  15. package/cjs/data/table/helpers/collectTableRowEntries.d.ts +13 -5
  16. package/cjs/data/table/helpers/collectTableRowEntries.js +26 -19
  17. package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -1
  18. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
  19. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js +112 -0
  20. package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
  21. package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
  22. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
  23. package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  24. package/cjs/data/table/helpers/selection/selection.types.d.ts +1 -0
  25. package/cjs/data/table/helpers/table-keyboard.d.ts +1 -2
  26. package/cjs/data/table/helpers/table-keyboard.js +1 -2
  27. package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
  28. package/cjs/data/table/hooks/useColumnOptions.js +18 -5
  29. package/cjs/data/table/hooks/useColumnOptions.js.map +1 -1
  30. package/cjs/data/table/hooks/useTableDetailsPanel.d.ts +62 -0
  31. package/cjs/data/table/hooks/{useTableExpansion.js → useTableDetailsPanel.js} +26 -19
  32. package/cjs/data/table/hooks/useTableDetailsPanel.js.map +1 -0
  33. package/cjs/data/table/hooks/useTableItems.d.ts +18 -17
  34. package/cjs/data/table/hooks/useTableItems.js +27 -15
  35. package/cjs/data/table/hooks/useTableItems.js.map +1 -1
  36. package/cjs/data/table/hooks/useTableSelection.d.ts +6 -3
  37. package/cjs/data/table/hooks/useTableSelection.js +10 -4
  38. package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
  39. package/cjs/data/table/index.d.ts +1 -2
  40. package/cjs/data/table/index.js +22 -12
  41. package/cjs/data/table/index.js.map +1 -1
  42. package/cjs/data/table/root/DataTable.types.d.ts +12 -10
  43. package/cjs/data/table/root/DataTableRoot.context.d.ts +5 -1
  44. package/cjs/data/table/root/DataTableRoot.context.js.map +1 -1
  45. package/cjs/data/table/root/DataTableRoot.d.ts +79 -115
  46. package/cjs/data/table/root/DataTableRoot.js +167 -39
  47. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  48. package/cjs/data/table/root/DataTableRoot.legacy.d.ts +177 -0
  49. package/cjs/data/table/root/DataTableRoot.legacy.js +104 -0
  50. package/cjs/data/table/root/DataTableRoot.legacy.js.map +1 -0
  51. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.d.ts +6 -0
  52. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js +21 -0
  53. package/cjs/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -0
  54. package/cjs/data/table/tr/DataTableTr.js +11 -11
  55. package/cjs/data/table/tr/DataTableTr.js.map +1 -1
  56. package/cjs/form/checkbox/Checkbox.js +1 -0
  57. package/cjs/form/checkbox/Checkbox.js.map +1 -1
  58. package/cjs/form/radio/Radio.js +7 -1
  59. package/cjs/form/radio/Radio.js.map +1 -1
  60. package/cjs/modal/types.d.ts +8 -4
  61. package/cjs/utils/components/dismissablelayer/DismissableLayer.js +1 -1
  62. package/cjs/utils/components/dismissablelayer/DismissableLayer.js.map +1 -1
  63. package/cjs/utils/components/floating/Floating.d.ts +16 -1
  64. package/cjs/utils/components/floating/Floating.js +50 -13
  65. package/cjs/utils/components/floating/Floating.js.map +1 -1
  66. package/cjs/utils/components/floating-menu/Menu.js +1 -1
  67. package/cjs/utils/components/floating-menu/Menu.js.map +1 -1
  68. package/cjs/utils/helpers/create-strict-context.js +1 -1
  69. package/cjs/utils/helpers/create-strict-context.js.map +1 -1
  70. package/cjs/utils/hooks/useControllableState.d.ts +5 -5
  71. package/cjs/utils/hooks/useControllableState.js.map +1 -1
  72. package/cjs/utils/hooks/useValueAsRef.js +1 -1
  73. package/cjs/utils/hooks/useValueAsRef.js.map +1 -1
  74. package/cjs/utils-external/hooks/useId.js +1 -1
  75. package/cjs/utils-external/hooks/useId.js.map +1 -1
  76. package/esm/action-menu/ActionMenu.js +1 -1
  77. package/esm/action-menu/ActionMenu.js.map +1 -1
  78. package/esm/data/stories/Data.test-data.d.ts +24 -0
  79. package/esm/data/stories/Data.test-data.js +1607 -0
  80. package/esm/data/stories/Data.test-data.js.map +1 -0
  81. package/esm/data/table/column-header/DataTableColumnHeader.d.ts +4 -1
  82. package/esm/data/table/column-header/DataTableColumnHeader.js +4 -2
  83. package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  84. package/esm/data/table/column-header/useTableColumnResize.d.ts +39 -14
  85. package/esm/data/table/column-header/useTableColumnResize.js +38 -40
  86. package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
  87. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.d.ts +6 -0
  88. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js +27 -0
  89. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -0
  90. package/esm/data/table/helpers/collectTableRowEntries.d.ts +13 -5
  91. package/esm/data/table/helpers/collectTableRowEntries.js +26 -19
  92. package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -1
  93. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
  94. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js +109 -0
  95. package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
  96. package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
  97. package/esm/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
  98. package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
  99. package/esm/data/table/helpers/selection/selection.types.d.ts +1 -0
  100. package/esm/data/table/helpers/table-keyboard.d.ts +1 -2
  101. package/esm/data/table/helpers/table-keyboard.js +1 -2
  102. package/esm/data/table/helpers/table-keyboard.js.map +1 -1
  103. package/esm/data/table/hooks/useColumnOptions.js +18 -5
  104. package/esm/data/table/hooks/useColumnOptions.js.map +1 -1
  105. package/esm/data/table/hooks/useTableDetailsPanel.d.ts +62 -0
  106. package/esm/data/table/hooks/useTableDetailsPanel.js +58 -0
  107. package/esm/data/table/hooks/useTableDetailsPanel.js.map +1 -0
  108. package/esm/data/table/hooks/useTableItems.d.ts +18 -17
  109. package/esm/data/table/hooks/useTableItems.js +27 -15
  110. package/esm/data/table/hooks/useTableItems.js.map +1 -1
  111. package/esm/data/table/hooks/useTableSelection.d.ts +6 -3
  112. package/esm/data/table/hooks/useTableSelection.js +10 -4
  113. package/esm/data/table/hooks/useTableSelection.js.map +1 -1
  114. package/esm/data/table/index.d.ts +1 -2
  115. package/esm/data/table/index.js +21 -1
  116. package/esm/data/table/index.js.map +1 -1
  117. package/esm/data/table/root/DataTable.types.d.ts +12 -10
  118. package/esm/data/table/root/DataTableRoot.context.d.ts +5 -1
  119. package/esm/data/table/root/DataTableRoot.context.js.map +1 -1
  120. package/esm/data/table/root/DataTableRoot.d.ts +79 -115
  121. package/esm/data/table/root/DataTableRoot.js +174 -37
  122. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  123. package/esm/data/table/root/DataTableRoot.legacy.d.ts +177 -0
  124. package/esm/data/table/root/DataTableRoot.legacy.js +59 -0
  125. package/esm/data/table/root/DataTableRoot.legacy.js.map +1 -0
  126. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.d.ts +6 -0
  127. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js +16 -0
  128. package/esm/data/table/sub-row-toggle/DataTableSubRowToggle.js.map +1 -0
  129. package/esm/data/table/tr/DataTableTr.js +11 -11
  130. package/esm/data/table/tr/DataTableTr.js.map +1 -1
  131. package/esm/form/checkbox/Checkbox.js +1 -0
  132. package/esm/form/checkbox/Checkbox.js.map +1 -1
  133. package/esm/form/radio/Radio.js +7 -1
  134. package/esm/form/radio/Radio.js.map +1 -1
  135. package/esm/modal/types.d.ts +8 -4
  136. package/esm/utils/components/dismissablelayer/DismissableLayer.js +1 -1
  137. package/esm/utils/components/dismissablelayer/DismissableLayer.js.map +1 -1
  138. package/esm/utils/components/floating/Floating.d.ts +16 -1
  139. package/esm/utils/components/floating/Floating.js +48 -13
  140. package/esm/utils/components/floating/Floating.js.map +1 -1
  141. package/esm/utils/components/floating-menu/Menu.js +2 -2
  142. package/esm/utils/components/floating-menu/Menu.js.map +1 -1
  143. package/esm/utils/helpers/create-strict-context.js +1 -1
  144. package/esm/utils/helpers/create-strict-context.js.map +1 -1
  145. package/esm/utils/hooks/useControllableState.d.ts +5 -5
  146. package/esm/utils/hooks/useControllableState.js.map +1 -1
  147. package/esm/utils/hooks/useValueAsRef.js +1 -1
  148. package/esm/utils/hooks/useValueAsRef.js.map +1 -1
  149. package/esm/utils-external/hooks/useId.js +1 -1
  150. package/esm/utils-external/hooks/useId.js.map +1 -1
  151. package/package.json +3 -3
  152. package/src/action-menu/ActionMenu.tsx +1 -1
  153. package/src/data/stories/Data.test-data.tsx +1703 -0
  154. package/src/data/table/column-header/DataTableColumnHeader.tsx +11 -7
  155. package/src/data/table/column-header/useTableColumnResize.ts +95 -54
  156. package/src/data/table/details-panel-row/DataTableDetailsPanelRow.tsx +53 -0
  157. package/src/data/table/helpers/collectTableRowEntries.ts +55 -31
  158. package/src/data/table/helpers/selection/SelectionSubtreeHelper.test.ts +66 -0
  159. package/src/data/table/helpers/selection/SelectionSubtreeHelper.ts +162 -0
  160. package/src/data/table/helpers/selection/getMultipleSelectProps.ts +57 -20
  161. package/src/data/table/helpers/selection/selection.types.ts +1 -0
  162. package/src/data/table/helpers/table-keyboard.ts +1 -2
  163. package/src/data/table/hooks/__tests__/useTableItems.test.ts +27 -6
  164. package/src/data/table/hooks/__tests__/useTableSelection.test.ts +182 -58
  165. package/src/data/table/hooks/useColumnOptions.ts +19 -5
  166. package/src/data/table/hooks/useTableDetailsPanel.tsx +182 -0
  167. package/src/data/table/hooks/useTableItems.ts +74 -60
  168. package/src/data/table/hooks/useTableSelection.ts +27 -12
  169. package/src/data/table/index.tsx +5 -3
  170. package/src/data/table/root/DataTable.types.ts +25 -10
  171. package/src/data/table/root/DataTableRoot.context.ts +5 -1
  172. package/src/data/table/root/DataTableRoot.legacy.tsx +297 -0
  173. package/src/data/table/root/DataTableRoot.tsx +483 -219
  174. package/src/data/table/sub-row-toggle/DataTableSubRowToggle.tsx +39 -0
  175. package/src/data/table/tr/DataTableTr.tsx +14 -13
  176. package/src/form/checkbox/Checkbox.tsx +1 -0
  177. package/src/form/radio/Radio.tsx +7 -1
  178. package/src/modal/types.ts +8 -4
  179. package/src/utils/components/dismissablelayer/DismissableLayer.tsx +1 -1
  180. package/src/utils/components/floating/Floating.tsx +56 -13
  181. package/src/utils/components/floating-menu/Menu.tsx +4 -1
  182. package/src/utils/helpers/create-strict-context.tsx +1 -1
  183. package/src/utils/hooks/useControllableState.ts +11 -8
  184. package/src/utils/hooks/useValueAsRef.ts +1 -1
  185. package/src/utils-external/hooks/useId.ts +1 -1
  186. package/cjs/data/table/hooks/useTableExpansion.d.ts +0 -29
  187. package/cjs/data/table/hooks/useTableExpansion.js.map +0 -1
  188. package/cjs/data/table/root/DataTableAuto.d.ts +0 -174
  189. package/cjs/data/table/root/DataTableAuto.js +0 -206
  190. package/cjs/data/table/root/DataTableAuto.js.map +0 -1
  191. package/esm/data/table/hooks/useTableExpansion.d.ts +0 -29
  192. package/esm/data/table/hooks/useTableExpansion.js +0 -51
  193. package/esm/data/table/hooks/useTableExpansion.js.map +0 -1
  194. package/esm/data/table/root/DataTableAuto.d.ts +0 -174
  195. package/esm/data/table/root/DataTableAuto.js +0 -170
  196. package/esm/data/table/root/DataTableAuto.js.map +0 -1
  197. package/src/data/table/hooks/__tests__/useTableExpansion.test.tsx +0 -115
  198. package/src/data/table/hooks/useTableExpansion.tsx +0 -141
  199. package/src/data/table/root/DataTableAuto.test.tsx +0 -118
  200. package/src/data/table/root/DataTableAuto.tsx +0 -603
@@ -17,11 +17,10 @@ import { type ResizeProps, useTableColumnResize } from "./useTableColumnResize";
17
17
 
18
18
  interface DataTableColumnHeaderProps
19
19
  extends ResizeProps, DataTableBaseCellProps {
20
- resizeHandler?: (
21
- event:
22
- | React.MouseEvent<HTMLButtonElement>
23
- | React.TouchEvent<HTMLButtonElement>,
24
- ) => void;
20
+ /**
21
+ * Accessible name of the column.
22
+ */
23
+ label: string;
25
24
  /**
26
25
  * Makes the column header sortable. The entire header cell content becomes
27
26
  * a clickable button when true.
@@ -59,15 +58,18 @@ const DataTableColumnHeader = forwardRef<
59
58
  {
60
59
  className,
61
60
  children,
61
+ label,
62
62
  sortable = false,
63
63
  sortDirection = "none",
64
64
  onSortClick,
65
+ resizable = true,
65
66
  style,
66
67
  width,
68
+ defaultWidth,
69
+ autoWidth,
67
70
  minWidth,
68
71
  maxWidth,
69
72
  onWidthChange,
70
- defaultWidth,
71
73
  colSpan,
72
74
  rowSpan,
73
75
  UNSAFE_isSelection,
@@ -80,9 +82,11 @@ const DataTableColumnHeader = forwardRef<
80
82
  const mergedRef = useMergeRefs(forwardedRef, thRef);
81
83
 
82
84
  const resizeResult = useTableColumnResize({
85
+ resizable,
83
86
  thRef,
84
87
  width,
85
88
  defaultWidth,
89
+ autoWidth,
86
90
  minWidth,
87
91
  maxWidth,
88
92
  onWidthChange,
@@ -140,7 +144,7 @@ const DataTableColumnHeader = forwardRef<
140
144
  aria-label={
141
145
  resizeResult.isResizingWithKeyboard
142
146
  ? "Bruk pil venstre/høyre"
143
- : "Endre bredde"
147
+ : `Endre bredde ${label}`
144
148
  } // TODO Translate
145
149
  data-active={resizeResult.isResizingWithKeyboard}
146
150
  data-disable-keyboard-nav={resizeResult.isResizingWithKeyboard}
@@ -1,36 +1,64 @@
1
- import { type DOMAttributes, useCallback, useRef, useState } from "react";
1
+ import {
2
+ type DOMAttributes,
3
+ useCallback,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from "react";
2
8
  import { useControllableState } from "../../../utils/hooks";
3
9
  import { useDataTableContext } from "../root/DataTableRoot.context";
4
10
 
5
- type ColumnWidth = number | string;
6
-
7
11
  type ResizeProps = {
12
+ // If/when we add support for composition, consider mentioning that resizing only works on first row in thead.
8
13
  /**
9
- * Controlled width of the column.
14
+ * Whether the column should be resizable by the user.
10
15
  *
11
- * Should only be used to fully control column width state. Otherwise, use `defaultWidth` and let the component handle resizing.
16
+ * **NB:** This is always disabled when `layout="auto"` on the root component.
17
+ * @default true
18
+ */
19
+ resizable?: boolean;
20
+ /**
21
+ * Whether the column should automatically resize to fit its content. **Runs only once.**
22
+ *
23
+ * `onWidthChange` will be called with the new size. `minWidth` and `maxWidth` will be respected.
24
+ *
25
+ * If you don't need manual resizing support and want most of the columns to resize automatically,
26
+ * consider using `layout="auto"` on the root instead for better performance.
27
+ *
28
+ * **NB:** This can cause a layout shift. Set a good initial width with `width` or `defaultWidth` to mitigate this.
29
+ */
30
+ autoWidth?: boolean;
31
+ /**
32
+ * Minimum width of the column when resizing. Only used when `resizable` or `autoWidth` is enabled.
33
+ * @default 40
12
34
  */
13
- width?: ColumnWidth;
35
+ minWidth?: number;
14
36
  /**
15
- * Initial width of the column. Only used when `width` is not set.
37
+ * Maximum width of the column when resizing. Only used when `resizable` or `autoWidth` is enabled.
16
38
  */
17
- defaultWidth?: ColumnWidth;
39
+ maxWidth?: number;
40
+ // TODO: Consider "allowing" %-width on last column, if we find a solution to the overflow issue (width becomes 0px).
18
41
  /**
19
- * Minimum width of the column.
42
+ * Controlled width of the column. Does not respect `minWidth` and `maxWidth`.
20
43
  *
21
- * Should be used in conjunction with `width` or `defaultWidth` to set limits when resizing.
44
+ * Should only be used to fully control column width state. Otherwise, use `defaultWidth` and let the component handle resizing.
45
+ *
46
+ * **NB:** Percentage as initial width does not work well with resizing.
22
47
  */
23
- minWidth?: ColumnWidth;
48
+ width?: number | string;
24
49
  /**
25
- * Maximum width of the column.
50
+ * Initial width of the column. Only used when `width` is not set and `resizable` is true.
51
+ * Does not respect `minWidth` and `maxWidth`.
26
52
  *
27
- * Should be used in conjunction with `width` or `defaultWidth` to set limits when resizing.
53
+ * **NB:** Percentage as initial width does not work well with resizing.
54
+ * @default 140px
28
55
  */
29
- maxWidth?: ColumnWidth;
56
+ defaultWidth?: number | string;
30
57
  /**
31
58
  * Called when the column width changes.
59
+ * @param width New width in pixels.
32
60
  */
33
- onWidthChange?: (width: ColumnWidth) => void;
61
+ onWidthChange?: (width: number) => void;
34
62
  /**
35
63
  * Forwarded styles
36
64
  */
@@ -41,7 +69,12 @@ type ResizeProps = {
41
69
  colSpan?: number;
42
70
  };
43
71
 
44
- type TableColumnResizeArgs = ResizeProps & {
72
+ type WithUndefined<T> = {
73
+ [K in keyof T]: T[K] | undefined;
74
+ };
75
+ type Unomittable<T> = WithUndefined<Required<T>>;
76
+
77
+ type TableColumnResizeArgs = Unomittable<ResizeProps> & {
45
78
  thRef: React.RefObject<HTMLTableCellElement | null>;
46
79
  };
47
80
 
@@ -74,9 +107,11 @@ function useTableColumnResize(
74
107
  args: TableColumnResizeArgs,
75
108
  ): TableColumnResizeResult {
76
109
  const {
110
+ resizable,
77
111
  thRef,
78
112
  width: userWidth,
79
113
  defaultWidth,
114
+ autoWidth,
80
115
  onWidthChange,
81
116
  maxWidth = Infinity,
82
117
  minWidth = 40,
@@ -89,7 +124,7 @@ function useTableColumnResize(
89
124
  const [isResizingWithKeyboard, setIsResizingWithKeyboard] = useState(false);
90
125
  const ignoreNextOnClick = useRef(false);
91
126
 
92
- const [width, _setWidth] = useControllableState({
127
+ const [width, setWidth] = useControllableState({
93
128
  value: userWidth,
94
129
  defaultValue: defaultWidth ?? (colSpan ?? 1) * 140,
95
130
  /**
@@ -100,14 +135,26 @@ function useTableColumnResize(
100
135
  onChange: onWidthChange,
101
136
  });
102
137
 
103
- const setWidth = useCallback(
138
+ const setClampedWidth = useCallback(
104
139
  (newWidth: number) => {
105
- const min = parseWidth(minWidth) ?? 0;
106
- const max = parseWidth(maxWidth) ?? Infinity;
107
- const clamped = Math.min(Math.max(newWidth, min), max);
108
- _setWidth(clamped);
140
+ setWidth(Math.min(Math.max(newWidth, minWidth), maxWidth));
141
+ },
142
+ [minWidth, maxWidth, setWidth],
143
+ );
144
+
145
+ // biome-ignore lint/correctness/useExhaustiveDependencies: We only want to run this on mount and when autoWidth changes
146
+ useEffect(
147
+ function autoResizeColumn() {
148
+ if (!autoWidth) {
149
+ return;
150
+ }
151
+
152
+ const newColumnWidth = getAutoColumnWidth(thRef);
153
+ if (newColumnWidth) {
154
+ setClampedWidth(newColumnWidth);
155
+ }
109
156
  },
110
- [minWidth, maxWidth, _setWidth],
157
+ [autoWidth], // eslint-disable-line react-hooks/exhaustive-deps
111
158
  );
112
159
 
113
160
  const handleOnClick: DOMAttributes<HTMLButtonElement>["onClick"] =
@@ -135,19 +182,19 @@ function useTableColumnResize(
135
182
  if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
136
183
  event.preventDefault();
137
184
  const delta = event.key === "ArrowRight" ? 20 : -20;
138
- setWidth(currentWidth + delta);
185
+ setClampedWidth(currentWidth + delta);
139
186
  return;
140
187
  }
141
188
  if (event.key === "Home") {
142
189
  event.preventDefault();
143
- setWidth(0); // will fall back to minWidth
190
+ setClampedWidth(0); // will fall back to minWidth
144
191
  return;
145
192
  }
146
193
  if (event.key === "End") {
147
194
  event.preventDefault();
148
- const autoWidth = getAutoColumnWidth(thRef);
149
- if (autoWidth && autoWidth > currentWidth) {
150
- setWidth(autoWidth);
195
+ const newWidth = getAutoColumnWidth(thRef);
196
+ if (newWidth && newWidth > currentWidth) {
197
+ setClampedWidth(newWidth);
151
198
  }
152
199
  return;
153
200
  }
@@ -155,7 +202,7 @@ function useTableColumnResize(
155
202
  setIsResizingWithKeyboard(false);
156
203
  }
157
204
  },
158
- [isResizingWithKeyboard, setWidth, thRef],
205
+ [isResizingWithKeyboard, setClampedWidth, thRef],
159
206
  );
160
207
 
161
208
  const startResize = useCallback(
@@ -166,19 +213,16 @@ function useTableColumnResize(
166
213
  const currentWidth = thRef.current?.offsetWidth ?? 0;
167
214
  const newWidth = startWidth + (clientX - startX);
168
215
 
169
- const min = parseWidth(minWidth) ?? 0;
170
- const max = parseWidth(maxWidth) ?? Infinity;
171
-
172
- if (newWidth > max) {
216
+ if (newWidth > maxWidth) {
173
217
  setWidth(newWidth < currentWidth ? newWidth : currentWidth);
174
218
  return;
175
219
  }
176
- if (newWidth < min) {
220
+ if (newWidth < minWidth) {
177
221
  setWidth(newWidth > currentWidth ? newWidth : currentWidth);
178
222
  return;
179
223
  }
180
224
 
181
- setWidth(newWidth);
225
+ setClampedWidth(newWidth);
182
226
  }
183
227
 
184
228
  function onMouseMove(e: MouseEvent) {
@@ -206,7 +250,7 @@ function useTableColumnResize(
206
250
  document.addEventListener("touchend", cleanup, { once: true });
207
251
  document.addEventListener("touchcancel", cleanup, { once: true });
208
252
  },
209
- [maxWidth, minWidth, setWidth, thRef],
253
+ [maxWidth, minWidth, setWidth, setClampedWidth, thRef],
210
254
  );
211
255
 
212
256
  const handleMouseDown: DOMAttributes<HTMLButtonElement>["onMouseDown"] =
@@ -230,9 +274,9 @@ function useTableColumnResize(
230
274
  useCallback(() => {
231
275
  const newColumnWidth = getAutoColumnWidth(thRef);
232
276
  if (newColumnWidth) {
233
- setWidth(newColumnWidth);
277
+ setClampedWidth(newColumnWidth);
234
278
  }
235
- }, [setWidth, thRef]);
279
+ }, [setClampedWidth, thRef]);
236
280
 
237
281
  if (tableContext.layout !== "fixed") {
238
282
  return {
@@ -241,6 +285,16 @@ function useTableColumnResize(
241
285
  };
242
286
  }
243
287
 
288
+ if (!resizable) {
289
+ return {
290
+ style: {
291
+ ...style,
292
+ width,
293
+ },
294
+ enabled: false,
295
+ };
296
+ }
297
+
244
298
  return {
245
299
  style: {
246
300
  ...style,
@@ -259,20 +313,6 @@ function useTableColumnResize(
259
313
  };
260
314
  }
261
315
 
262
- function parseWidth(width: ColumnWidth | undefined): number | undefined {
263
- if (width == null) {
264
- return undefined;
265
- }
266
- if (typeof width === "number") {
267
- return width;
268
- }
269
- if (typeof width === "string") {
270
- const parsed = parseInt(width, 10);
271
- return Number.isNaN(parsed) ? undefined : parsed;
272
- }
273
- return undefined;
274
- }
275
-
276
316
  function getAutoColumnWidth(
277
317
  thRef: React.RefObject<HTMLTableCellElement | null>,
278
318
  ) {
@@ -285,7 +325,9 @@ function getAutoColumnWidth(
285
325
  }
286
326
 
287
327
  // Find needed width for header cell
288
- const contentWidth = thContent.scrollWidth;
328
+ const range = document.createRange();
329
+ range.selectNodeContents(thContent);
330
+ const contentWidth = range.getBoundingClientRect().width;
289
331
  const paddingElStyle = window.getComputedStyle(thPaddingEl);
290
332
  const thInlinePadding =
291
333
  parseInt(paddingElStyle.paddingLeft, 10) +
@@ -301,7 +343,6 @@ function getAutoColumnWidth(
301
343
  }
302
344
 
303
345
  // Find needed width for each cell in column in tbody and tfoot
304
- const range = document.createRange();
305
346
  let skipRows = 0;
306
347
  for (const row of rows) {
307
348
  // Skip rows where the cell in this column is covered by a rowspan from a previous row
@@ -0,0 +1,53 @@
1
+ import React from "react";
2
+ import {
3
+ getDataTableDetailsPanelId,
4
+ useDataTableDetailsPanel,
5
+ } from "../hooks/useTableDetailsPanel";
6
+ import { useDataTableContext } from "../root/DataTableRoot.context";
7
+
8
+ function DataTableDetailsPanelRow<T>({
9
+ rowId,
10
+ rowData,
11
+ }: {
12
+ rowId: string | number;
13
+ rowData: T;
14
+ }) {
15
+ const { tableId, fullWidthColSpan } = useDataTableContext();
16
+ const {
17
+ enableDetailsPanel,
18
+ isExpanded,
19
+ getDetailsPanelContent,
20
+ getDetailsPanelHeight,
21
+ } = useDataTableDetailsPanel();
22
+
23
+ if (!enableDetailsPanel) {
24
+ return null;
25
+ }
26
+
27
+ if (!isExpanded(rowId)) {
28
+ return null;
29
+ }
30
+
31
+ const content = getDetailsPanelContent?.(rowData);
32
+ const expansionId = getDataTableDetailsPanelId(tableId, rowId);
33
+
34
+ if (!content) {
35
+ return null;
36
+ }
37
+
38
+ const panelHeight = getDetailsPanelHeight?.(rowData);
39
+
40
+ const style: React.CSSProperties = panelHeight
41
+ ? { height: panelHeight, overflow: "auto" }
42
+ : { height: "auto" };
43
+
44
+ return (
45
+ <tr className="aksel-data-table__details-panel-row">
46
+ <td id={expansionId} colSpan={fullWidthColSpan}>
47
+ <div style={style}>{content}</div>
48
+ </td>
49
+ </tr>
50
+ );
51
+ }
52
+
53
+ export { DataTableDetailsPanelRow };
@@ -2,9 +2,9 @@ type TableRowEntryId = string | number;
2
2
 
3
3
  type CollectTableRowEntriesArgs<T> = {
4
4
  items: T[];
5
- getRowId?: (rowData: T, index: number) => TableRowEntryId;
6
- getSubRows?: (rowData: T) => T[];
7
- isSubRowExpandable?: (rowData: T) => boolean;
5
+ getRowId: (rowData: T, index: number) => TableRowEntryId;
6
+ getRows?: (rowData: T) => T[];
7
+ isRowExpandable?: (rowData: T) => boolean;
8
8
  };
9
9
 
10
10
  interface ItemDetail<T> {
@@ -14,45 +14,69 @@ interface ItemDetail<T> {
14
14
  children: readonly T[];
15
15
  }
16
16
 
17
+ type CollectTableRowEntriesReturn<T> = {
18
+ itemDetails: Map<T, ItemDetail<T>>;
19
+ /**
20
+ * Direct child ids for each row, used to traverse nested selection groups
21
+ * without storing every descendant list on each ancestor.
22
+ */
23
+ childRowIdsById: Map<TableRowEntryId, TableRowEntryId[]>;
24
+ };
25
+
17
26
  function collectTableRowEntries<T>({
18
27
  items,
19
28
  getRowId,
20
- getSubRows,
21
- isSubRowExpandable,
22
- }: CollectTableRowEntriesArgs<T>): Map<T, ItemDetail<T>> {
29
+ getRows,
30
+ isRowExpandable,
31
+ }: CollectTableRowEntriesArgs<T>): CollectTableRowEntriesReturn<T> {
23
32
  const itemDetailsMap = new Map<T, ItemDetail<T>>();
33
+ const childRowIdsById = new Map<TableRowEntryId, TableRowEntryId[]>();
24
34
 
25
- const traverseRows = (
26
- rows: T[],
35
+ const traverseRow = (
36
+ rowData: T,
37
+ rowIndex: number,
27
38
  level: number,
28
39
  parent: T | null,
29
- parentId?: TableRowEntryId,
30
- ) => {
31
- for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
32
- const rowData = rows[rowIndex];
33
- const rowId =
34
- getRowId?.(rowData, rowIndex) ??
35
- (parentId == null ? rowIndex : `${parentId}-${rowIndex}`);
36
- const isRowExpandable = isSubRowExpandable?.(rowData) ?? true;
37
- const children = (isRowExpandable ? getSubRows?.(rowData) : []) ?? [];
38
-
39
- itemDetailsMap.set(rowData, {
40
- id: rowId,
41
- level,
42
- parent,
43
- children,
44
- });
45
-
46
- if (children.length > 0) {
47
- traverseRows(children, level + 1, rowData, rowId);
48
- }
40
+ ): TableRowEntryId => {
41
+ const rowId = getRowId(rowData, rowIndex);
42
+
43
+ const children =
44
+ ((isRowExpandable?.(rowData) ?? true) ? getRows?.(rowData) : []) ?? [];
45
+
46
+ itemDetailsMap.set(rowData, {
47
+ id: rowId,
48
+ level,
49
+ parent,
50
+ children,
51
+ });
52
+
53
+ const childRowIds: TableRowEntryId[] = [];
54
+
55
+ for (let childIndex = 0; childIndex < children.length; childIndex++) {
56
+ const childRow = children[childIndex];
57
+ const childRowId = traverseRow(childRow, childIndex, level + 1, rowData);
58
+ childRowIds.push(childRowId);
49
59
  }
60
+
61
+ childRowIdsById.set(rowId, childRowIds);
62
+
63
+ return rowId;
50
64
  };
51
65
 
52
- traverseRows(items, 0, null);
66
+ for (let rowIndex = 0; rowIndex < items.length; rowIndex++) {
67
+ traverseRow(items[rowIndex], rowIndex, 0, null);
68
+ }
53
69
 
54
- return itemDetailsMap;
70
+ return {
71
+ itemDetails: itemDetailsMap,
72
+ childRowIdsById,
73
+ };
55
74
  }
56
75
 
57
76
  export { collectTableRowEntries };
58
- export type { CollectTableRowEntriesArgs, TableRowEntryId, ItemDetail };
77
+ export type {
78
+ CollectTableRowEntriesArgs,
79
+ CollectTableRowEntriesReturn,
80
+ TableRowEntryId,
81
+ ItemDetail,
82
+ };
@@ -0,0 +1,66 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { SelectionSubtreeHelper } from "./SelectionSubtreeHelper";
3
+
4
+ const childRowIdsById = new Map<string | number, (string | number)[]>([
5
+ ["a", ["a1", "a2"]],
6
+ ["a1", []],
7
+ ["a2", ["a2a"]],
8
+ ["a2a", []],
9
+ ["b", []],
10
+ ]);
11
+
12
+ describe("SelectionSubtreeHelper", () => {
13
+ test("returns selectable subtree keys without duplicates", () => {
14
+ const helper = new SelectionSubtreeHelper({
15
+ childRowIdsById,
16
+ disabledKeysSet: new Set(["a2"]),
17
+ selectedKeysSet: new Set(),
18
+ });
19
+
20
+ expect(helper.getSelectableKeys(["a", "a1"])).toEqual(["a", "a1", "a2a"]);
21
+ });
22
+
23
+ test("returns cached subtree selection stats", () => {
24
+ const helper = new SelectionSubtreeHelper({
25
+ childRowIdsById,
26
+ disabledKeysSet: new Set(["a2"]),
27
+ selectedKeysSet: new Set(["a", "a1", "a2a"]),
28
+ });
29
+
30
+ const firstStats = helper.getSelectionStats("a");
31
+ const secondStats = helper.getSelectionStats("a");
32
+
33
+ expect(firstStats).toEqual({ selectableCount: 3, selectedCount: 3 });
34
+ expect(secondStats).toBe(firstStats);
35
+ expect(helper.isFullySelected("a")).toBe(true);
36
+ });
37
+
38
+ test("handles deep trees iteratively", () => {
39
+ const depth = 12000;
40
+ const deepChildRowIdsById = new Map<string | number, (string | number)[]>();
41
+ const selectedKeysSet = new Set<string | number>();
42
+
43
+ for (let index = 0; index < depth; index++) {
44
+ const key = `node-${index}`;
45
+ const childKey = `node-${index + 1}`;
46
+
47
+ deepChildRowIdsById.set(key, index === depth - 1 ? [] : [childKey]);
48
+
49
+ if (index % 2 === 0) {
50
+ selectedKeysSet.add(key);
51
+ }
52
+ }
53
+
54
+ const helper = new SelectionSubtreeHelper({
55
+ childRowIdsById: deepChildRowIdsById,
56
+ disabledKeysSet: new Set(),
57
+ selectedKeysSet,
58
+ });
59
+
60
+ expect(helper.getSelectableKeys(["node-0"])).toHaveLength(depth);
61
+ expect(helper.getSelectionStats("node-0")).toEqual({
62
+ selectableCount: depth,
63
+ selectedCount: Math.ceil(depth / 2),
64
+ });
65
+ });
66
+ });