@navikt/ds-react 8.10.1 → 8.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/data/table/column-header/DataTableColumnHeader.d.ts +1 -1
- package/cjs/data/table/column-header/DataTableColumnHeader.js +16 -11
- package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
- package/cjs/data/table/column-header/useTableColumnResize.d.ts +28 -4
- package/cjs/data/table/column-header/useTableColumnResize.js +144 -53
- package/cjs/data/table/column-header/useTableColumnResize.js.map +1 -1
- package/cjs/data/table/helpers/collectTableRowEntries.d.ts +24 -0
- package/cjs/data/table/helpers/collectTableRowEntries.js +35 -0
- package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -0
- package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
- package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js +112 -0
- package/cjs/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
- package/cjs/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
- package/cjs/data/table/helpers/selection/selection.types.d.ts +1 -0
- package/cjs/data/table/helpers/table-keyboard.d.ts +1 -2
- package/cjs/data/table/helpers/table-keyboard.js +1 -5
- package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
- package/cjs/data/table/hooks/useTableExpansion.d.ts +7 -6
- package/cjs/data/table/hooks/useTableExpansion.js +42 -15
- package/cjs/data/table/hooks/useTableExpansion.js.map +1 -1
- package/cjs/data/table/hooks/useTableItems.d.ts +33 -0
- package/cjs/data/table/hooks/useTableItems.js +74 -0
- package/cjs/data/table/hooks/useTableItems.js.map +1 -0
- package/cjs/data/table/hooks/useTableKeyboardNav.js +3 -3
- package/cjs/data/table/hooks/useTableKeyboardNav.js.map +1 -1
- package/cjs/data/table/hooks/useTableSelection.d.ts +3 -2
- package/cjs/data/table/hooks/useTableSelection.js +5 -4
- package/cjs/data/table/hooks/useTableSelection.js.map +1 -1
- package/cjs/data/table/root/DataTable.types.d.ts +5 -4
- package/cjs/data/table/root/DataTableAuto.d.ts +27 -1
- package/cjs/data/table/root/DataTableAuto.js +92 -50
- package/cjs/data/table/root/DataTableAuto.js.map +1 -1
- package/cjs/data/table/root/DataTableRoot.context.d.ts +5 -3
- package/cjs/data/table/root/DataTableRoot.context.js.map +1 -1
- package/cjs/data/table/root/DataTableRoot.js +6 -4
- package/cjs/data/table/root/DataTableRoot.js.map +1 -1
- package/cjs/data/table/tr/DataTableTr.js +30 -32
- package/cjs/data/table/tr/DataTableTr.js.map +1 -1
- package/cjs/form/checkbox/Checkbox.js +1 -0
- package/cjs/form/checkbox/Checkbox.js.map +1 -1
- package/cjs/form/radio/Radio.js +7 -1
- package/cjs/form/radio/Radio.js.map +1 -1
- package/cjs/modal/types.d.ts +8 -4
- package/esm/data/table/column-header/DataTableColumnHeader.d.ts +1 -1
- package/esm/data/table/column-header/DataTableColumnHeader.js +17 -12
- package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
- package/esm/data/table/column-header/useTableColumnResize.d.ts +28 -4
- package/esm/data/table/column-header/useTableColumnResize.js +145 -54
- package/esm/data/table/column-header/useTableColumnResize.js.map +1 -1
- package/esm/data/table/helpers/collectTableRowEntries.d.ts +24 -0
- package/esm/data/table/helpers/collectTableRowEntries.js +33 -0
- package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -0
- package/esm/data/table/helpers/selection/SelectionSubtreeHelper.d.ts +46 -0
- package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js +109 -0
- package/esm/data/table/helpers/selection/SelectionSubtreeHelper.js.map +1 -0
- package/esm/data/table/helpers/selection/getMultipleSelectProps.d.ts +3 -2
- package/esm/data/table/helpers/selection/getMultipleSelectProps.js +43 -19
- package/esm/data/table/helpers/selection/getMultipleSelectProps.js.map +1 -1
- package/esm/data/table/helpers/selection/selection.types.d.ts +1 -0
- package/esm/data/table/helpers/table-keyboard.d.ts +1 -2
- package/esm/data/table/helpers/table-keyboard.js +1 -5
- package/esm/data/table/helpers/table-keyboard.js.map +1 -1
- package/esm/data/table/hooks/useTableExpansion.d.ts +7 -6
- package/esm/data/table/hooks/useTableExpansion.js +42 -16
- package/esm/data/table/hooks/useTableExpansion.js.map +1 -1
- package/esm/data/table/hooks/useTableItems.d.ts +33 -0
- package/esm/data/table/hooks/useTableItems.js +69 -0
- package/esm/data/table/hooks/useTableItems.js.map +1 -0
- package/esm/data/table/hooks/useTableKeyboardNav.js +3 -3
- package/esm/data/table/hooks/useTableKeyboardNav.js.map +1 -1
- package/esm/data/table/hooks/useTableSelection.d.ts +3 -2
- package/esm/data/table/hooks/useTableSelection.js +5 -4
- package/esm/data/table/hooks/useTableSelection.js.map +1 -1
- package/esm/data/table/root/DataTable.types.d.ts +5 -4
- package/esm/data/table/root/DataTableAuto.d.ts +27 -1
- package/esm/data/table/root/DataTableAuto.js +94 -52
- package/esm/data/table/root/DataTableAuto.js.map +1 -1
- package/esm/data/table/root/DataTableRoot.context.d.ts +5 -3
- package/esm/data/table/root/DataTableRoot.context.js.map +1 -1
- package/esm/data/table/root/DataTableRoot.js +7 -5
- package/esm/data/table/root/DataTableRoot.js.map +1 -1
- package/esm/data/table/tr/DataTableTr.js +32 -34
- package/esm/data/table/tr/DataTableTr.js.map +1 -1
- package/esm/form/checkbox/Checkbox.js +1 -0
- package/esm/form/checkbox/Checkbox.js.map +1 -1
- package/esm/form/radio/Radio.js +7 -1
- package/esm/form/radio/Radio.js.map +1 -1
- package/esm/modal/types.d.ts +8 -4
- package/package.json +7 -7
- package/src/data/table/column-header/DataTableColumnHeader.tsx +26 -14
- package/src/data/table/column-header/useTableColumnResize.ts +209 -80
- package/src/data/table/helpers/collectTableRowEntries.ts +90 -0
- package/src/data/table/helpers/selection/SelectionSubtreeHelper.test.ts +66 -0
- package/src/data/table/helpers/selection/SelectionSubtreeHelper.ts +162 -0
- package/src/data/table/helpers/selection/getMultipleSelectProps.ts +57 -20
- package/src/data/table/helpers/selection/selection.types.ts +1 -0
- package/src/data/table/helpers/table-keyboard.ts +1 -6
- package/src/data/table/hooks/__tests__/useTableItems.test.ts +145 -0
- package/src/data/table/hooks/__tests__/useTableSelection.test.ts +132 -21
- package/src/data/table/hooks/useTableExpansion.tsx +68 -22
- package/src/data/table/hooks/useTableItems.ts +146 -0
- package/src/data/table/hooks/useTableKeyboardNav.ts +3 -3
- package/src/data/table/hooks/useTableSelection.ts +10 -6
- package/src/data/table/root/DataTable.types.ts +5 -4
- package/src/data/table/root/DataTableAuto.test.tsx +244 -0
- package/src/data/table/root/DataTableAuto.tsx +260 -141
- package/src/data/table/root/DataTableRoot.context.ts +4 -2
- package/src/data/table/root/DataTableRoot.tsx +22 -16
- package/src/data/table/tr/DataTableTr.tsx +48 -47
- package/src/form/checkbox/Checkbox.tsx +1 -0
- package/src/form/radio/Radio.tsx +7 -1
- package/src/modal/types.ts +8 -4
|
@@ -17,7 +17,13 @@ const items: Item[] = [
|
|
|
17
17
|
{ id: "c", name: "Charlie" },
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const visibleRowIds = items.map((item) => item.id);
|
|
21
|
+
const childRowIdsById = new Map<string | number, (string | number)[]>([
|
|
22
|
+
["a", ["a1", "a2"]],
|
|
23
|
+
["a1", []],
|
|
24
|
+
["a2", ["a2a"]],
|
|
25
|
+
["a2a", []],
|
|
26
|
+
]);
|
|
21
27
|
|
|
22
28
|
function asSingle(result: {
|
|
23
29
|
current: UseTableSelectionReturn;
|
|
@@ -37,7 +43,7 @@ describe("useTableSelection", () => {
|
|
|
37
43
|
const { result } = renderHook(() =>
|
|
38
44
|
useTableSelection({
|
|
39
45
|
selectionMode: "none",
|
|
40
|
-
|
|
46
|
+
visibleRowIds,
|
|
41
47
|
}),
|
|
42
48
|
);
|
|
43
49
|
|
|
@@ -51,7 +57,7 @@ describe("useTableSelection", () => {
|
|
|
51
57
|
const { result } = renderHook(() =>
|
|
52
58
|
useTableSelection({
|
|
53
59
|
selectionMode: "single",
|
|
54
|
-
|
|
60
|
+
visibleRowIds,
|
|
55
61
|
}),
|
|
56
62
|
);
|
|
57
63
|
|
|
@@ -64,7 +70,7 @@ describe("useTableSelection", () => {
|
|
|
64
70
|
const { result } = renderHook(() =>
|
|
65
71
|
useTableSelection({
|
|
66
72
|
selectionMode: "single",
|
|
67
|
-
|
|
73
|
+
visibleRowIds,
|
|
68
74
|
onSelectionChange: onChange,
|
|
69
75
|
}),
|
|
70
76
|
);
|
|
@@ -83,7 +89,7 @@ describe("useTableSelection", () => {
|
|
|
83
89
|
const { result } = renderHook(() =>
|
|
84
90
|
useTableSelection({
|
|
85
91
|
selectionMode: "single",
|
|
86
|
-
|
|
92
|
+
visibleRowIds,
|
|
87
93
|
defaultSelectedKeys: ["a"],
|
|
88
94
|
}),
|
|
89
95
|
);
|
|
@@ -101,7 +107,7 @@ describe("useTableSelection", () => {
|
|
|
101
107
|
const { result } = renderHook(() =>
|
|
102
108
|
useTableSelection({
|
|
103
109
|
selectionMode: "single",
|
|
104
|
-
|
|
110
|
+
visibleRowIds,
|
|
105
111
|
defaultSelectedKeys: ["a"],
|
|
106
112
|
}),
|
|
107
113
|
);
|
|
@@ -119,7 +125,7 @@ describe("useTableSelection", () => {
|
|
|
119
125
|
const { result } = renderHook(() =>
|
|
120
126
|
useTableSelection({
|
|
121
127
|
selectionMode: "single",
|
|
122
|
-
|
|
128
|
+
visibleRowIds,
|
|
123
129
|
disabledSelectionKeys: ["b"],
|
|
124
130
|
}),
|
|
125
131
|
);
|
|
@@ -133,7 +139,7 @@ describe("useTableSelection", () => {
|
|
|
133
139
|
({ selectedKeys }) =>
|
|
134
140
|
useTableSelection({
|
|
135
141
|
selectionMode: "single",
|
|
136
|
-
|
|
142
|
+
visibleRowIds,
|
|
137
143
|
selectedKeys,
|
|
138
144
|
}),
|
|
139
145
|
{ initialProps: { selectedKeys: ["a"] as (string | number)[] } },
|
|
@@ -151,7 +157,7 @@ describe("useTableSelection", () => {
|
|
|
151
157
|
const { result } = renderHook(() =>
|
|
152
158
|
useTableSelection({
|
|
153
159
|
selectionMode: "multiple",
|
|
154
|
-
|
|
160
|
+
visibleRowIds,
|
|
155
161
|
}),
|
|
156
162
|
);
|
|
157
163
|
|
|
@@ -164,7 +170,7 @@ describe("useTableSelection", () => {
|
|
|
164
170
|
const { result } = renderHook(() =>
|
|
165
171
|
useTableSelection({
|
|
166
172
|
selectionMode: "multiple",
|
|
167
|
-
|
|
173
|
+
visibleRowIds,
|
|
168
174
|
}),
|
|
169
175
|
);
|
|
170
176
|
|
|
@@ -189,7 +195,7 @@ describe("useTableSelection", () => {
|
|
|
189
195
|
const { result } = renderHook(() =>
|
|
190
196
|
useTableSelection({
|
|
191
197
|
selectionMode: "multiple",
|
|
192
|
-
|
|
198
|
+
visibleRowIds,
|
|
193
199
|
defaultSelectedKeys: ["a", "b"],
|
|
194
200
|
}),
|
|
195
201
|
);
|
|
@@ -207,7 +213,7 @@ describe("useTableSelection", () => {
|
|
|
207
213
|
const { result } = renderHook(() =>
|
|
208
214
|
useTableSelection({
|
|
209
215
|
selectionMode: "multiple",
|
|
210
|
-
|
|
216
|
+
visibleRowIds,
|
|
211
217
|
}),
|
|
212
218
|
);
|
|
213
219
|
|
|
@@ -220,11 +226,29 @@ describe("useTableSelection", () => {
|
|
|
220
226
|
expect(asMultiple(result).selectedKeys).toEqual(["a", "b", "c"]);
|
|
221
227
|
});
|
|
222
228
|
|
|
229
|
+
test("select all via thead includes hidden descendants for visible parents", () => {
|
|
230
|
+
const { result } = renderHook(() =>
|
|
231
|
+
useTableSelection({
|
|
232
|
+
selectionMode: "multiple",
|
|
233
|
+
visibleRowIds: ["a"],
|
|
234
|
+
childRowIdsById,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
act(() => {
|
|
239
|
+
asMultiple(result)
|
|
240
|
+
.getTheadCheckboxProps()
|
|
241
|
+
.onChange?.({} as React.ChangeEvent<HTMLInputElement>);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(asMultiple(result).selectedKeys).toEqual(["a", "a1", "a2", "a2a"]);
|
|
245
|
+
});
|
|
246
|
+
|
|
223
247
|
test("deselect all when all are selected", () => {
|
|
224
248
|
const { result } = renderHook(() =>
|
|
225
249
|
useTableSelection({
|
|
226
250
|
selectionMode: "multiple",
|
|
227
|
-
|
|
251
|
+
visibleRowIds,
|
|
228
252
|
defaultSelectedKeys: ["a", "b", "c"],
|
|
229
253
|
}),
|
|
230
254
|
);
|
|
@@ -238,11 +262,30 @@ describe("useTableSelection", () => {
|
|
|
238
262
|
expect(asMultiple(result).selectedKeys).toEqual([]);
|
|
239
263
|
});
|
|
240
264
|
|
|
265
|
+
test("deselect all clears hidden descendants for visible parents but preserves unrelated keys", () => {
|
|
266
|
+
const { result } = renderHook(() =>
|
|
267
|
+
useTableSelection({
|
|
268
|
+
selectionMode: "multiple",
|
|
269
|
+
visibleRowIds: ["a"],
|
|
270
|
+
childRowIdsById,
|
|
271
|
+
defaultSelectedKeys: ["a", "a1", "a2", "a2a", "external"],
|
|
272
|
+
}),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
act(() => {
|
|
276
|
+
asMultiple(result)
|
|
277
|
+
.getTheadCheckboxProps()
|
|
278
|
+
.onChange?.({} as React.ChangeEvent<HTMLInputElement>);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
expect(asMultiple(result).selectedKeys).toEqual(["external"]);
|
|
282
|
+
});
|
|
283
|
+
|
|
241
284
|
test("select all skips disabled keys", () => {
|
|
242
285
|
const { result } = renderHook(() =>
|
|
243
286
|
useTableSelection({
|
|
244
287
|
selectionMode: "multiple",
|
|
245
|
-
|
|
288
|
+
visibleRowIds,
|
|
246
289
|
disabledSelectionKeys: ["b"],
|
|
247
290
|
}),
|
|
248
291
|
);
|
|
@@ -260,7 +303,7 @@ describe("useTableSelection", () => {
|
|
|
260
303
|
const { result } = renderHook(() =>
|
|
261
304
|
useTableSelection({
|
|
262
305
|
selectionMode: "multiple",
|
|
263
|
-
|
|
306
|
+
visibleRowIds,
|
|
264
307
|
defaultSelectedKeys: ["a", "b", "c"],
|
|
265
308
|
disabledSelectionKeys: ["b"],
|
|
266
309
|
}),
|
|
@@ -279,7 +322,7 @@ describe("useTableSelection", () => {
|
|
|
279
322
|
const { result } = renderHook(() =>
|
|
280
323
|
useTableSelection({
|
|
281
324
|
selectionMode: "multiple",
|
|
282
|
-
|
|
325
|
+
visibleRowIds,
|
|
283
326
|
defaultSelectedKeys: ["a"],
|
|
284
327
|
}),
|
|
285
328
|
);
|
|
@@ -293,7 +336,7 @@ describe("useTableSelection", () => {
|
|
|
293
336
|
const { result } = renderHook(() =>
|
|
294
337
|
useTableSelection({
|
|
295
338
|
selectionMode: "multiple",
|
|
296
|
-
|
|
339
|
+
visibleRowIds,
|
|
297
340
|
defaultSelectedKeys: ["a", "b", "c"],
|
|
298
341
|
}),
|
|
299
342
|
);
|
|
@@ -307,7 +350,7 @@ describe("useTableSelection", () => {
|
|
|
307
350
|
const { result } = renderHook(() =>
|
|
308
351
|
useTableSelection({
|
|
309
352
|
selectionMode: "multiple",
|
|
310
|
-
|
|
353
|
+
visibleRowIds,
|
|
311
354
|
defaultSelectedKeys: ["a", "c"],
|
|
312
355
|
disabledSelectionKeys: ["b"],
|
|
313
356
|
}),
|
|
@@ -322,7 +365,7 @@ describe("useTableSelection", () => {
|
|
|
322
365
|
const { result } = renderHook(() =>
|
|
323
366
|
useTableSelection({
|
|
324
367
|
selectionMode: "multiple",
|
|
325
|
-
|
|
368
|
+
visibleRowIds,
|
|
326
369
|
defaultSelectedKeys: ["a", "b", "c"],
|
|
327
370
|
}),
|
|
328
371
|
);
|
|
@@ -340,7 +383,7 @@ describe("useTableSelection", () => {
|
|
|
340
383
|
const { result } = renderHook(() =>
|
|
341
384
|
useTableSelection({
|
|
342
385
|
selectionMode: "multiple",
|
|
343
|
-
|
|
386
|
+
visibleRowIds,
|
|
344
387
|
disabledSelectionKeys: ["b"],
|
|
345
388
|
}),
|
|
346
389
|
);
|
|
@@ -353,12 +396,80 @@ describe("useTableSelection", () => {
|
|
|
353
396
|
const { result } = renderHook(() =>
|
|
354
397
|
useTableSelection({
|
|
355
398
|
selectionMode: "multiple",
|
|
356
|
-
|
|
399
|
+
visibleRowIds,
|
|
357
400
|
disabledSelectionKeys: ["a", "b", "c"],
|
|
358
401
|
}),
|
|
359
402
|
);
|
|
360
403
|
|
|
361
404
|
expect(asMultiple(result).getTheadCheckboxProps().disabled).toBe(true);
|
|
362
405
|
});
|
|
406
|
+
|
|
407
|
+
test("parent rows show indeterminate when visible descendants are partially selected", () => {
|
|
408
|
+
const { result } = renderHook(() =>
|
|
409
|
+
useTableSelection({
|
|
410
|
+
selectionMode: "multiple",
|
|
411
|
+
visibleRowIds: ["a", "a1", "a2"],
|
|
412
|
+
childRowIdsById,
|
|
413
|
+
defaultSelectedKeys: ["a1"],
|
|
414
|
+
}),
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const parentProps = asMultiple(result).getRowCheckboxProps("a");
|
|
418
|
+
|
|
419
|
+
expect(parentProps.checked).toBe(false);
|
|
420
|
+
expect(parentProps.indeterminate).toBe(true);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("toggling a parent row selects and deselects its descendants", () => {
|
|
424
|
+
const { result } = renderHook(() =>
|
|
425
|
+
useTableSelection({
|
|
426
|
+
selectionMode: "multiple",
|
|
427
|
+
visibleRowIds: ["a", "a1", "a2"],
|
|
428
|
+
childRowIdsById,
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
act(() => {
|
|
433
|
+
asMultiple(result)
|
|
434
|
+
.getRowCheckboxProps("a")
|
|
435
|
+
.onChange?.({} as React.ChangeEvent<HTMLInputElement>);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
expect(asMultiple(result).selectedKeys).toEqual(["a", "a1", "a2", "a2a"]);
|
|
439
|
+
|
|
440
|
+
act(() => {
|
|
441
|
+
asMultiple(result)
|
|
442
|
+
.getRowCheckboxProps("a")
|
|
443
|
+
.onChange?.({} as React.ChangeEvent<HTMLInputElement>);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
expect(asMultiple(result).selectedKeys).toEqual([]);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("toggling a collapsed parent selects and deselects hidden descendants", () => {
|
|
450
|
+
const { result } = renderHook(() =>
|
|
451
|
+
useTableSelection({
|
|
452
|
+
selectionMode: "multiple",
|
|
453
|
+
visibleRowIds: ["a"],
|
|
454
|
+
childRowIdsById,
|
|
455
|
+
}),
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
act(() => {
|
|
459
|
+
asMultiple(result)
|
|
460
|
+
.getRowCheckboxProps("a")
|
|
461
|
+
.onChange?.({} as React.ChangeEvent<HTMLInputElement>);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
expect(asMultiple(result).selectedKeys).toEqual(["a", "a1", "a2", "a2a"]);
|
|
465
|
+
|
|
466
|
+
act(() => {
|
|
467
|
+
asMultiple(result)
|
|
468
|
+
.getRowCheckboxProps("a")
|
|
469
|
+
.onChange?.({} as React.ChangeEvent<HTMLInputElement>);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
expect(asMultiple(result).selectedKeys).toEqual([]);
|
|
473
|
+
});
|
|
363
474
|
});
|
|
364
475
|
});
|
|
@@ -1,17 +1,18 @@
|
|
|
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
5
|
|
|
5
6
|
type DataTableExpansionContextT = {
|
|
6
|
-
expandedIds: (string | number)[];
|
|
7
7
|
isExpanded: (id: string | number) => boolean;
|
|
8
|
+
isDetailsPanelExpandable: (id: string | number) => boolean;
|
|
8
9
|
toggleExpansion: (id: string | number) => void;
|
|
9
10
|
toggleAll: () => void;
|
|
10
11
|
isAllExpanded: boolean;
|
|
11
12
|
getDetailsPanelContent?: (row: unknown) => React.ReactNode;
|
|
12
13
|
getDetailsPanelHeight?: (row: unknown) => number | "auto";
|
|
13
|
-
showExpandAll
|
|
14
|
-
|
|
14
|
+
showExpandAll: boolean;
|
|
15
|
+
enableDetailsPanel: boolean;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const {
|
|
@@ -27,57 +28,98 @@ type TableExpansionOptions<T> = {
|
|
|
27
28
|
detailsPanelRowIds?: (string | number)[];
|
|
28
29
|
defaultDetailsPanelRowIds?: (string | number)[];
|
|
29
30
|
onDetailsPanelChange?: (ids: (string | number)[]) => void;
|
|
30
|
-
allRowKeys: (string | number)[];
|
|
31
31
|
getDetailsPanelContent?: (row: T) => React.ReactNode;
|
|
32
|
+
isDetailsPanelExpandable?: (rowData: T) => boolean;
|
|
32
33
|
getDetailsPanelHeight?: (row: T) => number | "auto";
|
|
33
34
|
showExpandAll?: boolean;
|
|
34
35
|
};
|
|
35
36
|
|
|
37
|
+
function getDataTableExpansionId(tableId: string, rowId: string | number) {
|
|
38
|
+
return `${tableId}-expansion-${rowId}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
function DataTableExpansionProvider<T>({
|
|
37
42
|
children,
|
|
38
43
|
detailsPanelRowIds,
|
|
39
44
|
defaultDetailsPanelRowIds = [],
|
|
40
45
|
onDetailsPanelChange,
|
|
41
|
-
allRowKeys,
|
|
42
46
|
getDetailsPanelContent,
|
|
47
|
+
isDetailsPanelExpandable,
|
|
43
48
|
getDetailsPanelHeight,
|
|
44
49
|
showExpandAll = false,
|
|
45
50
|
}: TableExpansionOptions<T> & { children: React.ReactNode }) {
|
|
46
51
|
const [expandedIds, setExpandedIds] = useControllableState({
|
|
47
52
|
value: detailsPanelRowIds,
|
|
48
53
|
defaultValue: defaultDetailsPanelRowIds,
|
|
54
|
+
onChange: onDetailsPanelChange,
|
|
49
55
|
});
|
|
50
56
|
|
|
57
|
+
/* TODO: False is just fallback until auto and root is merged */
|
|
58
|
+
const tableItemsContext = useTableItemsContext(false);
|
|
59
|
+
|
|
60
|
+
const { itemDetails } = tableItemsContext ?? {
|
|
61
|
+
itemDetails: new Map(),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const expandableIds = React.useMemo(() => {
|
|
65
|
+
if (!getDetailsPanelContent) {
|
|
66
|
+
return new Set<string | number>();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ids = new Set<string | number>();
|
|
70
|
+
|
|
71
|
+
for (const [rowData, { id, level }] of itemDetails.entries()) {
|
|
72
|
+
/* We only allow Master - Details pattern on top level rows */
|
|
73
|
+
if (level > 0) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!isDetailsPanelExpandable || isDetailsPanelExpandable(rowData)) {
|
|
78
|
+
ids.add(id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return ids;
|
|
83
|
+
}, [getDetailsPanelContent, isDetailsPanelExpandable, itemDetails]);
|
|
84
|
+
|
|
85
|
+
const isDetailsPanelExpandableById = useCallback(
|
|
86
|
+
(id: string | number) => expandableIds.has(id),
|
|
87
|
+
[expandableIds],
|
|
88
|
+
);
|
|
89
|
+
|
|
51
90
|
const isExpanded = useCallback(
|
|
52
|
-
(id: string | number) =>
|
|
53
|
-
|
|
91
|
+
(id: string | number) =>
|
|
92
|
+
isDetailsPanelExpandableById(id) && expandedIds.includes(id),
|
|
93
|
+
[expandedIds, isDetailsPanelExpandableById],
|
|
54
94
|
);
|
|
55
95
|
|
|
56
96
|
const toggleExpansion = useCallback(
|
|
57
97
|
(id: string | number) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
98
|
+
if (!isDetailsPanelExpandableById(id)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setExpandedIds((currentExpandedIds) =>
|
|
103
|
+
currentExpandedIds.includes(id)
|
|
104
|
+
? currentExpandedIds.filter((expandedId) => expandedId !== id)
|
|
105
|
+
: [...currentExpandedIds, id],
|
|
106
|
+
);
|
|
63
107
|
},
|
|
64
|
-
[
|
|
108
|
+
[isDetailsPanelExpandableById, setExpandedIds],
|
|
65
109
|
);
|
|
66
110
|
|
|
67
111
|
const isAllExpanded =
|
|
68
|
-
|
|
69
|
-
|
|
112
|
+
expandableIds.size > 0 &&
|
|
113
|
+
Array.from(expandableIds).every((key) => expandedIds.includes(key));
|
|
70
114
|
|
|
71
115
|
const toggleAll = useCallback(() => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
onDetailsPanelChange?.(next);
|
|
75
|
-
}, [isAllExpanded, allRowKeys, setExpandedIds, onDetailsPanelChange]);
|
|
116
|
+
setExpandedIds(isAllExpanded ? [] : Array.from(expandableIds));
|
|
117
|
+
}, [expandableIds, isAllExpanded, setExpandedIds]);
|
|
76
118
|
|
|
77
119
|
return (
|
|
78
120
|
<DataTableExpansionContextProvider
|
|
79
|
-
expandedIds={expandedIds}
|
|
80
121
|
isExpanded={isExpanded}
|
|
122
|
+
isDetailsPanelExpandable={isDetailsPanelExpandableById}
|
|
81
123
|
toggleExpansion={toggleExpansion}
|
|
82
124
|
toggleAll={toggleAll}
|
|
83
125
|
isAllExpanded={isAllExpanded}
|
|
@@ -90,11 +132,15 @@ function DataTableExpansionProvider<T>({
|
|
|
90
132
|
getDetailsPanelHeight as ((row: unknown) => number | "auto") | undefined
|
|
91
133
|
}
|
|
92
134
|
showExpandAll={showExpandAll}
|
|
93
|
-
|
|
135
|
+
enableDetailsPanel={!!getDetailsPanelContent}
|
|
94
136
|
>
|
|
95
137
|
{children}
|
|
96
138
|
</DataTableExpansionContextProvider>
|
|
97
139
|
);
|
|
98
140
|
}
|
|
99
141
|
|
|
100
|
-
export {
|
|
142
|
+
export {
|
|
143
|
+
DataTableExpansionProvider,
|
|
144
|
+
getDataTableExpansionId,
|
|
145
|
+
useDataTableExpansion,
|
|
146
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
|
+
import { createStrictContext } from "../../../utils/helpers";
|
|
3
|
+
import { useControllableState } from "../../../utils/hooks";
|
|
4
|
+
import {
|
|
5
|
+
type ItemDetail,
|
|
6
|
+
type TableRowEntryId,
|
|
7
|
+
collectTableRowEntries,
|
|
8
|
+
} from "../helpers/collectTableRowEntries";
|
|
9
|
+
|
|
10
|
+
type UseTableItemsArgs<T> = {
|
|
11
|
+
items: T[];
|
|
12
|
+
getRowId?: (rowData: T, index: number) => string | number;
|
|
13
|
+
/**
|
|
14
|
+
* Master - Detail pattern props
|
|
15
|
+
*/
|
|
16
|
+
/* expandedDetailsPanelIds?: (string | number)[];
|
|
17
|
+
defaultExpandedDetailsPanelIds?: (string | number)[];
|
|
18
|
+
isDetailsPanelExpandable?: (rowData: T) => boolean;
|
|
19
|
+
onDetailsPanelChange?: (ids: (string | number)[]) => void;
|
|
20
|
+
|
|
21
|
+
getDetailsPanelHeight?: (row: T) => number | "auto";
|
|
22
|
+
getDetailsPanelContent?: (row: T) => React.ReactNode; */
|
|
23
|
+
/**
|
|
24
|
+
* Expanded/Nested rows pattern props
|
|
25
|
+
*/
|
|
26
|
+
getSubRows?: (rowData: T) => T[];
|
|
27
|
+
expandedSubRowIds?: (string | number)[];
|
|
28
|
+
defaultExpandedSubRowIds?: (string | number)[];
|
|
29
|
+
isSubRowExpandable?: (rowData: T) => boolean;
|
|
30
|
+
onExpandedSubRowIdsChange?: (ids: (string | number)[]) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type useTableItemsReturn<T> = {
|
|
34
|
+
items: T[];
|
|
35
|
+
itemDetails: Map<T, ItemDetail<T>>;
|
|
36
|
+
/** Row ids for the rows currently rendered in the table body. */
|
|
37
|
+
visibleRowIds: TableRowEntryId[];
|
|
38
|
+
/** Direct child ids for each row, used to traverse selection groups lazily. */
|
|
39
|
+
childRowIdsById: Map<TableRowEntryId, TableRowEntryId[]>;
|
|
40
|
+
onExpandedSubRowIdsChange: (id: string | number) => void;
|
|
41
|
+
isSubRowExpanded: (id: string | number) => boolean;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function useTableItems<T>(args: UseTableItemsArgs<T>): useTableItemsReturn<T> {
|
|
45
|
+
const {
|
|
46
|
+
items,
|
|
47
|
+
expandedSubRowIds,
|
|
48
|
+
defaultExpandedSubRowIds,
|
|
49
|
+
getSubRows,
|
|
50
|
+
getRowId,
|
|
51
|
+
onExpandedSubRowIdsChange,
|
|
52
|
+
isSubRowExpandable,
|
|
53
|
+
} = args;
|
|
54
|
+
|
|
55
|
+
const [nestedSubRowsExpandedIds, setNestedSubRowsExpandedIds] =
|
|
56
|
+
useControllableState({
|
|
57
|
+
value: expandedSubRowIds,
|
|
58
|
+
defaultValue: defaultExpandedSubRowIds ?? [],
|
|
59
|
+
onChange: onExpandedSubRowIdsChange,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const expandedIdsSet = useMemo(
|
|
63
|
+
() => new Set(nestedSubRowsExpandedIds),
|
|
64
|
+
[nestedSubRowsExpandedIds],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const { itemDetails, visibleItems, visibleRowIds, childRowIdsById } =
|
|
68
|
+
useMemo(() => {
|
|
69
|
+
const { itemDetails: rowEntriesMap, childRowIdsById: _childRowIdsById } =
|
|
70
|
+
collectTableRowEntries({
|
|
71
|
+
items,
|
|
72
|
+
getRowId,
|
|
73
|
+
getSubRows,
|
|
74
|
+
isSubRowExpandable,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const localVisibleItems: T[] = [];
|
|
78
|
+
const localVisibleRowIds: TableRowEntryId[] = [];
|
|
79
|
+
|
|
80
|
+
const addVisibleRows = (rowData: T): TableRowEntryId[] => {
|
|
81
|
+
const details = rowEntriesMap.get(rowData);
|
|
82
|
+
|
|
83
|
+
if (!details) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
localVisibleItems.push(rowData);
|
|
88
|
+
localVisibleRowIds.push(details.id);
|
|
89
|
+
|
|
90
|
+
const visibleDescendantRowIds: TableRowEntryId[] = [];
|
|
91
|
+
|
|
92
|
+
if (expandedIdsSet.has(details.id)) {
|
|
93
|
+
for (const childRow of details.children) {
|
|
94
|
+
const childVisibleRowIds = addVisibleRows(childRow);
|
|
95
|
+
visibleDescendantRowIds.push(...childVisibleRowIds);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return [details.id, ...visibleDescendantRowIds];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
for (const rowData of items) {
|
|
103
|
+
addVisibleRows(rowData);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
visibleItems: localVisibleItems,
|
|
108
|
+
visibleRowIds: localVisibleRowIds,
|
|
109
|
+
childRowIdsById: _childRowIdsById,
|
|
110
|
+
itemDetails: rowEntriesMap,
|
|
111
|
+
};
|
|
112
|
+
}, [getSubRows, items, getRowId, isSubRowExpandable, expandedIdsSet]);
|
|
113
|
+
|
|
114
|
+
const handleExpandedSubRowIdChange = useCallback(
|
|
115
|
+
(id: string | number) => {
|
|
116
|
+
setNestedSubRowsExpandedIds((prev) =>
|
|
117
|
+
prev.includes(id)
|
|
118
|
+
? prev.filter((expandedId) => expandedId !== id)
|
|
119
|
+
: [...prev, id],
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
[setNestedSubRowsExpandedIds],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
items: visibleItems,
|
|
127
|
+
itemDetails,
|
|
128
|
+
visibleRowIds,
|
|
129
|
+
childRowIdsById,
|
|
130
|
+
onExpandedSubRowIdsChange: handleExpandedSubRowIdChange,
|
|
131
|
+
isSubRowExpanded: (id: string | number) => expandedIdsSet.has(id),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const { Provider: TableItemsProvider, useContext: useTableItemsContext } =
|
|
136
|
+
/* TODO: Can we type this better? */
|
|
137
|
+
createStrictContext<
|
|
138
|
+
Omit<useTableItemsReturn<any>, "visibleRowIds" | "childRowIdsById">
|
|
139
|
+
>({
|
|
140
|
+
name: "TableItemsContext",
|
|
141
|
+
errorMessage:
|
|
142
|
+
"useTableItemsContext must be used within a TableItemsProvider",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export { useTableItems, TableItemsProvider, useTableItemsContext };
|
|
146
|
+
export type { ItemDetail };
|
|
@@ -114,12 +114,12 @@ function useTableKeyboardNav({
|
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
const action = getNavigationAction(event);
|
|
118
|
+
if (!action) {
|
|
118
119
|
return;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
if (!action) {
|
|
122
|
+
if (shouldBlockNavigation(event)) {
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -10,8 +10,10 @@ import type {
|
|
|
10
10
|
} from "../helpers/selection/selection.types";
|
|
11
11
|
|
|
12
12
|
type UseTableSelectionArgs = SelectionProps & {
|
|
13
|
-
/*
|
|
14
|
-
|
|
13
|
+
/* Visible rows manage the header checkbox state and render selection cells. */
|
|
14
|
+
visibleRowIds: (string | number)[];
|
|
15
|
+
/* Direct child ids let selection walk nested rows lazily. */
|
|
16
|
+
childRowIdsById?: Map<string | number, (string | number)[]>;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
type UseTableSelectionReturn = {
|
|
@@ -25,7 +27,8 @@ function useTableSelection({
|
|
|
25
27
|
selectedKeys: selectedKeysProp,
|
|
26
28
|
onSelectionChange,
|
|
27
29
|
disabledSelectionKeys = [],
|
|
28
|
-
|
|
30
|
+
visibleRowIds = [],
|
|
31
|
+
childRowIdsById,
|
|
29
32
|
}: UseTableSelectionArgs): UseTableSelectionReturn {
|
|
30
33
|
const radioGroupName = useId();
|
|
31
34
|
|
|
@@ -72,7 +75,7 @@ function useTableSelection({
|
|
|
72
75
|
name: radioGroupName,
|
|
73
76
|
}),
|
|
74
77
|
},
|
|
75
|
-
renderSelection:
|
|
78
|
+
renderSelection: visibleRowIds.length !== 0,
|
|
76
79
|
};
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -85,10 +88,11 @@ function useTableSelection({
|
|
|
85
88
|
selectedKeys,
|
|
86
89
|
setSelectedKeys,
|
|
87
90
|
disabledKeysSet,
|
|
88
|
-
|
|
91
|
+
visibleRowIds,
|
|
92
|
+
childRowIdsById,
|
|
89
93
|
}),
|
|
90
94
|
},
|
|
91
|
-
renderSelection:
|
|
95
|
+
renderSelection: visibleRowIds.length !== 0,
|
|
92
96
|
};
|
|
93
97
|
}
|
|
94
98
|
|
|
@@ -12,11 +12,12 @@ type ColumnDefinition<T> = {
|
|
|
12
12
|
minWidth?: number | string;
|
|
13
13
|
maxWidth?: number | string;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Text alignment for cells in this column.
|
|
16
|
+
*
|
|
17
|
+
*
|
|
18
|
+
* @default "left"
|
|
18
19
|
*/
|
|
19
|
-
|
|
20
|
+
align?: "left" | "right" | "center";
|
|
20
21
|
/**
|
|
21
22
|
* Assigned to the cell's `th` element instead of `td` if true.
|
|
22
23
|
*
|