@jielga/tmdatagrid 2.2.0 → 2.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 (38) hide show
  1. package/dist/index.d.ts +41 -4
  2. package/dist/index.js +2087 -1968
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/docs/columns.md +2 -1
  6. package/docs/components.md +1 -0
  7. package/docs/filtering.md +3 -0
  8. package/docs/persistence.md +101 -2
  9. package/docs/row-details.md +5 -0
  10. package/docs/row-interaction.md +1 -1
  11. package/docs/row-styling.md +17 -0
  12. package/docs/use-tm-data-grid.md +6 -2
  13. package/package.json +1 -1
  14. package/skills/appearance/SKILL.md +1 -1
  15. package/skills/cell-selection/SKILL.md +1 -1
  16. package/skills/columns/SKILL.md +3 -3
  17. package/skills/columns/references/columns-api.md +1 -0
  18. package/skills/data/SKILL.md +1 -1
  19. package/skills/editing/SKILL.md +1 -1
  20. package/skills/filtering/SKILL.md +24 -12
  21. package/skills/getting-started/SKILL.md +1 -1
  22. package/skills/grouping/SKILL.md +1 -1
  23. package/skills/migrating-to-2/SKILL.md +1 -1
  24. package/skills/options/SKILL.md +81 -10
  25. package/skills/rows/SKILL.md +18 -9
  26. package/skills/server-side/SKILL.md +1 -1
  27. package/skills/testing/SKILL.md +1 -1
  28. package/skills/testing-components/SKILL.md +1 -1
  29. package/skills/testing-editing/SKILL.md +1 -1
  30. package/src/components/TMDataGridFilterPills.module.css +9 -7
  31. package/src/components/TMDataGridTable.module.css +15 -6
  32. package/src/components/TMDataGridTable.tsx +23 -11
  33. package/src/components/sticky.module.css +27 -7
  34. package/src/core/filterControls.ts +8 -0
  35. package/src/core/filterOperators.ts +13 -0
  36. package/src/core/persistence.ts +322 -26
  37. package/src/index.ts +1 -0
  38. package/src/useTMDataGrid.tsx +95 -14
@@ -9,16 +9,16 @@ description: >
9
9
  labels, editing, filters, exportOptions, enableColumnOrdering,
10
10
  enablePagination, resetPageOnQueryChange, enableRowNumbers, selectionMode,
11
11
  showSelectedBackground, cellSelection, renderDetails, overscan,
12
- quickSearchMode), persist with dataKey/settingsKey slices and storageMode, the
13
- returned api (table, ui, edit, features, labels, resetSettings, scrollToRow),
14
- ui actions, and reading grid state with useSelector over table.store, since
15
- table.getState() does not exist in v9. Load when configuring the hook, looking
16
- up an option's default, persisting layout or filters, or reacting to grid
17
- state from a parent.
12
+ quickSearchMode), persist with dataKey/settingsKey slices, storageMode and
13
+ urlKey deep links, the returned api (table, ui, edit, features, labels,
14
+ resetSettings, scrollToRow), ui actions, and reading grid state with
15
+ useSelector over table.store, since table.getState() does not exist in v9.
16
+ Load when configuring the hook, looking up an option's default, persisting
17
+ layout or filters, or reacting to grid state from a parent.
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '2.2.0'
21
+ library_version: '2.3.0'
22
22
  sources:
23
23
  - 'Jielga/react:packages/tmdatagrid/docs/use-tm-data-grid.md'
24
24
  - 'Jielga/react:packages/tmdatagrid/docs/persistence.md'
@@ -160,14 +160,17 @@ const grid = useTMDataGrid({
160
160
  ## persist
161
161
 
162
162
  Restores table state on mount and writes it back on every change. State is split
163
- across two keys because the groups have different lifetimes: column configuration
164
- stays valid indefinitely, while filters and pagination go stale as the data
165
- changes. Separate keys let one group be cleared without touching the other.
163
+ across three keys because the groups have different lifetimes: column configuration
164
+ stays valid indefinitely, filters and pagination go stale as the data
165
+ changes, and a deep link has to travel with the page URL. Separate keys let one
166
+ group be cleared without touching the others.
166
167
 
167
168
  | Field | Type | Default | Description |
168
169
  | --- | --- | --- | --- |
169
170
  | `dataKey` | `TMDataGridPersistKey<TMDataGridDataSlice>`: `string \| [string, DataSlice[]]` | – | Storage key for the data group. |
170
171
  | `settingsKey` | `TMDataGridPersistKey<TMDataGridSettingsSlice>`: `string \| [string, SettingsSlice[]]` | – | Storage key for the settings group. |
172
+ | `urlKey` | `TMDataGridPersistKey<TMDataGridDataSlice>`: `string \| [string, DataSlice[]]` | – | Search param for data slices kept in the URL. See [Deep links](#deep-links). |
173
+ | `urlStorage` | `TMDataGridUrlStorage`: `{ get(name): string \| null; set(name, value: string \| null): void }` | `location.search` and `history.replaceState` | Reads and writes the URL param. |
171
174
  | `storageMode` | `TMDataGridStorageMode`: `"localStorage" \| "sessionStorage"` | `"localStorage"` | Storage area. `"sessionStorage"` is per tab. |
172
175
  | `serialize` | `(value) => string` | `JSON.stringify` | Serializes before storing. |
173
176
  | `deserialize` | `(value: string) => unknown` | `JSON.parse` | Parses a stored payload. |
@@ -203,6 +206,7 @@ const persist = {
203
206
  | Group | Available slices |
204
207
  | --- | --- |
205
208
  | `dataKey` | `columnFilters`, `globalFilter`, `sorting`, `pagination`, `expanded` |
209
+ | `urlKey` | The data slices. A slice listed here is taken out of `dataKey`. |
206
210
  | `settingsKey` | `columnVisibility`, `columnSizing`, `columnOrder`, `columnPinning`, `grouping` |
207
211
 
208
212
  `grouping` is a settings slice because it names columns rather than values, so it
@@ -227,6 +231,73 @@ dropped.
227
231
 
228
232
  Source: `packages/tmdatagrid/docs/persistence.md` (Behaviour).
229
233
 
234
+ ### Deep links
235
+
236
+ `urlKey` keeps data slices in one URL search param, so a link to the page opens
237
+ the grid on the same filters. The string is the param name; a bare key takes
238
+ every data slice, a tuple a subset, as with `dataKey`.
239
+
240
+ ```tsx
241
+ const persist = {
242
+ settingsKey: "employees.settings",
243
+ // Sorting is remembered per browser; the filters travel in the link.
244
+ dataKey: ["employees.data", ["sorting"]],
245
+ urlKey: ["employees", ["columnFilters", "globalFilter"]],
246
+ } satisfies TMDataGridPersistence;
247
+ ```
248
+
249
+ - A slice lives in one group. A slice listed under `urlKey` is taken out of
250
+ `dataKey`: a bare `dataKey` hands it over silently, while a `dataKey` tuple
251
+ that names the slice explicitly is reported with `console.error` on mount.
252
+ - The param carries only the slices whose value differs from the initial state
253
+ (`initialState` merged over the grid defaults), and only the column filters
254
+ that are active. When nothing differs, the param is removed. A link without the param, or without a slice, opens on the
255
+ initial state; a URL slice never falls back to storage.
256
+ - Writes use `history.replaceState`, never `pushState`. Only the grid's own
257
+ param changes; the path, the hash, other params and `history.state` are kept.
258
+ Two grids on one page use two keys.
259
+ - The URL payload has no version stamp, so a bookmarked link survives a
260
+ release. The shape guards and the realignment against existing columns apply
261
+ as for storage, and `serialize` / `deserialize` apply to the URL payload too.
262
+ - `meta.filter.url: false` on a column keeps its filter out of the param, on
263
+ read and on write. The column's filter is then not persisted anywhere, and a
264
+ filter on it in `initialState` still applies when a link is opened. The
265
+ flag has no effect unless `urlKey` carries `columnFilters`.
266
+ - `urlStorage` replaces the default read and write of the param, for a router
267
+ that owns the URL. `get` returns the param's string or `null`; `set` receives
268
+ the string, or `null` to remove the param. `set` is only called when the
269
+ value changed. Keep `persist` referentially stable here too: react-router
270
+ rebuilds `setSearchParams` on every URL change, so read it through a ref.
271
+
272
+ ```tsx
273
+ // react-router: let the router do the replace.
274
+ const [, setSearchParams] = useSearchParams();
275
+ const setSearchParamsRef = useRef(setSearchParams);
276
+ setSearchParamsRef.current = setSearchParams;
277
+
278
+ const persist = useMemo(
279
+ () =>
280
+ ({
281
+ urlKey: ["employees", ["columnFilters"]],
282
+ urlStorage: {
283
+ get: (name) => new URLSearchParams(window.location.search).get(name),
284
+ set: (name, value) =>
285
+ setSearchParamsRef.current(
286
+ (prev) => {
287
+ if (value === null) prev.delete(name);
288
+ else prev.set(name, value);
289
+ return prev;
290
+ },
291
+ { replace: true },
292
+ ),
293
+ },
294
+ }) satisfies TMDataGridPersistence,
295
+ [],
296
+ );
297
+ ```
298
+
299
+ Source: `packages/tmdatagrid/docs/persistence.md` (Deep links).
300
+
230
301
  ## Return value
231
302
 
232
303
  | Field | Type | Description |
@@ -8,16 +8,16 @@ description: >
8
8
  reading a selection through the table store, onRowClick / onCellClick /
9
9
  onCellDoubleClick / onCellContextMenu, the renderRowContextMenu slot with its
10
10
  internalItems handback, renderColumnMenuItems and rowContextMenuProps, per-row
11
- styling with rowStyle, rowClassName, striped and the --row-bg rule, the row
12
- details panel through renderDetails, detailsColumnPosition and
13
- DETAILS_COLUMN_ID, row pinning with enableRowPinning and row.pin, and the
14
- row-number gutter through enableRowNumbers. Load when selecting rows, reacting
15
- to a click, opening a detail panel, colouring rows by their data, pinning rows
16
- to an edge, or numbering them.
11
+ styling with rowStyle, rowClassName, striped, highlightOnHover and the
12
+ --row-bg rule, the row details panel through renderDetails,
13
+ detailsColumnPosition and DETAILS_COLUMN_ID, row pinning with enableRowPinning
14
+ and row.pin, and the row-number gutter through enableRowNumbers. Load when
15
+ selecting rows, reacting to a click, opening a detail panel, colouring rows by
16
+ their data, pinning rows to an edge, or numbering them.
17
17
  metadata:
18
18
  type: core
19
19
  library: '@jielga/tmdatagrid'
20
- library_version: '2.2.0'
20
+ library_version: '2.3.0'
21
21
  sources:
22
22
  - 'Jielga/react:packages/tmdatagrid/docs/row-selection.md'
23
23
  - 'Jielga/react:packages/tmdatagrid/docs/row-interaction.md'
@@ -213,8 +213,15 @@ them and they stop showing. `striped` follows position in the view, so it
213
213
  survives sorting and filtering rather than sticking to records, and pinned rows
214
214
  are not striped.
215
215
 
216
+ The hover tint follows the gestures a row answers: a row takes it under
217
+ `selectionMode: "row"` or a highlight mode, `onRowClick` or
218
+ `renderRowContextMenu`, and a group row never does. A grid with none of those
219
+ shows no hover tint. `highlightOnHover` on the Table overrides that: `true`
220
+ tints every row, `false` none.
221
+
216
222
  Rows carry `data-selected`, `data-selected-bg`, `data-highlighted`,
217
- `data-grouped`, `data-depth`, `data-context-menu` and `data-row-id`, so a
223
+ `data-grouped`, `data-depth`, `data-context-menu`, `data-highlight-on-hover`
224
+ and `data-row-id`, so a
218
225
  stylesheet can target any of it without a callback. The boolean attributes are
219
226
  present, with the value `"true"`, only while they apply - `data-grouped` only
220
227
  on group rows - so `[data-grouped]` and `[data-grouped="true"]` are
@@ -247,7 +254,9 @@ Which rows are open is TanStack's own `expanded` state, so anything can open
247
254
  one - `row.toggleExpanded()` from a menu item, `initialState.expanded`,
248
255
  `table.toggleAllRowsExpanded()` - and it persists as a `data` slice. The panel
249
256
  is a cell spanning the row, not a row: `aria-rowcount` still counts records, and
250
- a click inside it does not select the row underneath. Group rows have no panel;
257
+ a click inside it does not select the row underneath. It has no padding: content
258
+ fills the row edge to edge, and content that should line up with the cell text
259
+ pads itself with `padding: var(--dg-padding)`. Group rows have no panel;
251
260
  expanding one opens its children.
252
261
 
253
262
  Rows are virtualized. A row that leaves the viewport and the `overscan`
@@ -15,7 +15,7 @@ description: >
15
15
  metadata:
16
16
  type: core
17
17
  library: '@jielga/tmdatagrid'
18
- library_version: '2.2.0'
18
+ library_version: '2.3.0'
19
19
  sources:
20
20
  - 'Jielga/react:packages/tmdatagrid/docs/server-side.md'
21
21
  - 'Jielga/react:packages/tmdatagrid/docs/server-query.md'
@@ -13,7 +13,7 @@ description: >
13
13
  metadata:
14
14
  type: core
15
15
  library: '@jielga/tmdatagrid'
16
- library_version: '2.2.0'
16
+ library_version: '2.3.0'
17
17
  sources:
18
18
  - 'Jielga/react:packages/tmdatagrid/docs/testing.md'
19
19
  - 'Jielga/react:playwright/support/DataGrid.ts'
@@ -12,7 +12,7 @@ description: >
12
12
  metadata:
13
13
  type: core
14
14
  library: '@jielga/tmdatagrid'
15
- library_version: '2.2.0'
15
+ library_version: '2.3.0'
16
16
  sources:
17
17
  - 'Jielga/react:packages/tmdatagrid/docs/testing.md'
18
18
  - 'Jielga/react:packages/tmdatagrid/test/gallery/main.tsx'
@@ -12,7 +12,7 @@ description: >
12
12
  metadata:
13
13
  type: core
14
14
  library: '@jielga/tmdatagrid'
15
- library_version: '2.2.0'
15
+ library_version: '2.3.0'
16
16
  sources:
17
17
  - 'Jielga/react:packages/tmdatagrid/docs/testing.md'
18
18
  - 'Jielga/react:packages/tmdatagrid/docs/editing.md'
@@ -13,13 +13,15 @@
13
13
  shape readable against any surface. Scoped through `.filterPills` for the
14
14
  specificity to beat Mantine's own rule whichever stylesheet loads last. */
15
15
  .filterPills .pill {
16
- background-color: light-dark(
17
- var(--mantine-color-gray-1),
18
- var(--mantine-color-dark-5)
19
- );
20
- color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
21
- border: 1px solid
22
- light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-3));
16
+ background-color: var(--mantine-color-gray-1);
17
+ color: var(--mantine-color-black);
18
+ border: 1px solid var(--mantine-color-gray-3);
19
+ }
20
+
21
+ :where([data-mantine-color-scheme="dark"]) .filterPills .pill {
22
+ background-color: var(--mantine-color-dark-5);
23
+ color: var(--mantine-color-dark-0);
24
+ border-color: var(--mantine-color-dark-3);
23
25
  }
24
26
 
25
27
  /* The label is a button so a pill can be clicked back open, but it must read as
@@ -84,7 +84,13 @@
84
84
  --row-bg: var(--dg-row-new-bg, var(--mantine-color-body));
85
85
  }
86
86
 
87
- .bodyRow:hover {
87
+ /* The hover tint, on the rows that carry `data-highlight-on-hover` - by default
88
+ the rows that answer a click or a right-click, see the Table's
89
+ `highlightOnHover`. `:where()` adds no specificity, so the rule stays at the
90
+ weight of the state rules around it: declared after the stripe and the entry
91
+ tint, which it wins, and before the selection and highlight rules, which win
92
+ over it. */
93
+ .bodyRow:where([data-highlight-on-hover="true"]):hover {
88
94
  --row-bg: var(--mantine-color-default-hover);
89
95
  }
90
96
 
@@ -320,7 +326,9 @@
320
326
  /* Base and tint, exactly as .bodyCell paints them. */
321
327
  background-color: var(--mantine-color-body);
322
328
  background-image: linear-gradient(var(--row-bg), var(--row-bg));
323
- padding: var(--dg-padding);
329
+ /* No padding: the panel is the user's content, and a nested grid or a tab
330
+ strip wants the full width. Content that should line up with the cell
331
+ text pads itself with var(--dg-padding). */
324
332
  /* The row above may be the selection control, which turns text selection off
325
333
  for everything inside it. A panel is content, so it takes both back. */
326
334
  user-select: text;
@@ -477,14 +485,15 @@
477
485
  the browser's default so the colour is the grid's to state - override with
478
486
  `--dg-match-highlight-bg` on the grid. */
479
487
  .matchHighlight {
480
- background-color: var(
481
- --dg-match-highlight-bg,
482
- light-dark(var(--mantine-color-yellow-2), rgba(250, 176, 5, 0.35))
483
- );
488
+ background-color: var(--dg-match-highlight-bg, var(--mantine-color-yellow-2));
484
489
  color: inherit;
485
490
  border-radius: 2px;
486
491
  }
487
492
 
493
+ :where([data-mantine-color-scheme="dark"]) .matchHighlight {
494
+ background-color: var(--dg-match-highlight-bg, rgba(250, 176, 5, 0.35));
495
+ }
496
+
488
497
  /* The summary row - one sticky row of column footers along the bottom edge,
489
498
  rendered only while at least one column defines `footer`. Same subgrid
490
499
  placement as the header, so its cells sit exactly on the body's tracks.
@@ -90,7 +90,7 @@ import {
90
90
  resolveRowSelectionClick,
91
91
  } from "../core/rowSelection";
92
92
  import { readPinnedRows } from "../core/rowPinning";
93
- import { isFilterActive, isTMDataGridFilterValue } from "../core/filterOperators";
93
+ import { filterNarrowsRows } from "../core/filterOperators";
94
94
  import { buildMatchNeedles, findMatchRanges } from "../core/matchHighlight";
95
95
  import { ROW_NUMBER_COLUMN_ID } from "./TMDataGridRowNumberColumn";
96
96
  import { SearchIcon } from "./icons";
@@ -631,6 +631,14 @@ export type TMDataGridTableProps<TData extends RowData> = {
631
631
  * stay put while the virtualizer mounts and unmounts rows.
632
632
  */
633
633
  striped?: boolean;
634
+ /**
635
+ * Whether a body row takes the hover tint under the pointer. Unset, a row
636
+ * takes it when the row itself answers a gesture: `selectionMode: "row"`,
637
+ * a highlight mode, `onRowClick` or `renderRowContextMenu`. Group rows sit
638
+ * out those gestures and so sit out the tint too. `true` tints every row,
639
+ * `false` none. The row carries `data-highlight-on-hover` while it applies.
640
+ */
641
+ highlightOnHover?: boolean;
634
642
  /**
635
643
  * Called when the scroll *arrives* at an edge - once per arrival, not per
636
644
  * scroll event, and not on mount (the grid starts at the top-left edge).
@@ -751,6 +759,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
751
759
  rowClassName,
752
760
  rowStyle,
753
761
  striped = false,
762
+ highlightOnHover,
754
763
  onScrollToTop,
755
764
  onScrollToBottom,
756
765
  onScrollToLeft,
@@ -1480,18 +1489,12 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
1480
1489
  const isEmpty = rows.length === 0 && pinnedRowCount === 0;
1481
1490
  // Which emptiness this is. A column filter counts by the rule the pills, the
1482
1491
  // header indicator and the page reset use, so the grid has one definition
1483
- // of "active": an envelope counts once `isFilterActive` says so - the
1484
- // half-typed `{ operator, value: "" }` the filter panel seeds filters
1485
- // nothing - and a value that is not an envelope always counts, since a raw
1486
- // value written by consumer code narrows the rows as far as the grid can
1487
- // tell. The body already subscribes to the whole table store, so these
1488
- // reads are reactive.
1492
+ // of "active" - see `filterNarrowsRows`. The half-typed
1493
+ // `{ operator, value: "" }` the filter panel seeds filters nothing. The body
1494
+ // already subscribes to the whole table store, so these reads are reactive.
1489
1495
  const emptyGlobalFilter = table.store.state.globalFilter;
1490
1496
  const hasActiveFilters =
1491
- table.store.state.columnFilters.some(
1492
- (entry) =>
1493
- !isTMDataGridFilterValue(entry.value) || isFilterActive(entry.value),
1494
- ) ||
1497
+ table.store.state.columnFilters.some(filterNarrowsRows) ||
1495
1498
  (typeof emptyGlobalFilter === "string"
1496
1499
  ? emptyGlobalFilter.trim() !== ""
1497
1500
  : emptyGlobalFilter != null);
@@ -2833,6 +2836,14 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
2833
2836
  ) => {
2834
2837
  const isGroupRow = row.getIsGrouped();
2835
2838
  const isInteractive = rowGesturesFor(row);
2839
+ // The hover tint promises that the row answers the pointer, so
2840
+ // by default it follows the gestures the row takes: the ones
2841
+ // behind `isInteractive`, and the context menu - which a group
2842
+ // row sits out as well, see the row's onContextMenu below.
2843
+ const highlightsOnHover =
2844
+ highlightOnHover ??
2845
+ (isInteractive ||
2846
+ (renderRowContextMenu !== undefined && !isGroupRow));
2836
2847
  const rowCells = [
2837
2848
  ...row.getStartVisibleCells(),
2838
2849
  ...row.getCenterVisibleCells(),
@@ -2940,6 +2951,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
2940
2951
  data-selects-on-click={
2941
2952
  (selectsOnRowClick && !isGroupRow) || undefined
2942
2953
  }
2954
+ data-highlight-on-hover={highlightsOnHover || undefined}
2943
2955
  aria-selected={
2944
2956
  features.rowSelection ? row.getIsSelected() : undefined
2945
2957
  }
@@ -5,9 +5,11 @@
5
5
  A box-shadow is drawn per cell, and its blur fades in at each cell's own top
6
6
  and bottom edge - stacked down a column that reads as banding at every row
7
7
  seam. Instead: a gradient pseudo element pinned to `top: 0; bottom: 0` so
8
- consecutive cells butt together into one unbroken band. Mantine sets
9
- `color-scheme` on the root, so light-dark() resolves without a second
10
- selector.
8
+ consecutive cells butt together into one unbroken band.
9
+
10
+ The dark scheme is a second rule under Mantine's `data-mantine-color-scheme`
11
+ attribute, not the `light-dark` function: scripts/package-stylesheets.test.mjs
12
+ says why.
11
13
 
12
14
  No border. A 1px line the colour of the grid's own borders drew a hard rule
13
15
  between the pinned lanes and the data, so the pinned columns read as a
@@ -31,8 +33,8 @@
31
33
  left: 100%;
32
34
  background: linear-gradient(
33
35
  to right,
34
- light-dark(rgba(0, 0, 0, 0.07), rgba(0, 0, 0, 0.26)),
35
- light-dark(rgba(0, 0, 0, 0.025), rgba(0, 0, 0, 0.09)) 45%,
36
+ rgba(0, 0, 0, 0.07),
37
+ rgba(0, 0, 0, 0.025) 45%,
36
38
  transparent
37
39
  );
38
40
  }
@@ -41,8 +43,26 @@
41
43
  right: 100%;
42
44
  background: linear-gradient(
43
45
  to left,
44
- light-dark(rgba(0, 0, 0, 0.07), rgba(0, 0, 0, 0.26)),
45
- light-dark(rgba(0, 0, 0, 0.025), rgba(0, 0, 0, 0.09)) 45%,
46
+ rgba(0, 0, 0, 0.07),
47
+ rgba(0, 0, 0, 0.025) 45%,
48
+ transparent
49
+ );
50
+ }
51
+
52
+ :where([data-mantine-color-scheme="dark"]) .stickyLeft::after {
53
+ background: linear-gradient(
54
+ to right,
55
+ rgba(0, 0, 0, 0.26),
56
+ rgba(0, 0, 0, 0.09) 45%,
57
+ transparent
58
+ );
59
+ }
60
+
61
+ :where([data-mantine-color-scheme="dark"]) .stickyRight::after {
62
+ background: linear-gradient(
63
+ to left,
64
+ rgba(0, 0, 0, 0.26),
65
+ rgba(0, 0, 0, 0.09) 45%,
46
66
  transparent
47
67
  );
48
68
  }
@@ -106,4 +106,12 @@ export type TMDataGridColumnFilterOptions = {
106
106
  * See {@link TMDataGridFilterControlComponent}.
107
107
  */
108
108
  control?: TMDataGridFilterControlComponent;
109
+ /**
110
+ * Whether this column's filter goes into the `persist.urlKey` search param.
111
+ * Defaults to `true`. `false` keeps it out of the param, on read and on
112
+ * write. Only has an effect when `urlKey` carries `columnFilters`, and since such
113
+ * a slice is not stored under `dataKey` either, the filter is then not
114
+ * persisted anywhere.
115
+ */
116
+ url?: boolean;
109
117
  };
@@ -251,6 +251,19 @@ export function isFilterActive(value: unknown): boolean {
251
251
  : typeof value.value === "string" && value.value.trim() !== "";
252
252
  }
253
253
 
254
+ /**
255
+ * Whether one `columnFilters` entry narrows the rows, by the one rule the grid
256
+ * uses for "active": an envelope counts once {@link isFilterActive} says so,
257
+ * and a value that is not an envelope always counts, since a raw value written
258
+ * by consumer code narrows the rows as far as the grid can tell.
259
+ *
260
+ * @internal Shared by the empty state and the `urlKey` param; not exported
261
+ * from the package.
262
+ */
263
+ export function filterNarrowsRows(entry: { value: unknown }): boolean {
264
+ return !isTMDataGridFilterValue(entry.value) || isFilterActive(entry.value);
265
+ }
266
+
254
267
  /** One column's filter, typed - what `columnFilters` holds per entry. */
255
268
  export type TMDataGridColumnFilter = {
256
269
  id: string;