@jielga/tmdatagrid 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +397 -37
- package/dist/index.js +1550 -978
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/skills/columns/SKILL.md +1 -1
- package/skills/features/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +41 -6
- package/skills/options/SKILL.md +1 -1
- package/skills/server-side/SKILL.md +1 -1
- package/src/tmdatagrid/components/TMDataGrid.module.css +11 -1
- package/src/tmdatagrid/components/TMDataGrid.tsx +6 -0
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +7 -1
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +15 -0
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +87 -10
- package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +35 -0
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +135 -0
- package/src/tmdatagrid/components/TMDataGridFooter.module.css +8 -0
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +70 -42
- package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +48 -0
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +147 -0
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +6 -0
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +77 -1
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +83 -6
- package/src/tmdatagrid/components/TMDataGridTable.module.css +38 -4
- package/src/tmdatagrid/components/TMDataGridTable.tsx +396 -32
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +3 -0
- package/src/tmdatagrid/components/icons.ts +5 -0
- package/src/tmdatagrid/core/capabilities.ts +81 -11
- package/src/tmdatagrid/core/filterOperators.ts +25 -0
- package/src/tmdatagrid/core/grouping.ts +21 -0
- package/src/tmdatagrid/core/persistence.ts +12 -0
- package/src/tmdatagrid/core/rowSelection.ts +202 -0
- package/src/tmdatagrid/index.ts +26 -2
- package/src/tmdatagrid/useTMDataGrid.tsx +240 -35
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import { useCreateStore } from "@tanstack/react-store";
|
|
2
2
|
import {
|
|
3
|
+
aggregationFns,
|
|
3
4
|
type ColumnDef,
|
|
4
5
|
columnFacetingFeature,
|
|
5
6
|
columnFilteringFeature,
|
|
7
|
+
columnGroupingFeature,
|
|
6
8
|
columnOrderingFeature,
|
|
7
9
|
columnPinningFeature,
|
|
8
10
|
columnResizingFeature,
|
|
9
11
|
columnSizingFeature,
|
|
10
12
|
columnVisibilityFeature,
|
|
11
13
|
createColumnHelper,
|
|
14
|
+
createExpandedRowModel,
|
|
12
15
|
createFacetedMinMaxValues,
|
|
13
16
|
createFacetedRowModel,
|
|
14
17
|
createFacetedUniqueValues,
|
|
15
18
|
createFilteredRowModel,
|
|
19
|
+
createGroupedRowModel,
|
|
16
20
|
createPaginatedRowModel,
|
|
17
21
|
createSortedRowModel,
|
|
18
22
|
filterFns,
|
|
19
23
|
globalFilteringFeature,
|
|
20
24
|
metaHelper,
|
|
21
25
|
type RowData,
|
|
26
|
+
rowExpandingFeature,
|
|
22
27
|
rowPaginationFeature,
|
|
23
28
|
rowSelectionFeature,
|
|
24
29
|
rowSortingFeature,
|
|
@@ -30,7 +35,7 @@ import {
|
|
|
30
35
|
useTable,
|
|
31
36
|
} from "@tanstack/react-table";
|
|
32
37
|
import type { Store } from "@tanstack/store";
|
|
33
|
-
import { useEffect, useMemo, useState } from "react";
|
|
38
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
34
39
|
import {
|
|
35
40
|
type TMDataGridColumnType,
|
|
36
41
|
tmDataGridFilterFn,
|
|
@@ -45,12 +50,16 @@ import {
|
|
|
45
50
|
import {
|
|
46
51
|
readFeatureFlags,
|
|
47
52
|
type TMDataGridFeatureFlags,
|
|
48
|
-
type
|
|
53
|
+
type TMDataGridSelectionMode,
|
|
49
54
|
} from "./core/capabilities";
|
|
50
55
|
import {
|
|
51
56
|
createSelectColumn,
|
|
52
57
|
SELECT_COLUMN_ID,
|
|
53
58
|
} from "./components/TMDataGridSelectColumn";
|
|
59
|
+
import {
|
|
60
|
+
createGroupColumn,
|
|
61
|
+
GROUP_COLUMN_ID,
|
|
62
|
+
} from "./components/TMDataGridGroupColumn";
|
|
54
63
|
|
|
55
64
|
/**
|
|
56
65
|
* How long the grid waits after the last state change before writing to
|
|
@@ -105,9 +114,16 @@ export const tmDataGridFeatures = tableFeatures({
|
|
|
105
114
|
columnSizingFeature,
|
|
106
115
|
columnResizingFeature,
|
|
107
116
|
columnFacetingFeature,
|
|
117
|
+
columnGroupingFeature,
|
|
118
|
+
// Registered for grouping's sake rather than for tree data: the grouped row
|
|
119
|
+
// model builds the parent rows, and this is what flattens the expanded ones
|
|
120
|
+
// back into the flat list the body virtualizes.
|
|
121
|
+
rowExpandingFeature,
|
|
108
122
|
|
|
109
123
|
filteredRowModel: createFilteredRowModel(),
|
|
124
|
+
groupedRowModel: createGroupedRowModel(),
|
|
110
125
|
sortedRowModel: createSortedRowModel(),
|
|
126
|
+
expandedRowModel: createExpandedRowModel(),
|
|
111
127
|
paginatedRowModel: createPaginatedRowModel(),
|
|
112
128
|
facetedRowModel: createFacetedRowModel(),
|
|
113
129
|
facetedMinMaxValues: createFacetedMinMaxValues(),
|
|
@@ -115,6 +131,10 @@ export const tmDataGridFeatures = tableFeatures({
|
|
|
115
131
|
|
|
116
132
|
filterFns: { ...filterFns, tmDataGrid: tmDataGridFilterFn },
|
|
117
133
|
sortFns,
|
|
134
|
+
// The names `columnDef.aggregationFn` accepts. Only consulted for columns
|
|
135
|
+
// that ask for one — a column with no `aggregationFn` reports `undefined` on
|
|
136
|
+
// a group row, which is what keeps a plain "group by" free of aggregates.
|
|
137
|
+
aggregationFns,
|
|
118
138
|
|
|
119
139
|
tableMeta: metaHelper<TMDataGridTableMeta>(),
|
|
120
140
|
columnMeta: metaHelper<TMDataGridColumnMeta>(),
|
|
@@ -146,6 +166,20 @@ export type TMDataGridUiState = {
|
|
|
146
166
|
* `dataTransfer`, which browsers keep unreadable until the drop.
|
|
147
167
|
*/
|
|
148
168
|
draggedColumnId: string | null;
|
|
169
|
+
/**
|
|
170
|
+
* The single highlighted row — the one a detail panel would be showing. Its
|
|
171
|
+
* own concept, not a slice of `rowSelection`: under
|
|
172
|
+
* `selectionMode: "checkboxAndHighlight"` the two coexist, and TanStack's one
|
|
173
|
+
* selection map cannot hold both.
|
|
174
|
+
*
|
|
175
|
+
* Not pruned when the row is filtered out, paged away or dropped from `data`,
|
|
176
|
+
* matching how TanStack treats `rowSelection` — nothing there resets it
|
|
177
|
+
* either. The row simply renders unhighlighted, and highlights again if it
|
|
178
|
+
* comes back.
|
|
179
|
+
*/
|
|
180
|
+
highlightedRowId: string | null;
|
|
181
|
+
/** The pivot a shift-click extends from. See resolveRowSelectionClick. */
|
|
182
|
+
selectionAnchorRowId: string | null;
|
|
149
183
|
};
|
|
150
184
|
|
|
151
185
|
export type TMDataGridUiActions = {
|
|
@@ -155,6 +189,12 @@ export type TMDataGridUiActions = {
|
|
|
155
189
|
toggleColumnsPanel: () => void;
|
|
156
190
|
startColumnDrag: (columnId: string) => void;
|
|
157
191
|
endColumnDrag: () => void;
|
|
192
|
+
/**
|
|
193
|
+
* Moves the active row, or clears it with `null` — which is how a consumer
|
|
194
|
+
* closing its detail panel puts the grid back in step.
|
|
195
|
+
*/
|
|
196
|
+
setHighlightedRow: (rowId: string | null) => void;
|
|
197
|
+
setSelectionAnchor: (rowId: string | null) => void;
|
|
158
198
|
};
|
|
159
199
|
|
|
160
200
|
export type TMDataGridUiStore = Store<TMDataGridUiState, TMDataGridUiActions>;
|
|
@@ -201,18 +241,29 @@ export type UseTMDataGridOptions<TData extends RowData> = Omit<
|
|
|
201
241
|
/**
|
|
202
242
|
* How rows are selected. Defaults to `"checkbox"`.
|
|
203
243
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
244
|
+
* | Mode | Checkbox column | Row click |
|
|
245
|
+
* | ---- | --------------- | --------- |
|
|
246
|
+
* | `"checkbox"` | yes, multi-select | nothing |
|
|
247
|
+
* | `"row"` | no | multi-selects, Ctrl/Shift modifiers |
|
|
248
|
+
* | `"checkboxAndHighlight"` | yes, multi-select | highlights one row |
|
|
249
|
+
* | `"highlight"` | no | highlights one row, no selection at all |
|
|
250
|
+
*
|
|
251
|
+
* One option rather than two, so the combination that cannot work — a click
|
|
252
|
+
* that both toggles a multi-selection and moves the highlight — is not
|
|
253
|
+
* expressible.
|
|
208
254
|
*
|
|
209
|
-
*
|
|
210
|
-
* `
|
|
255
|
+
* The first two write to TanStack's `rowSelection`. The highlight is separate
|
|
256
|
+
* state, which is what lets `"checkboxAndHighlight"` run both at once: tick
|
|
257
|
+
* rows for a bulk action, click one to open its detail panel. See
|
|
258
|
+
* `defaultHighlightedRowId` / `onHighlightedRowChange`.
|
|
259
|
+
*
|
|
260
|
+
* `enableRowSelection` still gates the selection half, predicate form
|
|
261
|
+
* included; under `"highlight"` there is nothing for it to gate.
|
|
211
262
|
*/
|
|
212
|
-
|
|
263
|
+
selectionMode?: TMDataGridSelectionMode;
|
|
213
264
|
/**
|
|
214
265
|
* Give selected rows the highlight background. Defaults to `true` under
|
|
215
|
-
* `
|
|
266
|
+
* `selectionMode: "row"`, where the highlight is the only feedback a click
|
|
216
267
|
* gives, and `false` under `"checkbox"`, where the box already shows it.
|
|
217
268
|
*
|
|
218
269
|
* The colour is the `--dg-row-selected-bg` CSS variable, so it can be changed
|
|
@@ -225,7 +276,31 @@ export type UseTMDataGridOptions<TData extends RowData> = Omit<
|
|
|
225
276
|
* />
|
|
226
277
|
* ```
|
|
227
278
|
*/
|
|
228
|
-
|
|
279
|
+
showSelectedBackground?: boolean;
|
|
280
|
+
/**
|
|
281
|
+
* The row that starts out highlighted, under a `selectionMode` that has a
|
|
282
|
+
* highlight. Read once on mount, like `initialState`.
|
|
283
|
+
*
|
|
284
|
+
* The grid never persists the highlighted row. Pair this with
|
|
285
|
+
* {@link onHighlightedRowChange} and keep it wherever it belongs — for a
|
|
286
|
+
* detail panel that is usually the route, which gets you a shareable link and
|
|
287
|
+
* a working back button as well as surviving a reload:
|
|
288
|
+
*
|
|
289
|
+
* ```tsx
|
|
290
|
+
* const { rowId } = useParams();
|
|
291
|
+
* useTMDataGrid({
|
|
292
|
+
* selectionMode: "highlight",
|
|
293
|
+
* defaultHighlightedRowId: rowId,
|
|
294
|
+
* onHighlightedRowChange: (id) => navigate(id ? `/employees/${id}` : "/employees"),
|
|
295
|
+
* });
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
defaultHighlightedRowId?: string | null;
|
|
299
|
+
/**
|
|
300
|
+
* Called with the newly highlighted row id, or `null` when it is cleared.
|
|
301
|
+
* Fires for `ui.actions.setHighlightedRow` too, not only for clicks.
|
|
302
|
+
*/
|
|
303
|
+
onHighlightedRowChange?: (rowId: string | null) => void;
|
|
229
304
|
};
|
|
230
305
|
|
|
231
306
|
type TMDataGridColumnDef<TData extends RowData> = ColumnDef<
|
|
@@ -235,8 +310,17 @@ type TMDataGridColumnDef<TData extends RowData> = ColumnDef<
|
|
|
235
310
|
>;
|
|
236
311
|
|
|
237
312
|
/**
|
|
238
|
-
* Point every column at the operator-dispatching filter function
|
|
239
|
-
* column opted into its
|
|
313
|
+
* Point every column at the operator-dispatching filter function, and take
|
|
314
|
+
* grouping's aggregation defaults back off, unless the column opted into its
|
|
315
|
+
* own. Anything the consumer set wins — it is spread over these.
|
|
316
|
+
*
|
|
317
|
+
* The aggregation pair needs explaining. TanStack's grouping feature hands
|
|
318
|
+
* every column `aggregationFn: "auto"` and an `aggregatedCell` that stringifies
|
|
319
|
+
* whatever comes out, so merely registering the feature would have every
|
|
320
|
+
* numeric column silently sum itself and every date column show a range the
|
|
321
|
+
* moment anything is grouped. That is a summary table, and "group by" is not a
|
|
322
|
+
* request for one. Cleared here, so a grouped grid is a tree until a column
|
|
323
|
+
* says otherwise — `aggregationFn: "sum"` on the column that wants it.
|
|
240
324
|
*/
|
|
241
325
|
function withTMDataGridDefaults<TData extends RowData>(
|
|
242
326
|
columns: ReadonlyArray<TMDataGridColumnDef<TData>>,
|
|
@@ -248,7 +332,12 @@ function withTMDataGridDefaults<TData extends RowData>(
|
|
|
248
332
|
columns: withTMDataGridDefaults<TData>(column.columns),
|
|
249
333
|
};
|
|
250
334
|
}
|
|
251
|
-
return {
|
|
335
|
+
return {
|
|
336
|
+
filterFn: "tmDataGrid",
|
|
337
|
+
aggregationFn: undefined,
|
|
338
|
+
aggregatedCell: undefined,
|
|
339
|
+
...column,
|
|
340
|
+
} as TMDataGridColumnDef<TData>;
|
|
252
341
|
});
|
|
253
342
|
}
|
|
254
343
|
|
|
@@ -268,48 +357,91 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
268
357
|
// Not TanStack options, so they are kept out of what `useTable` receives.
|
|
269
358
|
enableColumnOrdering,
|
|
270
359
|
enablePagination,
|
|
271
|
-
|
|
272
|
-
|
|
360
|
+
selectionMode,
|
|
361
|
+
showSelectedBackground,
|
|
362
|
+
defaultHighlightedRowId,
|
|
363
|
+
onHighlightedRowChange,
|
|
273
364
|
...options
|
|
274
365
|
}: UseTMDataGridOptions<TData>): TMDataGridApi<TData> {
|
|
275
|
-
|
|
366
|
+
// Derived up here, rather than just before the return, because the rest of the
|
|
367
|
+
// hook needs `selectColumn` — one place decides what each mode means.
|
|
368
|
+
//
|
|
369
|
+
// Deliberately not memoized on `table`: the flags must re-derive whenever the
|
|
370
|
+
// caller passes different options, and `table` keeps the same identity when
|
|
371
|
+
// they do. See readFeatureFlags.
|
|
372
|
+
const features = readFeatureFlags({
|
|
373
|
+
...options,
|
|
374
|
+
enableColumnOrdering,
|
|
375
|
+
enablePagination,
|
|
376
|
+
selectionMode,
|
|
377
|
+
showSelectedBackground,
|
|
378
|
+
});
|
|
379
|
+
|
|
276
380
|
const pinningEnabled = options.enableColumnPinning !== false;
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
selectionEnabled && rowSelectionMode !== "row";
|
|
381
|
+
const selectColumnEnabled = features.selectColumn;
|
|
382
|
+
const groupColumnEnabled = features.grouping;
|
|
280
383
|
|
|
281
384
|
const columns = useMemo(() => {
|
|
282
385
|
const base = withTMDataGridDefaults<TData>(
|
|
283
386
|
options.columns as ReadonlyArray<TMDataGridColumnDef<TData>>,
|
|
284
387
|
);
|
|
285
|
-
|
|
286
|
-
|
|
388
|
+
// Both generated columns are always present when their feature is on, and
|
|
389
|
+
// the tree column hides itself while nothing is grouped. Adding it to the
|
|
390
|
+
// array only once a column is grouped would make the column list depend on
|
|
391
|
+
// table state, which is the one thing that cannot be a `useMemo` dependency
|
|
392
|
+
// here — the table is built from these columns.
|
|
393
|
+
return [
|
|
394
|
+
...(selectColumnEnabled ? [createSelectColumn<TData>()] : []),
|
|
395
|
+
...(groupColumnEnabled ? [createGroupColumn<TData>()] : []),
|
|
396
|
+
...base,
|
|
397
|
+
];
|
|
398
|
+
}, [options.columns, selectColumnEnabled, groupColumnEnabled]);
|
|
287
399
|
|
|
288
400
|
// Read once on mount: `initialState` is only consumed on the first render,
|
|
289
401
|
// and re-reading later would fight the user's live edits.
|
|
290
402
|
const [persistedState] = useState(() => readPersistedState(persist));
|
|
291
403
|
|
|
404
|
+
// Restored grouping decides whether the tree column starts out visible, so a
|
|
405
|
+
// reload comes back to the tree the user left rather than to a hidden lane.
|
|
406
|
+
const initialGrouping =
|
|
407
|
+
persistedState.grouping ?? options.initialState?.grouping ?? [];
|
|
408
|
+
|
|
292
409
|
const table = useTable({
|
|
293
410
|
columnResizeMode: "onChange",
|
|
294
411
|
enableSorting: true,
|
|
295
412
|
enableColumnResizing: true,
|
|
296
413
|
globalFilterFn: "includesString",
|
|
414
|
+
// Grouping by a column takes it out of the grid, the way AG Grid does it:
|
|
415
|
+
// its values have moved into the tree column, so leaving it in place would
|
|
416
|
+
// show every row the same value it was grouped under. Overridable — pass
|
|
417
|
+
// `"reorder"` to keep the column and have it moved to the front instead.
|
|
418
|
+
groupedColumnMode: "remove",
|
|
297
419
|
...options,
|
|
298
420
|
features: tmDataGridFeatures,
|
|
299
421
|
columns: columns as TableOptions<TMDataGridFeatures, TData>["columns"],
|
|
300
422
|
initialState: {
|
|
301
423
|
...options.initialState,
|
|
302
424
|
...persistedState,
|
|
425
|
+
columnVisibility: {
|
|
426
|
+
...options.initialState?.columnVisibility,
|
|
427
|
+
...persistedState.columnVisibility,
|
|
428
|
+
// Last word, because the tree column's visibility is not a user setting
|
|
429
|
+
// — it tracks the grouping state. See the effect below.
|
|
430
|
+
...(groupColumnEnabled
|
|
431
|
+
? { [GROUP_COLUMN_ID]: initialGrouping.length > 0 }
|
|
432
|
+
: {}),
|
|
433
|
+
},
|
|
303
434
|
columnPinning: {
|
|
304
|
-
// The
|
|
305
|
-
// top of anything restored from storage.
|
|
435
|
+
// The generated columns are structurally pinned, so they are re-applied
|
|
436
|
+
// on top of anything restored from storage.
|
|
306
437
|
left: [
|
|
307
438
|
...(selectColumnEnabled && pinningEnabled ? [SELECT_COLUMN_ID] : []),
|
|
439
|
+
...(groupColumnEnabled && pinningEnabled ? [GROUP_COLUMN_ID] : []),
|
|
308
440
|
...(
|
|
309
441
|
persistedState.columnPinning?.left ??
|
|
310
442
|
options.initialState?.columnPinning?.left ??
|
|
311
443
|
[]
|
|
312
|
-
).filter((id) => id !== SELECT_COLUMN_ID),
|
|
444
|
+
).filter((id) => id !== SELECT_COLUMN_ID && id !== GROUP_COLUMN_ID),
|
|
313
445
|
],
|
|
314
446
|
right:
|
|
315
447
|
persistedState.columnPinning?.right ??
|
|
@@ -325,6 +457,60 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
325
457
|
},
|
|
326
458
|
});
|
|
327
459
|
|
|
460
|
+
// Two things have to happen whenever `grouping` changes.
|
|
461
|
+
//
|
|
462
|
+
// One: the tree column appears with the first grouped column and goes away
|
|
463
|
+
// with the last, so an ungrouped grid looks exactly as it did before grouping
|
|
464
|
+
// existed. Driven from a subscription rather than by rebuilding the column
|
|
465
|
+
// array, because the array is what the table is built from — deriving it from
|
|
466
|
+
// table state would close the loop. Visibility is the one column property
|
|
467
|
+
// that can be changed after the fact without touching the definitions.
|
|
468
|
+
//
|
|
469
|
+
// Two, and this one is a workaround. In table-core 9.0.0-beta.21 the
|
|
470
|
+
// per-region column APIs do not list `grouping` among their memo
|
|
471
|
+
// dependencies, even though they all derive from `getAllLeafColumns()`, which
|
|
472
|
+
// does:
|
|
473
|
+
//
|
|
474
|
+
// | API | Declares |
|
|
475
|
+
// | --- | --- |
|
|
476
|
+
// | `getLeft/Center/RightVisibleLeafColumns` | columns, columnPinning, columnVisibility, columnOrder |
|
|
477
|
+
// | `getLeft/Center/RightHeaderGroups` | columnPinning, columnOrder |
|
|
478
|
+
// | `row.getLeft/Center/RightVisibleCells` | columnPinning, columnVisibility |
|
|
479
|
+
//
|
|
480
|
+
// So grouping a *second* column leaves every one of them returning the
|
|
481
|
+
// previous list: the column TanStack removed keeps its header and its grid
|
|
482
|
+
// track, and the row cells no longer line up with them. The first grouping
|
|
483
|
+
// appears to work only because the visibility write above happens to touch a
|
|
484
|
+
// dependency they share.
|
|
485
|
+
//
|
|
486
|
+
// Re-publishing `columnVisibility` and `columnOrder` — same contents, new
|
|
487
|
+
// identity — invalidates all three families. `columnOrder` is the only
|
|
488
|
+
// dependency the header groups declare, and `columnVisibility` the only one
|
|
489
|
+
// the cells do, so both are needed. Remove this once the deps are fixed
|
|
490
|
+
// upstream; the test that fails without it groups two columns and asserts the
|
|
491
|
+
// second one leaves the grid.
|
|
492
|
+
//
|
|
493
|
+
// Writing back into the store from its own subscriber is safe: the guard is
|
|
494
|
+
// on `grouping`'s identity, and neither write touches it, so the callback
|
|
495
|
+
// these writes trigger short-circuits.
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
if (!groupColumnEnabled) return;
|
|
498
|
+
let previousGrouping = table.store.state.grouping;
|
|
499
|
+
|
|
500
|
+
const subscription = table.store.subscribe((state) => {
|
|
501
|
+
if (state.grouping === previousGrouping) return;
|
|
502
|
+
previousGrouping = state.grouping;
|
|
503
|
+
|
|
504
|
+
table.setColumnVisibility((old) => ({
|
|
505
|
+
...old,
|
|
506
|
+
[GROUP_COLUMN_ID]: state.grouping.length > 0,
|
|
507
|
+
}));
|
|
508
|
+
table.setColumnOrder((old) => [...old]);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
return () => subscription.unsubscribe();
|
|
512
|
+
}, [groupColumnEnabled, table]);
|
|
513
|
+
|
|
328
514
|
// Mirror every state change back to storage. Subscribing (rather than writing
|
|
329
515
|
// from an effect on a state snapshot) means nothing is missed, including
|
|
330
516
|
// changes made straight through the table API by the consumer.
|
|
@@ -367,6 +553,10 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
367
553
|
columnsPanelOpen: false,
|
|
368
554
|
filterPanelColumnId: null,
|
|
369
555
|
draggedColumnId: null,
|
|
556
|
+
// `useCreateStore` builds the store once per mount, so this is a genuine
|
|
557
|
+
// default rather than a value that would fight later clicks.
|
|
558
|
+
highlightedRowId: defaultHighlightedRowId ?? null,
|
|
559
|
+
selectionAnchorRowId: null,
|
|
370
560
|
},
|
|
371
561
|
({ setState }) => ({
|
|
372
562
|
openFilterPanel: (columnId = null) =>
|
|
@@ -389,19 +579,34 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
389
579
|
setState((prev) => ({ ...prev, draggedColumnId: columnId })),
|
|
390
580
|
endColumnDrag: () =>
|
|
391
581
|
setState((prev) => ({ ...prev, draggedColumnId: null })),
|
|
582
|
+
setHighlightedRow: (rowId) =>
|
|
583
|
+
setState((prev) => ({ ...prev, highlightedRowId: rowId })),
|
|
584
|
+
setSelectionAnchor: (rowId) =>
|
|
585
|
+
setState((prev) => ({ ...prev, selectionAnchorRowId: rowId })),
|
|
392
586
|
}),
|
|
393
587
|
);
|
|
394
588
|
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
589
|
+
// `onHighlightedRowChange` is fired from a subscription rather than from the store
|
|
590
|
+
// action, so it covers every route to a new active row — a row click, and a
|
|
591
|
+
// consumer calling `setHighlightedRow` itself. Held in a ref because the store is
|
|
592
|
+
// built once and its actions would otherwise close over the first render's
|
|
593
|
+
// callback.
|
|
594
|
+
const onHighlightedRowChangeRef = useRef(onHighlightedRowChange);
|
|
595
|
+
useEffect(() => {
|
|
596
|
+
onHighlightedRowChangeRef.current = onHighlightedRowChange;
|
|
597
|
+
}, [onHighlightedRowChange]);
|
|
598
|
+
|
|
599
|
+
useEffect(() => {
|
|
600
|
+
// Seeded from the current value so mounting with a `defaultHighlightedRowId`
|
|
601
|
+
// does not report a change that nothing made.
|
|
602
|
+
let previous = ui.state.highlightedRowId;
|
|
603
|
+
const subscription = ui.subscribe((state) => {
|
|
604
|
+
if (state.highlightedRowId === previous) return;
|
|
605
|
+
previous = state.highlightedRowId;
|
|
606
|
+
onHighlightedRowChangeRef.current?.(state.highlightedRowId);
|
|
607
|
+
});
|
|
608
|
+
return () => subscription.unsubscribe();
|
|
609
|
+
}, [ui]);
|
|
405
610
|
|
|
406
611
|
return { table, ui, features };
|
|
407
612
|
}
|