@jielga/tmdatagrid 2.1.4 → 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 (57) hide show
  1. package/dist/index.d.ts +78 -5
  2. package/dist/index.js +2194 -1992
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/docs/cell-selection.md +1 -1
  6. package/docs/column-layout.md +13 -8
  7. package/docs/columns.md +59 -3
  8. package/docs/components.md +1 -0
  9. package/docs/export.md +2 -2
  10. package/docs/filtering.md +108 -15
  11. package/docs/getting-started.md +40 -0
  12. package/docs/grouping.md +9 -6
  13. package/docs/loading-and-empty.md +29 -9
  14. package/docs/localization.md +130 -3
  15. package/docs/menu.md +1 -1
  16. package/docs/pagination.md +26 -7
  17. package/docs/persistence.md +101 -2
  18. package/docs/row-details.md +41 -4
  19. package/docs/row-interaction.md +2 -2
  20. package/docs/row-selection.md +6 -7
  21. package/docs/row-styling.md +17 -0
  22. package/docs/scrolling.md +1 -1
  23. package/docs/server-side.md +3 -1
  24. package/docs/testing.md +18 -0
  25. package/docs/use-tm-data-grid.md +75 -36
  26. package/package.json +1 -1
  27. package/skills/appearance/SKILL.md +6 -4
  28. package/skills/appearance/references/labels.md +126 -0
  29. package/skills/cell-selection/SKILL.md +2 -2
  30. package/skills/columns/SKILL.md +44 -43
  31. package/skills/columns/references/columns-api.md +16 -1
  32. package/skills/data/SKILL.md +77 -50
  33. package/skills/editing/SKILL.md +1 -1
  34. package/skills/filtering/SKILL.md +94 -60
  35. package/skills/filtering/references/custom-filters.md +118 -0
  36. package/skills/getting-started/SKILL.md +37 -2
  37. package/skills/grouping/SKILL.md +16 -9
  38. package/skills/migrating-to-2/SKILL.md +1 -1
  39. package/skills/options/SKILL.md +206 -55
  40. package/skills/rows/SKILL.md +70 -39
  41. package/skills/server-side/SKILL.md +1 -1
  42. package/skills/testing/SKILL.md +12 -1
  43. package/skills/testing-components/SKILL.md +1 -1
  44. package/skills/testing-editing/SKILL.md +1 -1
  45. package/src/components/TMDataGridFilterPills.module.css +9 -7
  46. package/src/components/TMDataGridTable.module.css +15 -6
  47. package/src/components/TMDataGridTable.tsx +253 -41
  48. package/src/components/sticky.module.css +27 -7
  49. package/src/core/autosize.ts +19 -7
  50. package/src/core/cellRange.ts +11 -2
  51. package/src/core/colSpan.ts +78 -0
  52. package/src/core/columnUtils.ts +7 -0
  53. package/src/core/filterControls.ts +8 -0
  54. package/src/core/filterOperators.ts +29 -1
  55. package/src/core/persistence.ts +322 -26
  56. package/src/index.ts +5 -0
  57. package/src/useTMDataGrid.tsx +133 -14
@@ -1,5 +1,7 @@
1
1
  import type { TableState } from "@tanstack/react-table";
2
2
  import type { TMDataGridFeatures } from "../useTMDataGrid";
3
+ import { sameStateValue } from "./controlledState";
4
+ import { filterNarrowsRows } from "./filterOperators";
3
5
 
4
6
  type GridState = TableState<TMDataGridFeatures>;
5
7
 
@@ -65,11 +67,32 @@ export type TMDataGridPersistKey<TSlice extends string> =
65
67
  /** Same values as Mantine's `StorageType`, which the package does not re-export. */
66
68
  export type TMDataGridStorageMode = "localStorage" | "sessionStorage";
67
69
 
70
+ /**
71
+ * Reads and writes the one URL search param that `urlKey` names. Replaces the
72
+ * default, which reads `window.location.search` and writes with
73
+ * `history.replaceState`, for a router that owns the URL.
74
+ */
75
+ export type TMDataGridUrlStorage = {
76
+ /** The param's current value, or `null` when the URL has none. */
77
+ get(name: string): string | null;
78
+ /** Sets the param to `value`, or removes it when `value` is `null`. */
79
+ set(name: string, value: string | null): void;
80
+ };
81
+
68
82
  export type TMDataGridPersistence = {
69
83
  /** Key for filters, global filter, sorting and pagination. */
70
84
  dataKey?: TMDataGridPersistKey<TMDataGridDataSlice>;
71
85
  /** Key for column visibility, sizing, order and pinning. */
72
86
  settingsKey?: TMDataGridPersistKey<TMDataGridSettingsSlice>;
87
+ /**
88
+ * Search param for data slices kept in the URL, so a link opens the grid on
89
+ * the same state. Takes the slices it lists away from `dataKey`. The param
90
+ * holds only the slices that differ from the initial state, and carries no
91
+ * version stamp.
92
+ */
93
+ urlKey?: TMDataGridPersistKey<TMDataGridDataSlice>;
94
+ /** Where `urlKey` reads and writes. Defaults to the browser's URL. */
95
+ urlStorage?: TMDataGridUrlStorage;
73
96
  /** Which storage area to use. Defaults to `"localStorage"`. */
74
97
  storageMode?: TMDataGridStorageMode;
75
98
  /** Serializes a payload before it is stored. Defaults to `JSON.stringify`. */
@@ -104,6 +127,59 @@ function resolveKey<TSlice extends string>(
104
127
  return { key, slices: slices as ReadonlyArray<keyof GridState> };
105
128
  }
106
129
 
130
+ /**
131
+ * The browser's URL. Writes replace the current history entry and never push
132
+ * one: a filter change is not a navigation, and Back should leave the page
133
+ * rather than undo filters one at a time. Path, hash, every other param and
134
+ * `history.state` are kept - a router keeps its own bookkeeping there.
135
+ */
136
+ const defaultUrlStorage: TMDataGridUrlStorage = {
137
+ get: (name) => new URLSearchParams(window.location.search).get(name),
138
+ set: (name, value) => {
139
+ // Spliced by hand rather than through `URLSearchParams`, whose serializer
140
+ // re-encodes every param (`/a%20b` comes back as `%2Fa+b`). Only the
141
+ // grid's own pair changes; every other entry is kept byte for byte.
142
+ const { pathname, search, hash, href } = window.location;
143
+ const entries = search
144
+ .replace(/^\?/, "")
145
+ .split("&")
146
+ .filter((entry) => entry !== "");
147
+ const kept = entries.filter((entry) => paramEntryKey(entry) !== name);
148
+ if (value === null && kept.length === entries.length) return;
149
+ if (value !== null) {
150
+ kept.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
151
+ }
152
+ const next = `${pathname}${kept.length === 0 ? "" : `?${kept.join("&")}`}${hash}`;
153
+ if (new URL(next, href).href === href) return;
154
+ window.history.replaceState(window.history.state, "", next);
155
+ },
156
+ };
157
+
158
+ /** The decoded key of one `key=value` query entry, `+` read as a space. */
159
+ function paramEntryKey(entry: string): string {
160
+ const separator = entry.indexOf("=");
161
+ const raw = (separator === -1 ? entry : entry.slice(0, separator)).replaceAll(
162
+ "+",
163
+ " ",
164
+ );
165
+ try {
166
+ return decodeURIComponent(raw);
167
+ } catch {
168
+ // A malformed escape cannot be the grid's own key, which it encoded.
169
+ return raw;
170
+ }
171
+ }
172
+
173
+ function resolveUrlStorage(
174
+ persistence: TMDataGridPersistence | undefined,
175
+ ): TMDataGridUrlStorage | null {
176
+ if (!persistence) return null;
177
+ if (persistence.urlStorage) return persistence.urlStorage;
178
+ // Absent under SSR, like web storage. Access that throws is caught where the
179
+ // param is read or written.
180
+ return typeof window === "undefined" ? null : defaultUrlStorage;
181
+ }
182
+
107
183
  function resolveStorage(
108
184
  persistence: TMDataGridPersistence | undefined,
109
185
  ): Storage | null {
@@ -213,30 +289,44 @@ function isValidSlice(slice: keyof GridState, value: unknown): boolean {
213
289
  return guard === undefined || guard(value);
214
290
  }
215
291
 
216
- function readSlices(
217
- storage: Storage | null,
218
- resolved: ResolvedKey | null,
219
- deserialize: (value: string) => unknown,
220
- ): Partial<GridState> {
221
- if (!storage || !resolved) return {};
292
+ function readSlices({
293
+ read,
294
+ resolved,
295
+ deserialize,
296
+ versioned,
297
+ }: {
298
+ /** Returns the raw payload stored under a key, or `null`. */
299
+ read: ((key: string) => string | null) | null;
300
+ resolved: ResolvedKey | null;
301
+ deserialize: (value: string) => unknown;
302
+ /** Whether the payload must carry the current version stamp. */
303
+ versioned: boolean;
304
+ }): Partial<GridState> {
305
+ if (!read || !resolved) return {};
222
306
  try {
223
- const raw = storage.getItem(resolved.key);
307
+ const raw = read(resolved.key);
224
308
  if (!raw) return {};
225
309
  const parsed = deserialize(raw);
226
310
  if (typeof parsed !== "object" || parsed === null) return {};
227
311
 
228
312
  const record = parsed as Record<string, unknown>;
229
- if (record[VERSION_FIELD] !== PERSIST_PAYLOAD_VERSION) return {};
313
+ if (versioned && record[VERSION_FIELD] !== PERSIST_PAYLOAD_VERSION) {
314
+ return {};
315
+ }
230
316
  const restored: Record<string, unknown> = {};
231
317
  // Only copy the selected slices. A payload written before the selection was
232
318
  // narrowed cannot reintroduce slices the caller has since opted out of.
233
319
  for (const slice of resolved.slices) {
234
320
  if (!(slice in record)) continue;
235
- if (!isValidSlice(slice, record[slice])) continue;
321
+ // A `null` slice stands for `undefined` - see the writers. An own key
322
+ // holding `undefined` still overrides `initialState` in the merge, which
323
+ // is the point: the user cleared the slice. The slice's guard then
324
+ // judges `undefined`, so only a slice that may be `undefined` (the
325
+ // untyped global filter) restores that way.
326
+ const value = record[slice] === null ? undefined : record[slice];
327
+ if (!isValidSlice(slice, value)) continue;
236
328
  restored[slice] =
237
- slice === "columnPinning"
238
- ? normalizeColumnPinning(record[slice])
239
- : record[slice];
329
+ slice === "columnPinning" ? normalizeColumnPinning(value) : value;
240
330
  }
241
331
  return restored as Partial<GridState>;
242
332
  } catch {
@@ -252,7 +342,13 @@ function writeSlices(
252
342
  ): void {
253
343
  if (!storage || !resolved) return;
254
344
  const payload: Record<string, unknown> = { [VERSION_FIELD]: PERSIST_PAYLOAD_VERSION };
255
- for (const slice of resolved.slices) payload[slice] = state[slice];
345
+ for (const slice of resolved.slices) {
346
+ // `null` for a slice the user cleared to `undefined` - the global filter
347
+ // over an `initialState.globalFilter`. JSON drops an `undefined` key, and
348
+ // a missing slice means the initial state, so a reload would come back on
349
+ // the very value the user cleared. readSlices turns it back.
350
+ payload[slice] = state[slice] === undefined ? null : state[slice];
351
+ }
256
352
  try {
257
353
  storage.setItem(resolved.key, serialize(payload as Partial<GridState>));
258
354
  } catch {
@@ -260,9 +356,100 @@ function writeSlices(
260
356
  }
261
357
  }
262
358
 
359
+ /**
360
+ * Writes the URL payload: the selected slices that differ from `baseline`,
361
+ * with no version stamp, or no param at all when none differs.
362
+ *
363
+ * Only the difference goes in, so a link names what the user changed and not
364
+ * what the page starts with anyway: the address bar stays short, and an old
365
+ * link does not pin a default the page has since changed. No stamp, because a
366
+ * bookmarked link has to survive a release; the shape guards and the
367
+ * realignment on read are what keep an old link safe.
368
+ */
369
+ function writeUrlSlices({
370
+ urlStorage,
371
+ resolved,
372
+ state,
373
+ baseline,
374
+ excludedFilterColumnIds,
375
+ serialize,
376
+ }: {
377
+ urlStorage: TMDataGridUrlStorage | null;
378
+ resolved: ResolvedKey | null;
379
+ state: GridState;
380
+ baseline: Partial<GridState>;
381
+ excludedFilterColumnIds: ReadonlySet<string>;
382
+ serialize: (value: Partial<GridState>) => string;
383
+ }): void {
384
+ if (!urlStorage || !resolved) return;
385
+ const payload: Record<string, unknown> = {};
386
+ for (const slice of resolved.slices) {
387
+ // Both sides reduced the same way, so a change to an opted-out column or
388
+ // to a filter that narrows nothing does not put the param in the URL.
389
+ const value =
390
+ slice === "columnFilters"
391
+ ? linkableColumnFilters(state.columnFilters, excludedFilterColumnIds)
392
+ : state[slice];
393
+ const initial =
394
+ slice === "columnFilters"
395
+ ? linkableColumnFilters(baseline.columnFilters, excludedFilterColumnIds)
396
+ : baseline[slice];
397
+ if (sameStateValue(value, initial)) continue;
398
+ // `null` for a slice the user cleared to `undefined` - the global filter
399
+ // over an `initialState.globalFilter`. JSON drops an `undefined` key, and
400
+ // a missing slice means the initial state, so the link would reopen on
401
+ // the very value the user cleared. readSlices turns it back.
402
+ payload[slice] = value === undefined ? null : value;
403
+ }
404
+ try {
405
+ const next =
406
+ Object.keys(payload).length === 0
407
+ ? null
408
+ : serialize(payload as Partial<GridState>);
409
+ // Unchanged: no write. A router-backed storage may navigate on every
410
+ // `set`, and the debounced write runs after a resize or a selection too.
411
+ if (urlStorage.get(resolved.key) === next) return;
412
+ urlStorage.set(resolved.key, next);
413
+ } catch {
414
+ // History access refused, or a router's own storage threw. Best effort,
415
+ // like storage.
416
+ }
417
+ }
418
+
419
+ function withoutColumnFilters(
420
+ filters: GridState["columnFilters"] | undefined,
421
+ excluded: ReadonlySet<string>,
422
+ ): GridState["columnFilters"] | undefined {
423
+ if (filters === undefined || excluded.size === 0) return filters;
424
+ return filters.filter((filter) => !excluded.has(filter.id));
425
+ }
426
+
427
+ /**
428
+ * The column filters a link carries: not on an opted-out column, and narrowing
429
+ * the rows. A filter that narrows nothing is a row the filter panel seeded or
430
+ * the user has not filled in yet; in a link it would only reopen an empty row.
431
+ */
432
+ function linkableColumnFilters(
433
+ filters: GridState["columnFilters"] | undefined,
434
+ excluded: ReadonlySet<string>,
435
+ ): GridState["columnFilters"] | undefined {
436
+ return withoutColumnFilters(filters, excluded)?.filter(filterNarrowsRows);
437
+ }
438
+
263
439
  function resolveKeys(persistence: TMDataGridPersistence | undefined) {
440
+ const url = resolveKey(persistence?.urlKey, DATA_STATE_SLICES);
441
+ const data = resolveKey(persistence?.dataKey, DATA_STATE_SLICES);
264
442
  return {
265
- data: resolveKey(persistence?.dataKey, DATA_STATE_SLICES),
443
+ url,
444
+ // A slice lives in one group. The URL takes what both keys list, so a
445
+ // stored copy can never override the link that was opened.
446
+ data:
447
+ data !== null && url !== null
448
+ ? {
449
+ key: data.key,
450
+ slices: data.slices.filter((slice) => !url.slices.includes(slice)),
451
+ }
452
+ : data,
266
453
  settings: resolveKey(persistence?.settingsKey, SETTINGS_STATE_SLICES),
267
454
  };
268
455
  }
@@ -271,8 +458,27 @@ function resolveKeys(persistence: TMDataGridPersistence | undefined) {
271
458
  export function hasPersistenceKeys(
272
459
  persistence: TMDataGridPersistence | undefined,
273
460
  ): boolean {
274
- const { data, settings } = resolveKeys(persistence);
275
- return data !== null || settings !== null;
461
+ const { url, data, settings } = resolveKeys(persistence);
462
+ return url !== null || data !== null || settings !== null;
463
+ }
464
+
465
+ /**
466
+ * The data slices that a `dataKey` tuple names and `urlKey` also lists;
467
+ * `urlKey` takes them. A bare `dataKey` names no slice, so it reports nothing:
468
+ * "every data slice" plus a `urlKey` is the ordinary setup, and resolveKeys
469
+ * hands the URL its slices without a word. Only a slice named twice is a
470
+ * contradiction worth reporting.
471
+ */
472
+ export function findOverlappingSlices(
473
+ persistence: TMDataGridPersistence | undefined,
474
+ ): TMDataGridDataSlice[] {
475
+ if (typeof persistence?.dataKey === "string") return [];
476
+ const url = resolveKey(persistence?.urlKey, DATA_STATE_SLICES);
477
+ const data = resolveKey(persistence?.dataKey, DATA_STATE_SLICES);
478
+ if (url === null || data === null) return [];
479
+ return DATA_STATE_SLICES.filter(
480
+ (slice) => url.slices.includes(slice) && data.slices.includes(slice),
481
+ );
276
482
  }
277
483
 
278
484
  const defaultDeserialize = (value: string): unknown => JSON.parse(value);
@@ -290,11 +496,34 @@ export function collectLeafColumnIds(
290
496
  defs: ReadonlyArray<unknown>,
291
497
  into: string[] = [],
292
498
  ): string[] {
499
+ forEachLeafColumnDef(defs, (id) => into.push(id));
500
+ return into;
501
+ }
502
+
503
+ /**
504
+ * The leaf column ids whose `meta.filter.url` is `false`, by the same id
505
+ * derivation as {@link collectLeafColumnIds}. Their filters stay out of the
506
+ * `urlKey` param, on read and on write.
507
+ */
508
+ export function collectUrlExcludedColumnIds(
509
+ defs: ReadonlyArray<unknown>,
510
+ into: string[] = [],
511
+ ): string[] {
512
+ forEachLeafColumnDef(defs, (id, record) => {
513
+ if (prop(prop(record.meta, "filter"), "url") === false) into.push(id);
514
+ });
515
+ return into;
516
+ }
517
+
518
+ function forEachLeafColumnDef(
519
+ defs: ReadonlyArray<unknown>,
520
+ visit: (id: string, record: Record<string, unknown>) => void,
521
+ ): void {
293
522
  for (const def of defs) {
294
523
  if (typeof def !== "object" || def === null) continue;
295
524
  const record = def as Record<string, unknown>;
296
525
  if (Array.isArray(record.columns) && record.columns.length > 0) {
297
- collectLeafColumnIds(record.columns, into);
526
+ forEachLeafColumnDef(record.columns, visit);
298
527
  continue;
299
528
  }
300
529
  const accessorKey =
@@ -303,9 +532,8 @@ export function collectLeafColumnIds(
303
532
  (typeof record.id === "string" ? record.id : undefined) ??
304
533
  accessorKey?.replaceAll(".", "_") ??
305
534
  (typeof record.header === "string" ? record.header : undefined);
306
- if (id !== undefined) into.push(id);
535
+ if (id !== undefined) visit(id, record);
307
536
  }
308
- return into;
309
537
  }
310
538
 
311
539
  /**
@@ -351,8 +579,25 @@ function realignToColumns(
351
579
  return realigned;
352
580
  }
353
581
 
582
+ /** What the `urlKey` group needs beyond the persistence config itself. */
583
+ export type TMDataGridUrlPersistInputs = {
584
+ /**
585
+ * The state the table starts with when nothing is restored: the grid's
586
+ * defaults merged with `initialState`. The URL carries only the slices that
587
+ * differ from it. Without it, every slice that has a value is written. On
588
+ * read, a column opted out of the URL keeps its filter from it.
589
+ */
590
+ baseline?: Partial<GridState>;
591
+ /** See {@link collectUrlExcludedColumnIds}. */
592
+ excludedFilterColumnIds?: ReadonlyArray<string>;
593
+ /** Whether a write touches the URL. Defaults to `true`; ignored on read. */
594
+ writeUrl?: boolean;
595
+ };
596
+
354
597
  /**
355
- * State to merge into `initialState`, read once on mount.
598
+ * State to merge into `initialState`, read once on mount: settings, then data,
599
+ * then the URL. The groups are disjoint, since the URL takes the slices it
600
+ * shares with `dataKey`.
356
601
  *
357
602
  * With `knownColumnIds` - every id the grid is about to construct, generated
358
603
  * lanes included - the column-shaped slices are realigned first; see
@@ -361,28 +606,79 @@ function realignToColumns(
361
606
  export function readPersistedState(
362
607
  persistence?: TMDataGridPersistence,
363
608
  knownColumnIds?: ReadonlyArray<string>,
609
+ {
610
+ baseline = {},
611
+ excludedFilterColumnIds = [],
612
+ }: TMDataGridUrlPersistInputs = {},
364
613
  ): Partial<GridState> {
365
614
  const storage = resolveStorage(persistence);
366
- const { data, settings } = resolveKeys(persistence);
615
+ const readStorage = storage ? (key: string) => storage.getItem(key) : null;
616
+ const urlStorage = resolveUrlStorage(persistence);
617
+ const readUrl = urlStorage ? (key: string) => urlStorage.get(key) : null;
618
+ const { url, data, settings } = resolveKeys(persistence);
367
619
  const deserialize = persistence?.deserialize ?? defaultDeserialize;
620
+ const fromUrl = readSlices({
621
+ read: readUrl,
622
+ resolved: url,
623
+ deserialize,
624
+ versioned: false,
625
+ });
626
+ if (fromUrl.columnFilters) {
627
+ // The link never speaks for an opted-out column - the write leaves those
628
+ // out of both sides - so such a column keeps its initial filter, which
629
+ // the restored slice would otherwise replace along with the rest.
630
+ const excluded = new Set(excludedFilterColumnIds);
631
+ fromUrl.columnFilters = [
632
+ ...(baseline.columnFilters ?? []).filter((filter) =>
633
+ excluded.has(filter.id),
634
+ ),
635
+ ...(withoutColumnFilters(fromUrl.columnFilters, excluded) ?? []),
636
+ ];
637
+ }
368
638
  const restored = {
369
- ...readSlices(storage, settings, deserialize),
370
- ...readSlices(storage, data, deserialize),
639
+ ...readSlices({
640
+ read: readStorage,
641
+ resolved: settings,
642
+ deserialize,
643
+ versioned: true,
644
+ }),
645
+ ...readSlices({
646
+ read: readStorage,
647
+ resolved: data,
648
+ deserialize,
649
+ versioned: true,
650
+ }),
651
+ ...fromUrl,
371
652
  };
372
653
  return knownColumnIds === undefined
373
654
  ? restored
374
655
  : realignToColumns(restored, new Set(knownColumnIds));
375
656
  }
376
657
 
377
- /** Writes both payloads. Called on every table state change. */
658
+ /** Writes every configured payload. Called on every table state change. */
378
659
  export function writePersistedState(
379
660
  state: GridState,
380
661
  persistence?: TMDataGridPersistence,
662
+ {
663
+ baseline = {},
664
+ excludedFilterColumnIds = [],
665
+ writeUrl = true,
666
+ }: TMDataGridUrlPersistInputs = {},
381
667
  ): void {
668
+ const { url, data, settings } = resolveKeys(persistence);
669
+ const serialize = persistence?.serialize ?? defaultSerialize;
670
+ if (writeUrl) {
671
+ writeUrlSlices({
672
+ urlStorage: resolveUrlStorage(persistence),
673
+ resolved: url,
674
+ state,
675
+ baseline,
676
+ excludedFilterColumnIds: new Set(excludedFilterColumnIds),
677
+ serialize,
678
+ });
679
+ }
382
680
  const storage = resolveStorage(persistence);
383
681
  if (!storage) return;
384
- const { data, settings } = resolveKeys(persistence);
385
- const serialize = persistence?.serialize ?? defaultSerialize;
386
682
  writeSlices(storage, data, state, serialize);
387
683
  writeSlices(storage, settings, state, serialize);
388
684
  }
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export {
16
16
  openColumnFilter,
17
17
  type TMDataGridApi,
18
18
  type TMDataGridColumnMeta,
19
+ type TMDataGridColSpanArgs,
19
20
  type TMDataGridDetailsArgs,
20
21
  type TMDataGridDetailsRenderer,
21
22
  type TMDataGridFeatures,
@@ -50,6 +51,7 @@ export type {
50
51
  export {
51
52
  type TMDataGridColumnFilter,
52
53
  type TMDataGridColumnType,
54
+ type TMDataGridFilterFn,
53
55
  type TMDataGridFilterOperator,
54
56
  type TMDataGridFilterValue,
55
57
  type TMDataGridFilterValueShape,
@@ -60,9 +62,11 @@ export {
60
62
  formatFilterLabel,
61
63
  getOperatorsForType,
62
64
  isFilterActive,
65
+ matchesFilter,
63
66
  operatorNeedsValue,
64
67
  operatorTakesArrayValue,
65
68
  operatorTakesRangeValue,
69
+ tmDataGridFilterFn,
66
70
  } from "./core/filterOperators";
67
71
  export {
68
72
  fuzzyGlobalFilterFn,
@@ -258,6 +262,7 @@ export {
258
262
  type TMDataGridPersistKey,
259
263
  type TMDataGridSettingsSlice,
260
264
  type TMDataGridStorageMode,
265
+ type TMDataGridUrlStorage,
261
266
  } from "./core/persistence";
262
267
  export type {
263
268
  TMDataGridCellEventArgs,