@jielga/tmdatagrid 2.0.0-beta.8 → 2.0.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 (155) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1323 -796
  3. package/dist/index.js +4719 -3193
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/adding-rows.md +132 -0
  7. package/docs/anatomy.md +119 -0
  8. package/docs/card-view.md +108 -0
  9. package/docs/cell-selection.md +194 -0
  10. package/docs/column-layout.md +182 -0
  11. package/docs/column-menu.md +66 -0
  12. package/docs/columns.md +268 -0
  13. package/docs/components.md +311 -0
  14. package/docs/draft-store.md +242 -0
  15. package/docs/editing.md +303 -0
  16. package/docs/editors.md +250 -0
  17. package/docs/export.md +319 -0
  18. package/docs/filtering.md +362 -0
  19. package/docs/getting-started.md +123 -0
  20. package/docs/grouping.md +165 -0
  21. package/docs/loading-and-empty.md +92 -0
  22. package/docs/localization.md +79 -0
  23. package/docs/menu.md +143 -0
  24. package/docs/migrating-to-2.md +163 -0
  25. package/docs/pagination.md +144 -0
  26. package/docs/persistence.md +114 -0
  27. package/docs/portfolio-rebalancer.md +94 -0
  28. package/docs/query-builder.md +179 -0
  29. package/docs/quick-search.md +84 -0
  30. package/docs/row-details.md +115 -0
  31. package/docs/row-interaction.md +149 -0
  32. package/docs/row-pinning.md +132 -0
  33. package/docs/row-selection.md +136 -0
  34. package/docs/row-styling.md +133 -0
  35. package/docs/scrolling.md +112 -0
  36. package/docs/server-query.md +246 -0
  37. package/docs/server-side.md +206 -0
  38. package/docs/sorting.md +101 -0
  39. package/docs/styling.md +126 -0
  40. package/docs/summary-row.md +76 -0
  41. package/docs/testing.md +744 -0
  42. package/docs/toolbar.md +161 -0
  43. package/docs/use-tm-data-grid.md +361 -0
  44. package/package.json +22 -46
  45. package/skills/appearance/SKILL.md +72 -19
  46. package/skills/cell-selection/SKILL.md +69 -78
  47. package/skills/columns/SKILL.md +90 -34
  48. package/skills/data/SKILL.md +86 -16
  49. package/skills/editing/SKILL.md +83 -50
  50. package/skills/editing/references/common-mistakes.md +77 -69
  51. package/skills/editing/references/editing-api.md +31 -23
  52. package/skills/editing/references/editors-and-validation.md +24 -17
  53. package/skills/filtering/SKILL.md +148 -40
  54. package/skills/getting-started/SKILL.md +17 -15
  55. package/skills/grouping/SKILL.md +31 -16
  56. package/skills/options/SKILL.md +8 -8
  57. package/skills/rows/SKILL.md +22 -18
  58. package/skills/server-side/SKILL.md +170 -17
  59. package/skills/testing/SKILL.md +150 -32
  60. package/skills/testing-components/SKILL.md +230 -0
  61. package/skills/testing-editing/SKILL.md +240 -0
  62. package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +7 -19
  63. package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +7 -1
  64. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
  65. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +73 -7
  66. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  67. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  68. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +9 -55
  69. package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
  70. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +23 -63
  71. package/src/components/TMDataGridEntryRows.tsx +354 -0
  72. package/src/components/TMDataGridExportPicker.module.css +77 -0
  73. package/src/components/TMDataGridExportPicker.tsx +234 -0
  74. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  75. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  76. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
  77. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  78. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  79. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
  80. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +9 -72
  81. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
  82. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
  83. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  84. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  85. package/src/components/TMDataGridMenu.tsx +357 -0
  86. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +15 -53
  87. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +88 -65
  88. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +579 -165
  89. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
  90. package/src/components/TMDataGridToolbar.tsx +181 -0
  91. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  92. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  93. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  94. package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
  95. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  96. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  97. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
  98. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  99. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  100. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  101. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  102. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  103. package/src/components/filters/controlLayout.ts +32 -0
  104. package/src/components/filters/filterControlFor.ts +65 -0
  105. package/src/components/generatedColumns.tsx +187 -0
  106. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  107. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  108. package/src/components/useHideableColumns.ts +52 -0
  109. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  110. package/src/{tmdatagrid/core → core}/capabilities.ts +5 -5
  111. package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
  112. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  113. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  114. package/src/core/controlledStateSync.ts +108 -0
  115. package/src/core/deletedRows.ts +34 -0
  116. package/src/core/dom.ts +74 -0
  117. package/src/{tmdatagrid/core → core}/editEngine.ts +1172 -388
  118. package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
  119. package/src/core/export.ts +704 -0
  120. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  121. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  122. package/src/core/filterSurface.ts +99 -0
  123. package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
  124. package/src/{tmdatagrid/core → core}/labels.ts +51 -6
  125. package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
  126. package/src/core/pageReset.ts +120 -0
  127. package/src/core/pagination.ts +81 -0
  128. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  129. package/src/{tmdatagrid/core → core}/summary.ts +20 -4
  130. package/src/{tmdatagrid/index.ts → index.ts} +70 -36
  131. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +534 -123
  132. package/src/useTMDataGridExport.ts +78 -0
  133. package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +0 -298
  134. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  135. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  136. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
  137. package/src/tmdatagrid/core/cellExport.ts +0 -320
  138. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  139. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  140. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  141. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  142. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  143. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  144. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  151. /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
  152. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  153. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  154. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
  155. /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
@@ -0,0 +1,357 @@
1
+ import {
2
+ ActionIcon,
3
+ Checkbox,
4
+ Menu,
5
+ ScrollArea,
6
+ Tooltip,
7
+ type MenuProps,
8
+ } from "@mantine/core";
9
+ import {
10
+ useState,
11
+ type ComponentPropsWithoutRef,
12
+ type KeyboardEvent,
13
+ type ReactNode,
14
+ } from "react";
15
+ import { useTMDataGridContext } from "../TMDataGridContext";
16
+ import {
17
+ getColumnLabel,
18
+ showColumnSearch,
19
+ type TMDataGridColumnSearchable,
20
+ } from "../core/columnUtils";
21
+ import type {
22
+ TMDataGridExportColumns,
23
+ TMDataGridExportOptions,
24
+ } from "../core/export";
25
+ import { useTMDataGridExport } from "../useTMDataGridExport";
26
+ import { useHideableColumns } from "./useHideableColumns";
27
+ import { BurgerIcon, DownloadIcon, RestoreIcon } from "./icons";
28
+
29
+ /**
30
+ * The column toggles are `Menu.Item`s with a `Checkbox.Indicator` in front,
31
+ * not `Menu.CheckboxItem`: that one draws its tick only when checked, so an
32
+ * unchecked column next to the consumer's own items reads as a plain action.
33
+ * A box that is visibly empty says "off, click to turn on".
34
+ *
35
+ * `Menu.Item` writes `role="menuitem"` after the props it is given, so the
36
+ * checkbox role goes on through `renderRoot`, the polymorphic escape hatch.
37
+ */
38
+ const asCheckboxItem = (props: ComponentPropsWithoutRef<"button">) => (
39
+ <button {...props} role="menuitemcheckbox" />
40
+ );
41
+
42
+ export type TMDataGridMenuProps = Omit<MenuProps, "children"> & {
43
+ /** The dropdown's content: Mantine `Menu.Item`s and the `TMDataGrid.Menu.*` items. */
44
+ children: ReactNode;
45
+ /** Replaces the burger icon on the trigger. */
46
+ icon?: ReactNode;
47
+ /** Tooltip and `aria-label` of the trigger. Default `labels.menuButton`. */
48
+ label?: string;
49
+ };
50
+
51
+ export type TMDataGridMenuColumnsProps = {
52
+ /**
53
+ * Renders a `Menu.Search` above the toggles: `"auto"` (the default) from
54
+ * six hideable columns, `true` always, `false` never. Use `false` inside a
55
+ * `Menu.Sub`.
56
+ */
57
+ searchable?: TMDataGridColumnSearchable;
58
+ };
59
+
60
+ // Documented on the `TMDataGridMenu` export below.
61
+ function TMDataGridMenuRoot({
62
+ children,
63
+ icon,
64
+ label,
65
+ ...menuProps
66
+ }: TMDataGridMenuProps) {
67
+ const { labels, controlSize } = useTMDataGridContext();
68
+ const triggerLabel = label ?? labels.menuButton;
69
+
70
+ return (
71
+ <Menu
72
+ position="bottom-end"
73
+ shadow="md"
74
+ width={260}
75
+ withinPortal
76
+ {...menuProps}
77
+ >
78
+ {/* The Tooltip goes outside the Target. Inside it, the Target's
79
+ `aria-expanded` / `aria-controls` reach the Tooltip, which hands
80
+ unknown props to its own floating label, not to the button. */}
81
+ <Tooltip label={triggerLabel} openDelay={400}>
82
+ <Menu.Target>
83
+ <ActionIcon
84
+ variant="subtle"
85
+ color="gray"
86
+ size={controlSize}
87
+ aria-label={triggerLabel}
88
+ data-dg-part="menu-button"
89
+ >
90
+ {icon ?? <BurgerIcon size={18} stroke={1.6} />}
91
+ </ActionIcon>
92
+ </Menu.Target>
93
+ </Tooltip>
94
+ <Menu.Dropdown>{children}</Menu.Dropdown>
95
+ </Menu>
96
+ );
97
+ }
98
+
99
+ /**
100
+ * The whole column chooser as menu items: a search box, one checkbox item per
101
+ * hideable column, show/hide all, and Reset layout. Renders nothing when no
102
+ * column can be hidden.
103
+ *
104
+ * `searchable` is only for a block at the top level of a dropdown.
105
+ * `Menu.Search` registers on the root menu context (`hasSearch`), which
106
+ * switches off type-ahead and the arrow-key handling of every dropdown of that
107
+ * menu, so a search inside a `Menu.Sub` breaks the parent menu's keyboard
108
+ * behaviour.
109
+ */
110
+ export function TMDataGridMenuColumns({
111
+ searchable = "auto",
112
+ }: TMDataGridMenuColumnsProps) {
113
+ const { labels, controlSize } = useTMDataGridContext();
114
+ const { columns } = useHideableColumns();
115
+ const [search, setSearch] = useState("");
116
+
117
+ if (columns.length === 0) return null;
118
+
119
+ const withSearch = showColumnSearch(searchable, columns.length);
120
+
121
+ // `Menu.Search` walks every item of the dropdown from the top, so with items
122
+ // placed above this block ArrowDown from the search lands on the first of
123
+ // those - and Enter runs it. When something precedes the block, ArrowDown
124
+ // moves focus to the first listed column instead; when nothing does,
125
+ // Mantine's own walk already starts there and is left alone.
126
+ const handleSearchKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
127
+ if (event.key !== "ArrowDown") return;
128
+ const dropdown = event.currentTarget.closest('[role="menu"]');
129
+ const first = dropdown?.querySelector('[role^="menuitem"]');
130
+ const firstToggle = dropdown?.querySelector<HTMLElement>(
131
+ '[data-dg-part="columns-toggle"]',
132
+ );
133
+ if (!first || !firstToggle || first === firstToggle) return;
134
+ event.preventDefault();
135
+ firstToggle.focus();
136
+ };
137
+
138
+ return (
139
+ <>
140
+ {withSearch && (
141
+ <Menu.Search
142
+ value={search}
143
+ onChange={(event) => setSearch(event.currentTarget.value)}
144
+ onKeyDown={handleSearchKeyDown}
145
+ placeholder={labels.columnsSearchPlaceholder}
146
+ size={controlSize}
147
+ data-dg-part="columns-search"
148
+ />
149
+ )}
150
+ <TMDataGridMenuColumnToggles search={search} />
151
+ <Menu.Divider />
152
+ <TMDataGridMenuShowHideAll />
153
+ <TMDataGridMenuResetLayout />
154
+ </>
155
+ );
156
+ }
157
+
158
+ /**
159
+ * One checkbox item per hideable column, narrowed to `search` when one is
160
+ * given. Renders nothing when no column can be hidden.
161
+ */
162
+ export function TMDataGridMenuColumnToggles({ search }: { search?: string }) {
163
+ const { labels, controlSize } = useTMDataGridContext();
164
+ const { columns, columnVisibility } = useHideableColumns();
165
+
166
+ if (columns.length === 0) return null;
167
+
168
+ const needle = (search ?? "").trim().toLowerCase();
169
+ const listed = needle
170
+ ? columns.filter((column) =>
171
+ getColumnLabel(column).toLowerCase().includes(needle),
172
+ )
173
+ : columns;
174
+
175
+ return (
176
+ <ScrollArea.Autosize mah={260} type="auto">
177
+ {listed.length === 0 ? (
178
+ <Menu.Label>{labels.columnsNoMatch(search ?? "")}</Menu.Label>
179
+ ) : (
180
+ listed.map((column) => {
181
+ const visible = columnVisibility[column.id] !== false;
182
+ return (
183
+ <Menu.Item
184
+ key={column.id}
185
+ renderRoot={asCheckboxItem}
186
+ aria-checked={visible}
187
+ closeMenuOnClick={false}
188
+ leftSection={
189
+ <Checkbox.Indicator checked={visible} size={controlSize} />
190
+ }
191
+ onClick={() => column.toggleVisibility(!visible)}
192
+ data-dg-part="columns-toggle"
193
+ data-column-id={column.id}
194
+ >
195
+ {getColumnLabel(column)}
196
+ </Menu.Item>
197
+ );
198
+ })
199
+ )}
200
+ </ScrollArea.Autosize>
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Shows or hides every listed column at once. Renders nothing when no column
206
+ * can be hidden.
207
+ */
208
+ export function TMDataGridMenuShowHideAll() {
209
+ const { labels, controlSize } = useTMDataGridContext();
210
+ const { columns, shownCount, setAllVisible } = useHideableColumns();
211
+
212
+ if (columns.length === 0) return null;
213
+
214
+ const all = shownCount === columns.length;
215
+ // Some shown but not all: an indeterminate box, and a click shows all - the
216
+ // same reading as the panel's checkbox.
217
+ const some = shownCount > 0 && !all;
218
+
219
+ return (
220
+ <Menu.Item
221
+ renderRoot={asCheckboxItem}
222
+ aria-checked={some ? "mixed" : all}
223
+ closeMenuOnClick={false}
224
+ leftSection={
225
+ <Checkbox.Indicator
226
+ checked={all}
227
+ indeterminate={some}
228
+ size={controlSize}
229
+ />
230
+ }
231
+ onClick={() => setAllVisible(!all)}
232
+ data-dg-part="columns-toggle-all"
233
+ >
234
+ {labels.columnsShowHideAll}
235
+ </Menu.Item>
236
+ );
237
+ }
238
+
239
+ /**
240
+ * Resets the whole layout, not only visibility: order, widths, pinning and
241
+ * grouping go back too. Not `table.resetColumnVisibility()`, which resets to
242
+ * `initialState` - and under persistence the mount built that *from* the
243
+ * persisted payload.
244
+ */
245
+ export function TMDataGridMenuResetLayout() {
246
+ const { labels, resetSettings } = useTMDataGridContext();
247
+
248
+ return (
249
+ <Menu.Item
250
+ leftSection={<RestoreIcon size={16} stroke={1.6} />}
251
+ onClick={() => resetSettings()}
252
+ data-dg-part="columns-reset"
253
+ >
254
+ {labels.columnsReset}
255
+ </Menu.Item>
256
+ );
257
+ }
258
+
259
+ /** Per-item overrides of the grid's `exportOptions`, and the item's text. */
260
+ export type TMDataGridMenuExportProps = Omit<
261
+ TMDataGridExportOptions,
262
+ "columns"
263
+ > & {
264
+ /**
265
+ * Which columns the item writes: `"visible"`, `"all"`, a list of ids, or
266
+ * `"custom"` - a picker listing every exportable column with the visible
267
+ * ones ticked, and the download on its Export button. Defaults to the grid's
268
+ * `exportOptions.columns`.
269
+ */
270
+ columns?: TMDataGridExportColumns | "custom";
271
+ /**
272
+ * The item's text. Defaults to `labels.exportAll`, or for the selected-rows
273
+ * item `labels.exportSelected(count)`. Two items offering two formats need
274
+ * two texts, which is what this is for.
275
+ */
276
+ label?: ReactNode;
277
+ };
278
+
279
+ /** The item's props as export options, with `"custom"` set aside for the picker. */
280
+ function splitExportProps({ label, columns, ...rest }: TMDataGridMenuExportProps) {
281
+ const custom = columns === "custom";
282
+ const options: TMDataGridExportOptions = custom
283
+ ? rest
284
+ : { ...rest, columns };
285
+ return { label, custom, options };
286
+ }
287
+
288
+ /**
289
+ * Downloads every filtered and sorted row, all pages, in the grid's export
290
+ * format. Props override `exportOptions` for this item alone, which is how one
291
+ * menu offers two formats.
292
+ */
293
+ export function TMDataGridMenuExport(props: TMDataGridMenuExportProps) {
294
+ const { label, custom, options } = splitExportProps(props);
295
+ const { ui, labels } = useTMDataGridContext();
296
+ const { exportAll } = useTMDataGridExport(options);
297
+
298
+ return (
299
+ <Menu.Item
300
+ leftSection={<DownloadIcon size={16} stroke={1.6} />}
301
+ onClick={() => {
302
+ if (custom) ui.actions.openExportPicker({ rows: "all", options });
303
+ else void exportAll();
304
+ }}
305
+ data-dg-part="menu-export"
306
+ >
307
+ {label ?? labels.exportAll}
308
+ </Menu.Item>
309
+ );
310
+ }
311
+
312
+ /**
313
+ * Downloads the selected rows, in grid order. Disabled while nothing is
314
+ * selected; renders nothing when row selection is off, since then there is
315
+ * never anything for it to do.
316
+ */
317
+ export function TMDataGridMenuExportSelected(props: TMDataGridMenuExportProps) {
318
+ const { label, custom, options } = splitExportProps(props);
319
+ const { ui, labels } = useTMDataGridContext();
320
+ const { exportSelected, selectedCount, canExportSelected } =
321
+ useTMDataGridExport(options);
322
+
323
+ if (!canExportSelected) return null;
324
+
325
+ return (
326
+ <Menu.Item
327
+ leftSection={<DownloadIcon size={16} stroke={1.6} />}
328
+ disabled={selectedCount === 0}
329
+ onClick={() => {
330
+ if (custom) ui.actions.openExportPicker({ rows: "selected", options });
331
+ else void exportSelected();
332
+ }}
333
+ data-dg-part="menu-export-selected"
334
+ >
335
+ {label ?? labels.exportSelected(selectedCount)}
336
+ </Menu.Item>
337
+ );
338
+ }
339
+
340
+ /**
341
+ * Burger menu in the grid's top-right corner, holding whatever the consumer
342
+ * puts in it. Every Mantine `Menu` prop is accepted and wins over the defaults
343
+ * below.
344
+ *
345
+ * It always renders. It cannot know whether its children render anything, so
346
+ * a menu holding only `TMDataGrid.Menu.Columns` on a grid where nothing can be
347
+ * hidden still shows its button over an empty dropdown - leave the menu out of
348
+ * the toolbar in that case.
349
+ */
350
+ export const TMDataGridMenu = Object.assign(TMDataGridMenuRoot, {
351
+ Columns: TMDataGridMenuColumns,
352
+ ColumnToggles: TMDataGridMenuColumnToggles,
353
+ ShowHideAll: TMDataGridMenuShowHideAll,
354
+ ResetLayout: TMDataGridMenuResetLayout,
355
+ Export: TMDataGridMenuExport,
356
+ ExportSelected: TMDataGridMenuExportSelected,
357
+ });
@@ -1,10 +1,11 @@
1
1
  import { Checkbox } from "@mantine/core";
2
2
  import { useSelector } from "@tanstack/react-store";
3
- import type { ColumnDef, Row, RowData } from "@tanstack/react-table";
3
+ import type { Row, RowData } from "@tanstack/react-table";
4
4
  import {
5
- useCellControlTabIndex,
5
+ useBodyControlTabIndex,
6
6
  useTMDataGridContext,
7
7
  } from "../TMDataGridContext";
8
+ import { isMouseEvent } from "../core/dom";
8
9
  import {
9
10
  getDisplayedRows,
10
11
  getSelectableRowIds,
@@ -62,7 +63,7 @@ function SelectAllCheckbox<TData extends RowData>({
62
63
  * change - the same reason the rest of the chrome reads them. See
63
64
  * readFeatureFlags.
64
65
  */
65
- function SelectAllHeader<TData extends RowData>({
66
+ export function SelectAllHeader<TData extends RowData>({
66
67
  table,
67
68
  }: {
68
69
  table: TMDataGridTable<TData>;
@@ -72,7 +73,7 @@ function SelectAllHeader<TData extends RowData>({
72
73
  return <SelectAllCheckbox table={table} />;
73
74
  }
74
75
 
75
- function SelectRowCheckbox<TData extends RowData>({
76
+ export function SelectRowCheckbox<TData extends RowData>({
76
77
  row,
77
78
  }: {
78
79
  row: Row<TMDataGridFeatures, TData>;
@@ -80,12 +81,14 @@ function SelectRowCheckbox<TData extends RowData>({
80
81
  // Cells render inside the grid's provider, so the checkbox can reach the
81
82
  // chrome store - it needs the shift-click pivot, and the feature flags to know
82
83
  // which row model a range is measured over.
83
- const { ui, features, labels } = useTMDataGridContext();
84
- // Out of the tab order once the grid has a cell cursor - see
85
- // useCellControlTabIndex. Enter on the lane still steps in, and Space on any
86
- // cell of the row ticks it.
87
- const tabIndex = useCellControlTabIndex();
84
+ const { ui, features, labels, edit } = useTMDataGridContext();
85
+ const tabIndex = useBodyControlTabIndex();
88
86
  const isGroupRow = row.subRows.length > 0;
87
+ // A deletion mark makes the row unselectable (see `enableRowSelection` in
88
+ // the hook), and the box below reads that through `getCanSelect`. The
89
+ // mark lives in the edit store, so the subscription is what re-renders
90
+ // the box when it lands - the row and cell identities do not change.
91
+ useSelector(edit.store, (state) => state.deletedRowIds.includes(row.id));
89
92
 
90
93
  // A group row is never selected by id: `rowSelection` only holds the leaves,
91
94
  // and TanStack's `getIsSelected()` is a plain lookup in that map. A group
@@ -109,6 +112,8 @@ function SelectRowCheckbox<TData extends RowData>({
109
112
  return (
110
113
  <Checkbox
111
114
  size="xs"
115
+ // See useBodyControlTabIndex: under cell selection Enter steps into the
116
+ // lane, the row's Tab walk reaches it, and Space on any cell ticks it.
112
117
  tabIndex={tabIndex}
113
118
  aria-label={isGroupRow ? labels.selectGroup : labels.selectRow}
114
119
  data-dg-part="select-row"
@@ -127,7 +132,7 @@ function SelectRowCheckbox<TData extends RowData>({
127
132
  // silently loses the clicked row, which `onChange` then toggles back off.
128
133
  onChange={(event) => {
129
134
  const native = event.nativeEvent;
130
- const isMouse = native instanceof MouseEvent;
135
+ const isMouse = isMouseEvent(native);
131
136
  const resolved = resolveRowSelectionClick({
132
137
  rows: getDisplayedRows(row.table, features),
133
138
  rowId: row.id,
@@ -149,46 +154,3 @@ function SelectRowCheckbox<TData extends RowData>({
149
154
  />
150
155
  );
151
156
  }
152
-
153
- /**
154
- * The generated checkbox column, prepended under
155
- * `selectionMode: "checkbox"` (the default) or `"checkboxAndHighlight"`.
156
- */
157
- export function createSelectColumn<TData extends RowData>(
158
- label = "Checkbox selection",
159
- ): ColumnDef<TMDataGridFeatures, TData, unknown> {
160
- return {
161
- id: SELECT_COLUMN_ID,
162
- meta: {
163
- label,
164
- align: "center",
165
- // Structurally the first column; it also anchors the left pinned lane, so
166
- // no other column can be moved in front of it.
167
- enableOrdering: false,
168
- },
169
- // A system lane: as wide as the control it holds and no wider. Fixed at
170
- // every scale - the control does not grow with the font size, so neither
171
- // should its track.
172
- size: 36,
173
- minSize: 36,
174
- maxSize: 36,
175
- enableResizing: false,
176
- enableSorting: false,
177
- enableColumnFilter: false,
178
- enableGlobalFilter: false,
179
- // Not a column the user chose, so not one they can switch off: hiding the
180
- // lane would take the grid's only way to select a row with it, with the
181
- // row-selection state left behind and no way back to it. Keeping it out of
182
- // "Manage columns" follows from this - the panel lists what can be hidden.
183
- enableHiding: false,
184
- // Structurally pinned to the left; users shouldn't be able to move it.
185
- enablePinning: false,
186
- header: ({ table }) => <SelectAllHeader table={table} />,
187
- cell: ({ row }) => <SelectRowCheckbox row={row} />,
188
- // Every cell on a group row that is not the grouped column counts as
189
- // aggregated, this lane included, and an aggregated cell with nothing
190
- // declared renders blank. Without this the checkbox would disappear from
191
- // exactly the rows that select a whole group. See renderCellContent.
192
- aggregatedCell: ({ row }) => <SelectRowCheckbox row={row} />,
193
- };
194
- }