@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.
Files changed (35) hide show
  1. package/dist/index.d.ts +397 -37
  2. package/dist/index.js +1550 -978
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/package.json +1 -1
  6. package/skills/columns/SKILL.md +1 -1
  7. package/skills/features/SKILL.md +1 -1
  8. package/skills/getting-started/SKILL.md +41 -6
  9. package/skills/options/SKILL.md +1 -1
  10. package/skills/server-side/SKILL.md +1 -1
  11. package/src/tmdatagrid/components/TMDataGrid.module.css +11 -1
  12. package/src/tmdatagrid/components/TMDataGrid.tsx +6 -0
  13. package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +7 -1
  14. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +15 -0
  15. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +87 -10
  16. package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +35 -0
  17. package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +135 -0
  18. package/src/tmdatagrid/components/TMDataGridFooter.module.css +8 -0
  19. package/src/tmdatagrid/components/TMDataGridFooter.tsx +70 -42
  20. package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +48 -0
  21. package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +147 -0
  22. package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +6 -0
  23. package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +77 -1
  24. package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +83 -6
  25. package/src/tmdatagrid/components/TMDataGridTable.module.css +38 -4
  26. package/src/tmdatagrid/components/TMDataGridTable.tsx +396 -32
  27. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +3 -0
  28. package/src/tmdatagrid/components/icons.ts +5 -0
  29. package/src/tmdatagrid/core/capabilities.ts +81 -11
  30. package/src/tmdatagrid/core/filterOperators.ts +25 -0
  31. package/src/tmdatagrid/core/grouping.ts +21 -0
  32. package/src/tmdatagrid/core/persistence.ts +12 -0
  33. package/src/tmdatagrid/core/rowSelection.ts +202 -0
  34. package/src/tmdatagrid/index.ts +26 -2
  35. package/src/tmdatagrid/useTMDataGrid.tsx +240 -35
@@ -20,9 +20,23 @@ import type {
20
20
  * what applies the per-column overrides.
21
21
  */
22
22
  /**
23
- * How rows are selected — see `rowSelectionMode` on {@link UseTMDataGridOptions}.
23
+ * How rows are selected — see `selectionMode` on {@link UseTMDataGridOptions}.
24
+ *
25
+ * One axis, because the two things a config could set independently — which
26
+ * chrome selects, and what a bare row click does — cannot both be free: a click
27
+ * can either toggle a multi-selection or move the highlight, never both. Folding
28
+ * them into one value makes that conflict unrepresentable rather than something
29
+ * to warn about.
24
30
  */
25
- export type TMDataGridRowSelectionMode = "checkbox" | "row";
31
+ export type TMDataGridSelectionMode =
32
+ /** Checkbox column, multi-select. Clicking a row elsewhere does nothing. */
33
+ | "checkbox"
34
+ /** No checkbox column; row click multi-selects, with Ctrl/Shift modifiers. */
35
+ | "row"
36
+ /** Checkbox column multi-selects, and a row click highlights one row. */
37
+ | "checkboxAndHighlight"
38
+ /** No selection at all — a row click only highlights. Master-detail. */
39
+ | "highlight";
26
40
 
27
41
  export type TMDataGridFeatureFlags = {
28
42
  sorting: boolean;
@@ -31,17 +45,49 @@ export type TMDataGridFeatureFlags = {
31
45
  pinning: boolean;
32
46
  resizing: boolean;
33
47
  ordering: boolean;
48
+ /** Whether any row selection is possible. Off outright under `"highlight"`. */
34
49
  rowSelection: boolean;
35
- /** Only meaningful while `rowSelection` is on. Defaults to `"checkbox"`. */
36
- rowSelectionMode: TMDataGridRowSelectionMode;
50
+ /** Defaults to `"checkbox"`. */
51
+ selectionMode: TMDataGridSelectionMode;
52
+ /** Whether the generated checkbox column is prepended. */
53
+ selectColumn: boolean;
54
+ /** Whether a bare row click toggles the selection. Only under `"row"`. */
55
+ rowClickSelects: boolean;
56
+ /**
57
+ * Whether more than one row can be selected at once — TanStack's
58
+ * `enableMultiRowSelection`. Off, the grid drops the select-all header
59
+ * checkbox: TanStack's `toggleAllRowsSelected` only consults `getCanSelect`,
60
+ * so that control would select every row and walk straight past the limit.
61
+ *
62
+ * A per-row predicate counts as on, since some rows may still multi-select.
63
+ */
64
+ multiRowSelection: boolean;
37
65
  /** Whether a selected row takes the highlight colour. Follows the mode. */
38
- highlightSelectedRows: boolean;
66
+ showSelectedBackground: boolean;
67
+ /**
68
+ * The single highlighted row — clicking a row highlights it, for a detail
69
+ * panel to follow. State of its own, not a slice of `rowSelection`, which is
70
+ * what lets it coexist with a checkbox multi-selection.
71
+ */
72
+ highlightRow: boolean;
39
73
  /**
40
74
  * The one default-off flag: pagination must be asked for, either with the
41
75
  * grid's `enablePagination` or implicitly by declaring `manualPagination`.
42
76
  * Off, the grid renders every filtered row and relies on virtualization.
43
77
  */
44
78
  pagination: boolean;
79
+ /**
80
+ * Row grouping: the "Group by" items in the header menu and the generated
81
+ * tree column. On unless `enableGrouping: false`, since an ungrouped grid
82
+ * looks and behaves exactly as it did before — `grouping` starts empty, and
83
+ * an empty grouping state passes straight through the row model.
84
+ *
85
+ * The exception is `manualPagination`, where the client holds one page rather
86
+ * than the whole set: grouping that page would build groups out of an
87
+ * arbitrary slice and quietly show wrong counts. A server-side grid that does
88
+ * its own grouping can still say `enableGrouping: true` to override.
89
+ */
90
+ grouping: boolean;
45
91
  };
46
92
 
47
93
  export function readFeatureFlags<TData extends RowData>(
@@ -54,12 +100,21 @@ export function readFeatureFlags<TData extends RowData>(
54
100
  | "enableColumnResizing"
55
101
  | "enableColumnOrdering"
56
102
  | "enableRowSelection"
57
- | "rowSelectionMode"
58
- | "highlightSelectedRows"
103
+ | "enableMultiRowSelection"
104
+ | "selectionMode"
105
+ | "showSelectedBackground"
59
106
  | "enablePagination"
60
107
  | "manualPagination"
108
+ | "enableGrouping"
61
109
  >,
62
110
  ): TMDataGridFeatureFlags {
111
+ const selectionMode = options.selectionMode ?? "checkbox";
112
+ // `"highlight"` is the master-detail mode: no selection to speak of, so the
113
+ // checkbox column and the click-to-select behaviour both fall away. Otherwise
114
+ // `enableRowSelection` still has the final say, including its predicate form.
115
+ const rowSelection =
116
+ selectionMode !== "highlight" && options.enableRowSelection !== false;
117
+
63
118
  return {
64
119
  sorting: options.enableSorting !== false,
65
120
  filtering: options.enableColumnFilters !== false,
@@ -67,14 +122,22 @@ export function readFeatureFlags<TData extends RowData>(
67
122
  pinning: options.enableColumnPinning !== false,
68
123
  resizing: options.enableColumnResizing !== false,
69
124
  ordering: options.enableColumnOrdering !== false,
70
- rowSelection: options.enableRowSelection !== false,
71
- rowSelectionMode: options.rowSelectionMode ?? "checkbox",
125
+ rowSelection,
126
+ selectionMode,
127
+ selectColumn:
128
+ rowSelection &&
129
+ (selectionMode === "checkbox" || selectionMode === "checkboxAndHighlight"),
130
+ rowClickSelects: rowSelection && selectionMode === "row",
131
+ multiRowSelection: options.enableMultiRowSelection !== false,
72
132
  // In "row" mode the highlight *is* the feedback for a click, so it is on.
73
133
  // With checkboxes the box already says it, so it is off unless asked for.
74
- highlightSelectedRows:
75
- options.highlightSelectedRows ?? options.rowSelectionMode === "row",
134
+ showSelectedBackground:
135
+ options.showSelectedBackground ?? selectionMode === "row",
136
+ highlightRow:
137
+ selectionMode === "checkboxAndHighlight" || selectionMode === "highlight",
76
138
  pagination:
77
139
  options.enablePagination === true || options.manualPagination === true,
140
+ grouping: options.enableGrouping ?? options.manualPagination !== true,
78
141
  };
79
142
  }
80
143
 
@@ -89,6 +152,7 @@ export function readFeatureFlags<TData extends RowData>(
89
152
  * | Pin to left / right | `enableColumnPinning` / `enablePinning` |
90
153
  * | Resize dragging | `enableColumnResizing` / `enableResizing` |
91
154
  * | Header dragging, Move left / right | `enableColumnOrdering` / `meta.enableOrdering` |
155
+ * | Group by / Ungroup | `enableGrouping` (table or column) |
92
156
  */
93
157
  export type TMDataGridColumnCapabilities = {
94
158
  canSort: boolean;
@@ -97,6 +161,7 @@ export type TMDataGridColumnCapabilities = {
97
161
  canPin: boolean;
98
162
  canResize: boolean;
99
163
  canReorder: boolean;
164
+ canGroup: boolean;
100
165
  };
101
166
 
102
167
  export function getColumnCapabilities(
@@ -111,6 +176,9 @@ export function getColumnCapabilities(
111
176
  canResize: features.resizing && column.getCanResize(),
112
177
  // Ordering has no TanStack capability method — see isColumnReorderable.
113
178
  canReorder: features.ordering && isColumnReorderable(column),
179
+ // `getCanGroup()` also insists on an accessor, which is what keeps the
180
+ // generated checkbox and tree columns from offering to group on themselves.
181
+ canGroup: features.grouping && column.getCanGroup(),
114
182
  };
115
183
  }
116
184
 
@@ -121,6 +189,7 @@ export type TMDataGridCapabilities = {
121
189
  canHideAny: boolean;
122
190
  canPinAny: boolean;
123
191
  canReorderAny: boolean;
192
+ canGroupAny: boolean;
124
193
  canSelectRows: boolean;
125
194
  canPaginate: boolean;
126
195
  };
@@ -139,6 +208,7 @@ export function getGridCapabilities(
139
208
  canHideAny: any((c) => c.canHide),
140
209
  canPinAny: any((c) => c.canPin),
141
210
  canReorderAny: any((c) => c.canReorder),
211
+ canGroupAny: any((c) => c.canGroup),
142
212
  canSelectRows: features.rowSelection,
143
213
  canPaginate: features.pagination,
144
214
  };
@@ -106,6 +106,31 @@ export function isFilterActive(value: unknown): boolean {
106
106
  return !operatorNeedsValue(value.operator) || value.value.trim() !== "";
107
107
  }
108
108
 
109
+ /**
110
+ * One-line description of a single filter, as shown on a filter pill.
111
+ *
112
+ * The type's default operator is left implicit — "First name: Sofia" reads the
113
+ * way a person would say it — while any other operator is spelled out, since
114
+ * that is the part a reader cannot guess.
115
+ */
116
+ export function formatFilterLabel({
117
+ label,
118
+ type,
119
+ filter,
120
+ }: {
121
+ label: string;
122
+ type: TMDataGridColumnType;
123
+ filter: TMDataGridFilterValue;
124
+ }): string {
125
+ const { operator, value } = filter;
126
+ if (!operatorNeedsValue(operator)) {
127
+ return `${label} ${FILTER_OPERATOR_LABELS[operator]}`;
128
+ }
129
+ const text = value.trim();
130
+ if (operator === getDefaultOperator(type)) return `${label}: ${text}`;
131
+ return `${label} ${FILTER_OPERATOR_LABELS[operator]} ${text}`;
132
+ }
133
+
109
134
  export function matchesFilter(
110
135
  cellValue: unknown,
111
136
  { operator, value }: TMDataGridFilterValue,
@@ -0,0 +1,21 @@
1
+ import type { Row, RowData } from "@tanstack/react-table";
2
+ import type { TMDataGridFeatures } from "../useTMDataGrid";
3
+
4
+ /**
5
+ * The data rows under a group row, at any depth.
6
+ *
7
+ * Not `row.getLeafRows()`, despite the name: that flattens the whole subtree
8
+ * and keeps the branches, so a group nested two deep reports its sub-groups
9
+ * alongside the records. Grouping `city` then `name` would have Stockholm
10
+ * counting four rows and three names as seven, and one tick on it selecting
11
+ * ids that hold no record.
12
+ *
13
+ * A row with no subRows is a data row, which also makes this the identity on an
14
+ * ungrouped grid.
15
+ */
16
+ export function getGroupDataRows<TData extends RowData>(
17
+ row: Row<TMDataGridFeatures, TData>,
18
+ ): Array<Row<TMDataGridFeatures, TData>> {
19
+ if (row.subRows.length === 0) return [row];
20
+ return row.getLeafRows().filter((leaf) => leaf.subRows.length === 0);
21
+ }
@@ -28,6 +28,10 @@ export const DATA_STATE_SLICES = [
28
28
  "globalFilter",
29
29
  "sorting",
30
30
  "pagination",
31
+ // Which groups are open. Keyed by group ids built from the values themselves
32
+ // (`department:Design`), so a restored entry for a group the data no longer
33
+ // produces is simply never looked up.
34
+ "expanded",
31
35
  ] as const satisfies ReadonlyArray<keyof GridState>;
32
36
 
33
37
  /**
@@ -39,6 +43,10 @@ export const SETTINGS_STATE_SLICES = [
39
43
  "columnSizing",
40
44
  "columnOrder",
41
45
  "columnPinning",
46
+ // Which columns the rows are grouped by. A settings slice rather than a data
47
+ // one: it names columns, not values, so it survives the data changing under
48
+ // it the way the column layout does.
49
+ "grouping",
42
50
  ] as const satisfies ReadonlyArray<keyof GridState>;
43
51
 
44
52
  export type TMDataGridDataSlice = (typeof DATA_STATE_SLICES)[number];
@@ -162,6 +170,9 @@ const SLICE_GUARDS: Record<
162
170
  pageSize > 0
163
171
  );
164
172
  },
173
+ // `true` is a legal ExpandedState of its own, meaning every row is open.
174
+ expanded: (value) =>
175
+ value === true || isRecordOf(value, (entry) => typeof entry === "boolean"),
165
176
  columnVisibility: (value) =>
166
177
  isRecordOf(value, (entry) => typeof entry === "boolean"),
167
178
  columnSizing: (value) => isRecordOf(value, isFiniteNumber),
@@ -169,6 +180,7 @@ const SLICE_GUARDS: Record<
169
180
  columnPinning: (value) =>
170
181
  isArrayOf(prop(value, "left"), isString) &&
171
182
  isArrayOf(prop(value, "right"), isString),
183
+ grouping: (value) => isArrayOf(value, isString),
172
184
  };
173
185
 
174
186
  function isValidSlice(slice: keyof GridState, value: unknown): boolean {
@@ -0,0 +1,202 @@
1
+ import type { Row, RowData, RowSelectionState } from "@tanstack/react-table";
2
+ import type { TMDataGridFeatureFlags } from "./capabilities";
3
+ import { getGroupDataRows } from "./grouping";
4
+ import type { TMDataGridFeatures, TMDataGridTable } from "../useTMDataGrid";
5
+
6
+ /**
7
+ * Rows in the order they are displayed, which is the order a shift-click range
8
+ * is measured over — so a range follows the active sort and skips filtered-out
9
+ * rows. Under pagination it is the current page, so a range cannot cross a page
10
+ * boundary.
11
+ *
12
+ * Once a column is grouped this includes the group rows, and only the leaves of
13
+ * groups that are open — and paging is suspended, so it is the whole tree. That
14
+ * is the right list for a range: it is what the user can see, and a range that
15
+ * swept collapsed rows would select things off screen. What a group row in the
16
+ * range contributes is decided by {@link getSelectableRowIds}.
17
+ */
18
+ export function getDisplayedRows<TData extends RowData>(
19
+ table: TMDataGridTable<TData>,
20
+ features: TMDataGridFeatureFlags,
21
+ ): Array<Row<TMDataGridFeatures, TData>> {
22
+ return isPagingActive(table, features)
23
+ ? table.getPaginatedRowModel().rows
24
+ : table.getPrePaginatedRowModel().rows;
25
+ }
26
+
27
+ /**
28
+ * Whether the built-in pager is actually slicing anything.
29
+ *
30
+ * Grouping suspends it. A page can only count one kind of thing, and once the
31
+ * rows are a tree neither answer works: counting every row splits a group
32
+ * across a page boundary and strands the rest of the tree on pages the user has
33
+ * to go looking for, and counting only the top-level rows quietly redefines
34
+ * "rows per page" as groups per page, so a page of 25 can hold thousands of
35
+ * rows. Rather than pick, the grid renders the whole tree and leans on the
36
+ * virtualizer — which is its default mode anyway, pagination being opt-in.
37
+ *
38
+ * `TMDataGrid.Footer` reads this too, and greys the pager out rather than
39
+ * hiding it, so the pager going quiet is visible instead of mysterious.
40
+ */
41
+ export function isPagingActive<TData extends RowData>(
42
+ table: TMDataGridTable<TData>,
43
+ features: TMDataGridFeatureFlags,
44
+ ): boolean {
45
+ return features.pagination && table.store.state.grouping.length === 0;
46
+ }
47
+
48
+ /**
49
+ * Which modifiers were held. Named for what they mean rather than for the keys,
50
+ * because the keys differ per platform — `toggle` is Ctrl on Windows/Linux and
51
+ * Cmd on macOS.
52
+ */
53
+ export type TMDataGridRowClickModifiers = {
54
+ toggle: boolean;
55
+ extend: boolean;
56
+ };
57
+
58
+ export type ResolveRowSelectionClickArgs<TData extends RowData> = {
59
+ /** Displayed order — see {@link getDisplayedRows}. */
60
+ rows: ReadonlyArray<Row<TMDataGridFeatures, TData>>;
61
+ rowId: string;
62
+ /** The pivot a shift-click extends from. */
63
+ anchorRowId: string | null;
64
+ modifiers: TMDataGridRowClickModifiers;
65
+ selection: RowSelectionState;
66
+ /**
67
+ * Whether this gesture is allowed to clear rows it did not touch.
68
+ *
69
+ * `true` for a bare row click, where replacing is the whole point. `false` for
70
+ * a checkbox, which is only ever additive — ticking one box has never cleared
71
+ * the others, and shift-clicking one adds the range rather than becoming it.
72
+ */
73
+ canReplaceSelection: boolean;
74
+ };
75
+
76
+ export type ResolvedRowSelection = {
77
+ selection: RowSelectionState;
78
+ anchorRowId: string | null;
79
+ };
80
+
81
+ /**
82
+ * The row ids one gesture on this row selects.
83
+ *
84
+ * A group row is never selected itself: it holds no record, and TanStack
85
+ * derives its checked state from its descendants. So ticking a group means
86
+ * ticking every selectable leaf under it, which is also what makes the group's
87
+ * own box light up.
88
+ *
89
+ * Leaves rather than direct children, so one click on a top-level group takes
90
+ * the whole subtree however deeply it nests.
91
+ */
92
+ export function getSelectableRowIds<TData extends RowData>(
93
+ row: Row<TMDataGridFeatures, TData>,
94
+ ): Array<string> {
95
+ return getGroupDataRows(row)
96
+ .filter((dataRow) => dataRow.getCanSelect())
97
+ .map((dataRow) => dataRow.id);
98
+ }
99
+
100
+ /**
101
+ * Turns one click into the next selection, following the convention every
102
+ * desktop list uses:
103
+ *
104
+ * | Gesture | Effect |
105
+ * | ------- | ------ |
106
+ * | Click | selection becomes only this row |
107
+ * | Ctrl/Cmd + click | toggle this row, leave the rest |
108
+ * | Shift + click | selection becomes the range anchor → this row |
109
+ * | Ctrl/Cmd + Shift + click | add that range to the selection |
110
+ *
111
+ * The anchor is a *pivot*, not "the last row touched": only a plain or
112
+ * Ctrl-click moves it, which is what lets a run of shift-clicks grow and shrink
113
+ * the same range from a fixed point.
114
+ *
115
+ * Selection state is rebuilt here and handed to `setRowSelection` wholesale
116
+ * rather than going through `row.toggleSelected()` per row, which would publish
117
+ * one state update each. That skips TanStack's sub-row cascade, so the cascade
118
+ * is done here instead: every row in the gesture is expanded through
119
+ * {@link getSelectableRowIds} before it reaches the map. Under grouping that is
120
+ * what makes one tick on a group select all of it.
121
+ */
122
+ export function resolveRowSelectionClick<TData extends RowData>({
123
+ rows,
124
+ rowId,
125
+ anchorRowId,
126
+ modifiers,
127
+ selection,
128
+ canReplaceSelection,
129
+ }: ResolveRowSelectionClickArgs<TData>): ResolvedRowSelection {
130
+ const index = rows.findIndex((row) => row.id === rowId);
131
+ const clickedRow = rows[index];
132
+ // Clicked row is not in the displayed set — nothing sensible to resolve.
133
+ if (index === -1 || clickedRow === undefined) return { selection, anchorRowId };
134
+
135
+ const targets = getSelectableRowIds(clickedRow);
136
+ // Nothing selectable under this gesture: a row the predicate refuses, or a
137
+ // group whose every leaf refuses.
138
+ if (targets.length === 0) return { selection, anchorRowId };
139
+
140
+ // `enableMultiRowSelection: false`, or the per-row predicate saying no: one
141
+ // row at a time, so the modifiers have nothing to build on and every gesture
142
+ // collapses to a single id.
143
+ //
144
+ // This has to be enforced here. Rebuilding the map and handing it to
145
+ // `setRowSelection` skips `mutateRowIsSelected`, which is where TanStack
146
+ // clears the other rows — so without this, Ctrl and Shift would quietly
147
+ // produce the multi-selection the option ruled out.
148
+ if (!clickedRow.getCanMultiSelect()) {
149
+ // A group stands for several rows, which single-select cannot express. The
150
+ // checkbox column drops the box on those rows for the same reason.
151
+ const only = targets.length === 1 ? targets[0] : undefined;
152
+ if (only === undefined) return { selection, anchorRowId };
153
+ // A checkbox must still be able to untick the one selected row; a row click
154
+ // only ever sets, so re-clicking it cannot empty the selection.
155
+ const untick = !canReplaceSelection && selection[only] === true;
156
+ return {
157
+ selection: untick ? {} : { [only]: true },
158
+ anchorRowId: rowId,
159
+ };
160
+ }
161
+
162
+ const anchorIndex =
163
+ anchorRowId === null
164
+ ? -1
165
+ : rows.findIndex((row) => row.id === anchorRowId);
166
+
167
+ // A range needs a pivot that is still on screen. When the anchor has been
168
+ // filtered or paged away, fall through and treat this as a fresh click.
169
+ if (modifiers.extend && anchorIndex !== -1) {
170
+ const from = Math.min(anchorIndex, index);
171
+ const to = Math.max(anchorIndex, index);
172
+ // Ctrl+Shift adds to what is already selected; Shift alone becomes the
173
+ // range — unless the gesture may not replace, in which case it also adds.
174
+ const next: RowSelectionState =
175
+ modifiers.toggle || !canReplaceSelection ? { ...selection } : {};
176
+ for (const row of rows.slice(from, to + 1)) {
177
+ // A group row inside the range brings its whole subtree, including leaves
178
+ // that are collapsed out of `rows` — sweeping past a closed group selects
179
+ // it entire, which is the only reading that matches what it displays.
180
+ for (const id of getSelectableRowIds(row)) next[id] = true;
181
+ }
182
+ // The pivot deliberately stays put.
183
+ return { selection: next, anchorRowId };
184
+ }
185
+
186
+ if (modifiers.toggle || !canReplaceSelection) {
187
+ const next: RowSelectionState = { ...selection };
188
+ // Asked of the targets rather than of `rowId`: a group row carries no id in
189
+ // the map, so it counts as selected exactly when all of its leaves do —
190
+ // which is also when its checkbox shows a tick rather than a dash.
191
+ const allSelected = targets.every((id) => selection[id] === true);
192
+ for (const id of targets) {
193
+ if (allSelected) delete next[id];
194
+ else next[id] = true;
195
+ }
196
+ return { selection: next, anchorRowId: rowId };
197
+ }
198
+
199
+ const next: RowSelectionState = {};
200
+ for (const id of targets) next[id] = true;
201
+ return { selection: next, anchorRowId: rowId };
202
+ }
@@ -5,6 +5,10 @@ export {
5
5
  type TMDataGridRowData,
6
6
  } from "./TMDataGridContext";
7
7
  export { SELECT_COLUMN_ID } from "./components/TMDataGridSelectColumn";
8
+ export {
9
+ formatGroupValue,
10
+ GROUP_COLUMN_ID,
11
+ } from "./components/TMDataGridGroupColumn";
8
12
  export {
9
13
  createTMDataGridColumnHelper,
10
14
  openColumnFilter,
@@ -20,11 +24,16 @@ export {
20
24
  useTMDataGrid,
21
25
  type UseTMDataGridOptions,
22
26
  } from "./useTMDataGrid";
27
+ export {
28
+ TMDataGridFilterPills,
29
+ type TMDataGridFilterPillsProps,
30
+ } from "./components/TMDataGridFilterPills";
23
31
  export {
24
32
  type TMDataGridColumnType,
25
33
  type TMDataGridFilterOperator,
26
34
  type TMDataGridFilterValue,
27
35
  FILTER_OPERATOR_LABELS,
36
+ formatFilterLabel,
28
37
  isFilterActive,
29
38
  } from "./core/filterOperators";
30
39
  export {
@@ -32,6 +41,7 @@ export {
32
41
  getColumnType,
33
42
  isColumnReorderable,
34
43
  } from "./core/columnUtils";
44
+ export { getGroupDataRows } from "./core/grouping";
35
45
  export {
36
46
  getColumnRegion,
37
47
  getStepTargetColumn,
@@ -49,8 +59,17 @@ export {
49
59
  type TMDataGridCapabilities,
50
60
  type TMDataGridColumnCapabilities,
51
61
  type TMDataGridFeatureFlags,
52
- type TMDataGridRowSelectionMode,
62
+ type TMDataGridSelectionMode,
53
63
  } from "./core/capabilities";
64
+ export {
65
+ getDisplayedRows,
66
+ getSelectableRowIds,
67
+ isPagingActive,
68
+ resolveRowSelectionClick,
69
+ type ResolvedRowSelection,
70
+ type ResolveRowSelectionClickArgs,
71
+ type TMDataGridRowClickModifiers,
72
+ } from "./core/rowSelection";
54
73
  export {
55
74
  DEFAULT_TMDATAGRID_SIZE,
56
75
  SIZE_CONTROL_SIZE,
@@ -66,7 +85,12 @@ export {
66
85
  type TMDataGridSettingsSlice,
67
86
  type TMDataGridStorageMode,
68
87
  } from "./core/persistence";
69
- export type { TMDataGridColumnLayout } from "./components/TMDataGridTable";
88
+ export type {
89
+ TMDataGridColumnLayout,
90
+ TMDataGridRowContextMenu,
91
+ TMDataGridRowContextMenuArgs,
92
+ TMDataGridTableProps,
93
+ } from "./components/TMDataGridTable";
70
94
  export {
71
95
  getTMDataGridPaginationApi,
72
96
  type TMDataGridFooterProps,