@jielga/tmdatagrid 2.0.0-beta.2 → 2.0.0-beta.21

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 (150) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1664 -632
  3. package/dist/index.js +5226 -3223
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/anatomy.md +102 -0
  7. package/docs/cell-selection.md +154 -0
  8. package/docs/column-layout.md +204 -0
  9. package/docs/columns.md +262 -0
  10. package/docs/components.md +304 -0
  11. package/docs/editing.md +603 -0
  12. package/docs/editors.md +250 -0
  13. package/docs/export.md +326 -0
  14. package/docs/filtering.md +358 -0
  15. package/docs/getting-started.md +123 -0
  16. package/docs/grouping.md +165 -0
  17. package/docs/loading-and-empty.md +92 -0
  18. package/docs/localization.md +79 -0
  19. package/docs/menu.md +143 -0
  20. package/docs/pagination.md +144 -0
  21. package/docs/persistence.md +111 -0
  22. package/docs/portfolio-rebalancer.md +94 -0
  23. package/docs/query-builder.md +175 -0
  24. package/docs/quick-search.md +83 -0
  25. package/docs/row-details.md +113 -0
  26. package/docs/row-interaction.md +148 -0
  27. package/docs/row-pinning.md +132 -0
  28. package/docs/row-selection.md +134 -0
  29. package/docs/row-styling.md +133 -0
  30. package/docs/scrolling.md +111 -0
  31. package/docs/server-query.md +246 -0
  32. package/docs/server-side.md +206 -0
  33. package/docs/sorting.md +101 -0
  34. package/docs/styling.md +126 -0
  35. package/docs/summary-row.md +76 -0
  36. package/docs/testing.md +309 -0
  37. package/docs/toolbar.md +161 -0
  38. package/docs/use-tm-data-grid.md +361 -0
  39. package/package.json +21 -45
  40. package/skills/appearance/SKILL.md +70 -17
  41. package/skills/cell-selection/SKILL.md +70 -76
  42. package/skills/columns/SKILL.md +131 -32
  43. package/skills/data/SKILL.md +100 -23
  44. package/skills/editing/SKILL.md +217 -96
  45. package/skills/editing/references/common-mistakes.md +111 -24
  46. package/skills/editing/references/editing-api.md +63 -39
  47. package/skills/editing/references/editors-and-validation.md +77 -19
  48. package/skills/filtering/SKILL.md +148 -40
  49. package/skills/getting-started/SKILL.md +18 -16
  50. package/skills/grouping/SKILL.md +32 -15
  51. package/skills/options/SKILL.md +39 -9
  52. package/skills/rows/SKILL.md +22 -18
  53. package/skills/server-side/SKILL.md +170 -17
  54. package/skills/testing/SKILL.md +10 -7
  55. package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +7 -19
  56. package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +7 -1
  57. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +39 -23
  58. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +106 -38
  59. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  60. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  61. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +4 -4
  62. package/src/components/TMDataGridDraftActions.tsx +307 -0
  63. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +58 -50
  64. package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +150 -115
  65. package/src/components/TMDataGridExportPicker.module.css +77 -0
  66. package/src/components/TMDataGridExportPicker.tsx +234 -0
  67. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  68. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  69. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +7 -5
  70. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  71. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  72. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -13
  73. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +4 -3
  74. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +10 -0
  75. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +100 -28
  76. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  77. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  78. package/src/components/TMDataGridMenu.tsx +354 -0
  79. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +12 -7
  80. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +90 -67
  81. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +678 -156
  82. package/src/components/TMDataGridToolbar.module.css +21 -0
  83. package/src/components/TMDataGridToolbar.tsx +181 -0
  84. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  85. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  86. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  87. package/src/components/editors/TMDataGridNumberEditor.tsx +70 -0
  88. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  89. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  90. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +17 -31
  91. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  92. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  93. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  94. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  95. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  96. package/src/components/filters/controlLayout.ts +32 -0
  97. package/src/components/filters/filterControlFor.ts +65 -0
  98. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  99. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  100. package/src/components/useHideableColumns.ts +52 -0
  101. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  102. package/src/{tmdatagrid/core → core}/capabilities.ts +14 -6
  103. package/src/{tmdatagrid/core → core}/columnOptions.ts +46 -0
  104. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  105. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  106. package/src/core/controlledState.ts +179 -0
  107. package/src/core/controlledStateSync.ts +108 -0
  108. package/src/core/deletedRows.ts +34 -0
  109. package/src/core/dom.ts +74 -0
  110. package/src/core/editEngine.ts +2476 -0
  111. package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
  112. package/src/core/export.ts +843 -0
  113. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  114. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  115. package/src/core/filterSurface.ts +99 -0
  116. package/src/{tmdatagrid/core → core}/labels.ts +66 -8
  117. package/src/{tmdatagrid/core → core}/labelsSv.ts +26 -3
  118. package/src/core/pageReset.ts +120 -0
  119. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  120. package/src/core/resizePreview.ts +141 -0
  121. package/src/core/summary.ts +59 -0
  122. package/src/core/useSettledTableState.ts +36 -0
  123. package/src/{tmdatagrid/index.ts → index.ts} +75 -12
  124. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +734 -135
  125. package/src/useTMDataGridExport.ts +78 -0
  126. package/src/tmdatagrid/components/TMDataGridEditActions.tsx +0 -162
  127. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  128. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  129. package/src/tmdatagrid/components/TMDataGridToolbar.module.css +0 -12
  130. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -162
  131. package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +0 -40
  132. package/src/tmdatagrid/core/cellExport.ts +0 -320
  133. package/src/tmdatagrid/core/editEngine.ts +0 -1006
  134. package/src/tmdatagrid/core/summary.ts +0 -35
  135. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  136. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  137. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  138. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  139. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  140. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  141. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  142. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  143. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  144. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/grouping.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
@@ -0,0 +1,309 @@
1
+ # Testing
2
+
3
+ The grid publishes a fixed set of roles, ARIA attributes and `data-*` hooks so
4
+ that a consumer's suite can be written against structure rather than against
5
+ copy or class names. Everything on this page is supported. Anything else in the
6
+ DOM is internal and may change without notice.
7
+
8
+ > Renaming or dropping anything on this page is a breaking change, so it moves
9
+ > only with a major version.
10
+
11
+ ## Selector attributes
12
+
13
+ The grid publishes three kinds of selector, and no `data-testid` of its own:
14
+
15
+ - `data-dg-part` - what an element is (`"row"`, `"filter-button"`)
16
+ - `data-row-id` and `data-column-id` - which one, using your own ids
17
+ - roles and ARIA - the same thing a screen reader reads
18
+
19
+ `data-testid` is left to your suite, whose `testIdAttribute` may be `data-qa` or
20
+ `data-cy` rather than `data-testid`. The one exception is
21
+ `<TMDataGrid data-testid>`, which sets the value you pass.
22
+
23
+ ## Naming a grid
24
+
25
+ Parts repeat across grids on the same page. Name the grid and scope through it:
26
+
27
+ ```tsx
28
+ <TMDataGrid {...grid} data-testid="orders">
29
+ <TMDataGrid.Table<Order> aria-label="Orders" />
30
+ </TMDataGrid>
31
+ ```
32
+
33
+ ```ts
34
+ const orders = page.getByTestId("orders");
35
+ await expect(orders.locator('[data-dg-part="row"][data-row-id="42"]')).toBeVisible();
36
+ ```
37
+
38
+ `data-testid` and `id` go on the root element (which also carries
39
+ `data-dg-root`); `aria-label` (or `aria-labelledby`) goes on
40
+ `TMDataGrid.Table`, because the accessible name belongs to the element carrying
41
+ the `grid` role.
42
+
43
+ ## Structure
44
+
45
+ | Element | Role | Attributes |
46
+ | --- | --- | --- |
47
+ | Root | - | `data-dg-root`, `data-size` |
48
+ | Grid | `table`, or `grid` under cell selection | `aria-rowcount`, `aria-colcount`, `aria-busy`, `data-dg-row-count` |
49
+ | Header row | `row` | `aria-rowindex` |
50
+ | Header cell | `columnheader` | `data-dg-part="header"`, `data-column-id`, `aria-sort`, `data-active` |
51
+ | Body row | `row` | `data-dg-part="row"`, `data-row-id`, `aria-rowindex`, `data-selected`, `data-highlighted`, `data-grouped`, `data-depth`, `data-pinned`, `data-deleted`, `data-dirty`, `data-draft`, `data-new`, `data-striped` |
52
+ | Body cell | `cell`, or `gridcell` under cell selection | `data-row-id`, `data-column-id`, `data-align`, `data-editing`, `data-dirty`, `data-invalid`, `data-focused`, `data-selected` |
53
+
54
+ **The role changes with cell selection.** `cellSelection` turns the grid's
55
+ `table` into a `grid` and every `cell` into a `gridcell`, so a suite written on
56
+ `getByRole("cell")` breaks when the feature is switched on. Query cells by their
57
+ coordinates instead:
58
+
59
+ ```ts
60
+ const cell = orders.locator('[data-row-id="42"][data-column-id="total"]');
61
+ ```
62
+
63
+ Body cells carry no `data-dg-part`; the coordinate pair identifies them.
64
+
65
+ ## Parts
66
+
67
+ Row and column ids come from your data (`getRowId` and the column definitions),
68
+ so a part that repeats is addressed by adding the coordinate.
69
+
70
+ ### Whole-grid
71
+
72
+ | `data-dg-part` | What it is |
73
+ | --- | --- |
74
+ | `toolbar` | The toolbar row |
75
+ | `summary-count` | The visible/total count |
76
+ | `loading` | The toolbar spinner |
77
+ | `search`, `search-clear` | Quick search input and its ✕ |
78
+ | `filter-button` | The funnel toggle |
79
+ | `filter-panel` | The panel of filter rows, wherever it is rendered |
80
+ | `filter-popup`, `filter-sidebar` | The surface holding it, under `filters.surface` |
81
+ | `filter-panel-close` | The surface's ✕; absent on a hand-placed panel |
82
+ | `filter-add`, `filter-clear-all` | The panel's footer buttons |
83
+ | `header-filter-row` | The header filter row, under `filters.inHeader` |
84
+ | `filter-pills` | The active-filter pill group |
85
+ | `menu-button` | The burger, `TMDataGrid.Menu` |
86
+ | `columns-panel`, `columns-search` | The column chooser panel, and the search box in the panel or in `TMDataGrid.Menu.Columns` |
87
+ | `columns-toggle-all`, `columns-reset` | Show/hide all and Reset layout, in the panel or in the menu |
88
+ | `footer` | The pager row |
89
+ | `page-size`, `page-range`, `page-number`, `page-prev`, `page-next` | The pager |
90
+ | `summary-row` | The footer summary row |
91
+ | `pinned-top`, `pinned-bottom` | The pinned-row edge blocks |
92
+ | `select-all` | The header select-all checkbox |
93
+ | `details-toggle-all` | Expand/collapse every detail panel |
94
+ | `save-all`, `discard-all` | `TMDataGrid.DraftActions`. `save-all` carries `data-draft-count` - the rows the save will send |
95
+ | `editor-confirm`, `editor-cancel` | `cellConfirm`'s ✓ and ✕ |
96
+ | `editor-input` | The input inside a built-in editor |
97
+ | `sort-index` | A column's position in a multi-column sort |
98
+ | `tab-guard` | The body's tab stop under [cell selection](/docs/cell-selection); `data-guard` is `leading` (before the rows) or `trailing` (after them). Zero-size, and focusing one puts the cursor on a cell |
99
+
100
+ ### Keyed by `data-row-id`
101
+
102
+ | `data-dg-part` | What it is |
103
+ | --- | --- |
104
+ | `row` | A body row, pinned or not. A committed new row is one of them, marked `data-new` |
105
+ | `entry-row` | An entry row being typed into. Carries `data-new`, and `data-committed` / `data-draft` once committed, which is where a committed row stays under `editing.newRowsSticky`. Its cells carry `data-column-id` and, on a failed ✓, `data-invalid` |
106
+ | `details` | A row's detail panel |
107
+ | `select-row` | Its selection checkbox |
108
+ | `details-toggle`, `group-toggle` | Its detail and tree chevrons |
109
+ | `edit-row`, `delete-row` | The edit lane, idle. `edit-row` also reopens an entered new row |
110
+ | `save-row`, `cancel-row` | The edit lane's Save and Cancel on an open row |
111
+ | `row-state` | The draft store's change marker; `data-state` is `new`, `edited` or `deleted` |
112
+ | `revert-row` | Drops a committed row's draft |
113
+ | `restore-row` | Undo a deletion mark |
114
+ | `confirm-new-row`, `discard-new-row` | An entry row's ✓ (commit) and ✕ |
115
+ | `open-rows-note` | `DraftActions`' count of rows still open. Carries `data-open-count`; absent while there are none |
116
+
117
+ ### Keyed by `data-column-id`
118
+
119
+ | `data-dg-part` | What it is |
120
+ | --- | --- |
121
+ | `header` | A column header |
122
+ | `header-sort`, `header-menu`, `header-filter` | Its three action buttons. `header-filter` is absent under `filters.inHeader`, where the control below is the indicator |
123
+ | `header-filter-cell`, `header-filter-operator` | One column's header filter control and its operator button |
124
+ | `filter-row` | One row of the filter panel |
125
+ | `filter-pill` | One active-filter pill; its ✕ is the only button inside it |
126
+ | `columns-toggle` | The checkbox of one column, in the panel or in the menu |
127
+
128
+ ### Keyed by both
129
+
130
+ | `data-dg-part` | What it is |
131
+ | --- | --- |
132
+ | `editor` | An open cell editor |
133
+
134
+ Within a filter row the three controls are `filter-column`,
135
+ `filter-operator` and `filter-value` (or `filter-value-from` /
136
+ `filter-value-to` for `between`).
137
+
138
+ A column declaring `meta.filter.control` or `meta.edit.editor` renders your component
139
+ in that slot, so `filter-value` and `editor-input` cover the built-ins only.
140
+ `filter-row` and `editor` still apply; scope your own queries through them.
141
+
142
+ `editor-input` is also where the grid puts the caret when an editor opens. An
143
+ editor that does not publish it is focused on the first focusable element inside
144
+ its `editor` instead, so a custom editor needs the attribute only to name which
145
+ of several inputs the caret should land in.
146
+
147
+ Every icon-only control also carries an `aria-label` drawn from `labels`. Those
148
+ are translated, so they make brittle selectors. Prefer the parts above unless
149
+ your grid runs in one language.
150
+
151
+ ## Virtualization
152
+
153
+ The grid is always virtualized: only the rows in the viewport plus overscan are
154
+ in the DOM. A row at index 500 has no element, and Playwright cannot scroll to
155
+ what it cannot find.
156
+
157
+ **Count rows off the grid, not off the DOM.** `aria-rowcount` includes every
158
+ header row - stacked column groups and the `filters.inHeader` row among them -
159
+ and the summary row, so how many it adds is a function of the grid's
160
+ configuration. `data-dg-row-count` counts the body rows alone: the current page
161
+ under pagination, or everything the filters left otherwise. Assert on that one.
162
+
163
+ ```ts
164
+ const grid = orders.getByRole("table");
165
+ await expect(grid).toHaveAttribute("data-dg-row-count", "3");
166
+ ```
167
+
168
+ **Reach a row by narrowing to it.** Filtering or searching is faster and more
169
+ stable than scrolling:
170
+
171
+ ```ts
172
+ await orders.locator('[data-dg-part="search"]').fill("Nordkvist");
173
+ await expect(orders.locator('[data-row-id="42"]').first()).toBeVisible();
174
+ ```
175
+
176
+ **Or scroll to it.** When the row must be reached in place, such as when testing
177
+ the scroll itself, `scrollToRow` moves the virtualizer:
178
+
179
+ ```ts
180
+ const found = grid.scrollToRow({ rowId: "42", align: "center" });
181
+ ```
182
+
183
+ It returns `false` when the row is not in the current view (filtered out, on
184
+ another page, or an unknown id) and scrolls nothing. From a Playwright test it
185
+ has to be called through the page, since the API lives in React:
186
+
187
+ ```ts
188
+ await page.evaluate(() => window.__ordersGrid.scrollToRow({ rowId: "42" }));
189
+ ```
190
+
191
+ which requires the app to expose the grid on `window`. Narrowing needs no such
192
+ hook.
193
+
194
+ ## Waiting
195
+
196
+ `meta.loading` sets `aria-busy` on the grid whether or not the body has rows, so
197
+ a refetch over existing rows is still visible to a test:
198
+
199
+ ```ts
200
+ await expect(grid).toHaveAttribute("aria-busy", "true");
201
+ await expect(grid).not.toHaveAttribute("aria-busy");
202
+ ```
203
+
204
+ Quick search debounces (250 ms by default, `debounce` on `TMDataGrid.Search`).
205
+ Assert on `data-dg-row-count` rather than adding a timeout. Playwright retries
206
+ the assertion until the debounce lands.
207
+
208
+ ## A page object
209
+
210
+ A helper class wraps the parts:
211
+
212
+ ```ts
213
+ import { type Locator, type Page, expect } from "@playwright/test";
214
+
215
+ type PartKey = { rowId?: string; columnId?: string };
216
+
217
+ export class DataGrid {
218
+ readonly root: Locator;
219
+ readonly grid: Locator;
220
+
221
+ constructor(page: Page, testId: string) {
222
+ this.root = page.getByTestId(testId);
223
+ this.grid = this.root.getByRole("table");
224
+ }
225
+
226
+ /** A named part, narrowed by row or column when the part repeats. */
227
+ part(name: string, key: PartKey = {}): Locator {
228
+ const selector =
229
+ `[data-dg-part="${name}"]` +
230
+ (key.rowId === undefined ? "" : `[data-row-id="${key.rowId}"]`) +
231
+ (key.columnId === undefined ? "" : `[data-column-id="${key.columnId}"]`);
232
+ return this.root.locator(selector);
233
+ }
234
+
235
+ cell({ rowId, columnId }: { rowId: string; columnId: string }): Locator {
236
+ return this.root.locator(
237
+ `[data-row-id="${rowId}"][data-column-id="${columnId}"]`,
238
+ );
239
+ }
240
+
241
+ async search(text: string): Promise<void> {
242
+ await this.part("search").fill(text);
243
+ }
244
+
245
+ async sortBy(columnId: string): Promise<void> {
246
+ await this.part("header-sort", { columnId }).click();
247
+ }
248
+
249
+ async filterBy({
250
+ columnId,
251
+ value,
252
+ }: {
253
+ columnId: string;
254
+ value: string;
255
+ }): Promise<void> {
256
+ await this.part("filter-button").click();
257
+ await this.part("filter-row", { columnId })
258
+ .locator('[data-dg-part="filter-value"]')
259
+ .fill(value);
260
+ }
261
+
262
+ async toggleColumn(columnId: string): Promise<void> {
263
+ await this.part("menu-button").click();
264
+ await this.part("columns-toggle", { columnId }).click();
265
+ }
266
+
267
+ async expectRowCount(count: number): Promise<void> {
268
+ await expect(this.grid).toHaveAttribute(
269
+ "data-dg-row-count",
270
+ String(count),
271
+ );
272
+ }
273
+
274
+ async expectSettled(): Promise<void> {
275
+ await expect(this.grid).not.toHaveAttribute("aria-busy");
276
+ }
277
+ }
278
+ ```
279
+
280
+ ```ts
281
+ test("filters to one employee", async ({ page }) => {
282
+ const grid = new DataGrid(page, "employees");
283
+ await page.goto("/employees");
284
+ await grid.expectSettled();
285
+
286
+ await grid.filterBy({ columnId: "lastName", value: "Nordkvist" });
287
+ await grid.expectRowCount(1);
288
+ await expect(grid.cell({ rowId: "42", columnId: "city" })).toHaveText(
289
+ "Stockholm",
290
+ );
291
+ });
292
+ ```
293
+
294
+ ## React Testing Library
295
+
296
+ Under jsdom there is no layout, so the virtualizer mounts a handful of rows
297
+ whatever the data says. Assert on the grid's own counts rather than on the
298
+ number of row elements:
299
+
300
+ ```tsx
301
+ const rowCount = Number(
302
+ screen.getByRole("table").getAttribute("data-dg-row-count"),
303
+ );
304
+ expect(rowCount).toBe(3);
305
+ ```
306
+
307
+ Mantine's transitions never settle under jsdom, so a Popover's dropdown mounts
308
+ empty, including the filter and column panels. Render inside
309
+ `<MantineProvider env="test">`.
@@ -0,0 +1,161 @@
1
+ # Toolbar
2
+
3
+ The toolbar is a flex row. Your own buttons are children of it, beside the
4
+ built-in ones; there is no slots API and no `actions` prop.
5
+
6
+ ```tsx
7
+ <TMDataGrid.Toolbar>
8
+ <TMDataGrid.SummaryCount />
9
+ <TMDataGrid.Spacer />
10
+ <TMDataGrid.LoadingIndicator />
11
+ <Button size="xs" variant="light" onClick={exportAll}>
12
+ Export
13
+ </Button>
14
+ <TMDataGrid.FilterButton />
15
+ <TMDataGrid.Menu>
16
+ <TMDataGrid.Menu.Columns />
17
+ </TMDataGrid.Menu>
18
+ </TMDataGrid.Toolbar>
19
+ ```
20
+
21
+ `TMDataGrid.Spacer` pushes everything after it to the right.
22
+ `TMDataGrid.Menu` is the burger; it holds the column chooser here, and anything else you put in it.
23
+ See [Grid menu](/docs/menu).
24
+
25
+ ```demo
26
+ file: customization/ToolbarComposition.tsx
27
+ extraSources: data/employeeColumns.tsx
28
+ ```
29
+
30
+ Only the parts you render exist. A grid with no `Toolbar` has no toolbar; a
31
+ toolbar with only `Search` has only a search box.
32
+
33
+ ## Style props
34
+
35
+ `TMDataGrid.Toolbar` and `TMDataGrid.Spacer` take Mantine's `BoxProps`: the style props (`mb`, `px`, `h`, `hiddenFrom`, …), `className`, `style` and `mod`, set on the element itself.
36
+ `withBottomBorder` draws a 1px line under the toolbar in the theme's default border colour, the same line the header draws under itself.
37
+ It defaults to `false`.
38
+
39
+ ```tsx
40
+ <TMDataGrid.Toolbar withBottomBorder px="sm">
41
+ <TMDataGrid.SummaryCount />
42
+ <TMDataGrid.Spacer hiddenFrom="sm" />
43
+ <TMDataGrid.FilterButton />
44
+ </TMDataGrid.Toolbar>
45
+ ```
46
+
47
+ `TMDataGrid.Footer`, `TMDataGrid.FilterPanel`, `TMDataGrid.FilterPills` and `TMDataGrid.ColumnsPanel` take the same `BoxProps`; see [Components](/docs/components).
48
+
49
+ ## The built-in parts
50
+
51
+ | Component | Shows |
52
+ | --- | --- |
53
+ | `TMDataGrid.Search` | The [quick search](/docs/quick-search) input |
54
+ | `TMDataGrid.FilterButton` | Opens the [filter panel](/docs/filtering), with the active count |
55
+ | `TMDataGrid.Menu` | The burger: a [menu](/docs/menu) you fill; `TMDataGrid.Menu.Columns` is the column chooser as items |
56
+ | `TMDataGrid.SummaryCount` | Visible rows out of total, or the count alone where there is no total to compare it against |
57
+ | `TMDataGrid.LoadingIndicator` | A spinner while `meta.loading` |
58
+ | `TMDataGrid.Spacer` | Pushes what follows to the right |
59
+ | `TMDataGridDraftActions` | Save and Discard for the [draft store](/docs/editing#the-draft-store) |
60
+
61
+ Each renders nothing when its feature is off, so a read-only grid needs no
62
+ conditionals in the toolbar: `FilterButton` under `enableColumnFilters: false`
63
+ renders nothing at all.
64
+ `TMDataGrid.Menu` is the exception, since it cannot see what its children render; see [Grid menu](/docs/menu).
65
+
66
+ ## Buttons of your own
67
+
68
+ A button that acts on the grid reads it from context:
69
+
70
+ ```tsx
71
+ import { useTMDataGridContext } from "@jielga/tmdatagrid";
72
+
73
+ function ClearFiltersButton() {
74
+ const { table } = useTMDataGridContext();
75
+ return (
76
+ <Button size="xs" onClick={() => table.resetColumnFilters()}>
77
+ Clear filters
78
+ </Button>
79
+ );
80
+ }
81
+ ```
82
+
83
+ Anything rendered inside `TMDataGrid` can call it. It returns
84
+ `{ table, ui, features, filters, exportOptions, labels, controlSize, resetSettings }`.
85
+
86
+ An export button uses `useTMDataGridExport`, which hands back the export as click handlers:
87
+
88
+ ```tsx
89
+ import { useTMDataGridExport } from "@jielga/tmdatagrid";
90
+
91
+ function ExportButton() {
92
+ const { exportAll } = useTMDataGridExport();
93
+ return (
94
+ <Button size="xs" onClick={() => void exportAll()}>
95
+ Export
96
+ </Button>
97
+ );
98
+ }
99
+ ```
100
+
101
+ It writes every filtered row, whatever the cell selection is or whether cell selection is on at all.
102
+ The format, the file name and the selected-rows variant are on [Export](/docs/export).
103
+
104
+ ### Hide a button when its feature is off
105
+
106
+ The built-in parts disappear when their feature is off. Your own can use the
107
+ same checks instead of re-deriving them:
108
+
109
+ ```tsx
110
+ import { getGridCapabilities, useTMDataGridContext } from "@jielga/tmdatagrid";
111
+
112
+ function ExportButton() {
113
+ const { table, features } = useTMDataGridContext();
114
+ const { canFilterAny } = getGridCapabilities(table, features);
115
+
116
+ if (!canFilterAny) return null;
117
+ // …
118
+ }
119
+ ```
120
+
121
+ | Field | True when |
122
+ | --- | --- |
123
+ | `canSortAny` | At least one leaf column can be sorted |
124
+ | `canFilterAny` | At least one leaf column can be filtered |
125
+ | `canHideAny` | At least one leaf column can be hidden |
126
+ | `canPinAny` | At least one leaf column can be pinned |
127
+ | `canReorderAny` | At least one leaf column can be moved |
128
+ | `canGroupAny` | At least one leaf column can be grouped on |
129
+ | `canSelectRows` | `enableRowSelection` is not `false` and the mode is not `"highlight"` |
130
+ | `canPaginate` | Paging is configured. See [`isPagingActive`](/docs/pagination#grouping) for whether it is slicing anything |
131
+ | `canSearch` | At least one leaf column takes part in the quick search |
132
+
133
+ `getColumnCapabilities(column, features)` returns the same for one column, as
134
+ `canSort`, `canFilter`, `canHide`, `canPin`, `canResize`, `canReorder` and
135
+ `canGroup`.
136
+
137
+ ### Reading options reactively
138
+
139
+ `features` is re-derived from the options object on every render, and both
140
+ capability helpers take it alongside the table or column. A bare
141
+ `column.getCanSort()` is memoized against a column identity that survives an
142
+ options change, so a grid whose `enableSorting` turned `false` would keep
143
+ rendering sort indicators; `features` is the value that changes.
144
+
145
+ Read state through TanStack Store's
146
+ [`useSelector(table.store, …)`](https://tanstack.com/store/latest/docs/framework/react/reference)
147
+ and options through `features`, rather than calling methods on a long-lived
148
+ object.
149
+
150
+ ## Reference
151
+
152
+ | Name | Kind | Type | Default | What it does |
153
+ | --- | --- | --- | --- | --- |
154
+ | `TMDataGrid.Toolbar` | Component | `children`, `withBottomBorder`, Mantine `BoxProps` | `withBottomBorder: false` | The flex row above the grid. Style props set on the row. |
155
+ | `TMDataGrid.Spacer` | Component | Mantine `BoxProps` | – | Pushes what follows to the right. |
156
+ | `TMDataGrid.FilterButton` | Component | – | – | Opens the filter panel, with an active count. |
157
+ | `TMDataGrid.Menu` | Component | `children` | – | The burger and its dropdown. See [Grid menu](/docs/menu). |
158
+ | `useTMDataGridContext` | Hook | `() => TMDataGridContextValue` | – | `{ table, ui, features, labels, controlSize, resetSettings }`. |
159
+ | `getGridCapabilities` | Export | `(table, features) => TMDataGridCapabilities` | – | What this grid can do. Reactive to option changes. |
160
+ | `getColumnCapabilities` | Export | `(column, features) => TMDataGridColumnCapabilities` | – | The same for one column. |
161
+ | `readFeatureFlags` | Export | `(options) => TMDataGridFeatureFlags` | – | Derives the flags from an options object. |