@jielga/tmdatagrid 2.2.0 → 2.4.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.
- package/dist/index.d.ts +302 -11
- package/dist/index.js +2338 -2097
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/columns.md +9 -1
- package/docs/components.md +1 -0
- package/docs/filtering.md +5 -1
- package/docs/grouping.md +1 -1
- package/docs/localization.md +3 -3
- package/docs/persistence.md +189 -2
- package/docs/row-details.md +5 -0
- package/docs/row-interaction.md +1 -1
- package/docs/row-styling.md +17 -0
- package/docs/typed-deep-links.md +169 -0
- package/docs/use-tm-data-grid.md +6 -2
- package/package.json +1 -1
- package/skills/appearance/SKILL.md +1 -1
- package/skills/appearance/references/labels.md +3 -3
- package/skills/cell-selection/SKILL.md +1 -1
- package/skills/columns/SKILL.md +6 -3
- package/skills/columns/references/columns-api.md +1 -0
- package/skills/data/SKILL.md +1 -1
- package/skills/editing/SKILL.md +1 -1
- package/skills/filtering/SKILL.md +26 -13
- package/skills/getting-started/SKILL.md +2 -2
- package/skills/grouping/SKILL.md +2 -2
- package/skills/migrating-to-2/SKILL.md +1 -1
- package/skills/options/SKILL.md +105 -10
- package/skills/options/references/typed-links.md +134 -0
- package/skills/rows/SKILL.md +18 -9
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/skills/testing-components/SKILL.md +1 -1
- package/skills/testing-editing/SKILL.md +1 -1
- package/src/components/TMDataGridFilterPills.module.css +9 -7
- package/src/components/TMDataGridFilterPills.tsx +15 -1
- package/src/components/TMDataGridGroupColumn.tsx +19 -4
- package/src/components/TMDataGridHeaderFilterRow.tsx +7 -1
- package/src/components/TMDataGridTable.module.css +15 -6
- package/src/components/TMDataGridTable.tsx +23 -11
- package/src/components/sticky.module.css +27 -7
- package/src/core/filterControls.ts +8 -0
- package/src/core/filterOperators.ts +54 -18
- package/src/core/grouping.ts +11 -3
- package/src/core/labels.ts +4 -3
- package/src/core/persistence.ts +330 -26
- package/src/core/urlState.ts +439 -0
- package/src/core/valueText.ts +46 -0
- package/src/index.ts +14 -0
- package/src/useTMDataGrid.tsx +145 -21
package/src/useTMDataGrid.tsx
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type DeepValue,
|
|
29
29
|
type DisplayColumnDef,
|
|
30
30
|
filterFns,
|
|
31
|
+
getInitialTableState,
|
|
31
32
|
type GroupColumnDef,
|
|
32
33
|
globalFilteringFeature,
|
|
33
34
|
type IdentifiedColumnDef,
|
|
@@ -76,6 +77,7 @@ import {
|
|
|
76
77
|
import {
|
|
77
78
|
emptyValueForOperator,
|
|
78
79
|
type TMDataGridColumnType,
|
|
80
|
+
type TMDataGridFilterOperator,
|
|
79
81
|
tmDataGridFilterFn,
|
|
80
82
|
} from "./core/filterOperators";
|
|
81
83
|
import {
|
|
@@ -103,6 +105,8 @@ import {
|
|
|
103
105
|
} from "./core/labels";
|
|
104
106
|
import {
|
|
105
107
|
collectLeafColumnIds,
|
|
108
|
+
collectUrlExcludedColumnIds,
|
|
109
|
+
findOverlappingSlices,
|
|
106
110
|
hasPersistenceKeys,
|
|
107
111
|
readPersistedState,
|
|
108
112
|
type TMDataGridPersistence,
|
|
@@ -134,6 +138,12 @@ import {
|
|
|
134
138
|
type TMDataGridQueryTable,
|
|
135
139
|
} from "./core/pageReset";
|
|
136
140
|
import type { TMDataGridCellRange } from "./core/cellRange";
|
|
141
|
+
import type {
|
|
142
|
+
TMDataGridAccessorColumnId,
|
|
143
|
+
TMDataGridColumnInfoHints,
|
|
144
|
+
TMDataGridColumnInfoOf,
|
|
145
|
+
TMDataGridTypedColumn,
|
|
146
|
+
} from "./core/urlState";
|
|
137
147
|
import {
|
|
138
148
|
resolveExportOptions,
|
|
139
149
|
type TMDataGridExportOptions,
|
|
@@ -358,6 +368,13 @@ type WithRowTypedMeta<TDef, TData extends RowData> = TDef extends unknown
|
|
|
358
368
|
* TanStack's column helper with `meta` typed against `TData`, so the
|
|
359
369
|
* `meta.options` and `meta.edit.enabled` callbacks receive
|
|
360
370
|
* `Row<TMDataGridFeatures, TData>`.
|
|
371
|
+
*
|
|
372
|
+
* Every column it returns also carries `TMDataGridColumnInfo` in its type,
|
|
373
|
+
* read from the literal values of `id`, `meta.type`,
|
|
374
|
+
* `meta.filter.operators`, `meta.filter.url`, `enableColumnFilter` and
|
|
375
|
+
* `enableSorting`. `createTMDataGridUrlState` reads it to type the links into
|
|
376
|
+
* the grid. TanStack's own return type keeps none of this: it types
|
|
377
|
+
* `accessorKey` as a plain string.
|
|
361
378
|
*/
|
|
362
379
|
export type TMDataGridColumnHelper<TData extends RowData> = {
|
|
363
380
|
accessor: <
|
|
@@ -367,6 +384,12 @@ export type TMDataGridColumnHelper<TData extends RowData> = {
|
|
|
367
384
|
: TAccessor extends DeepKeys<TData>
|
|
368
385
|
? DeepValue<TData, TAccessor>
|
|
369
386
|
: never,
|
|
387
|
+
const TId extends string = never,
|
|
388
|
+
TType extends TMDataGridColumnType = "string",
|
|
389
|
+
TOperator extends TMDataGridFilterOperator = TMDataGridFilterOperator,
|
|
390
|
+
TFilterable extends boolean = true,
|
|
391
|
+
TSortable extends boolean = true,
|
|
392
|
+
TUrl extends boolean = true,
|
|
370
393
|
>(
|
|
371
394
|
accessor: TAccessor,
|
|
372
395
|
column: WithRowTypedMeta<
|
|
@@ -374,20 +397,43 @@ export type TMDataGridColumnHelper<TData extends RowData> = {
|
|
|
374
397
|
? DisplayColumnDef<TMDataGridFeatures, TData, TValue>
|
|
375
398
|
: IdentifiedColumnDef<TMDataGridFeatures, TData, TValue>,
|
|
376
399
|
TData
|
|
377
|
-
|
|
378
|
-
|
|
400
|
+
> &
|
|
401
|
+
TMDataGridColumnInfoHints<
|
|
402
|
+
TId,
|
|
403
|
+
TType,
|
|
404
|
+
TOperator,
|
|
405
|
+
TFilterable,
|
|
406
|
+
TSortable,
|
|
407
|
+
TUrl
|
|
408
|
+
>,
|
|
409
|
+
) => (TAccessor extends AccessorFn<TData>
|
|
379
410
|
? AccessorFnColumnDef<TMDataGridFeatures, TData, TValue>
|
|
380
|
-
: AccessorKeyColumnDef<TMDataGridFeatures, TData, TValue
|
|
411
|
+
: AccessorKeyColumnDef<TMDataGridFeatures, TData, TValue>) &
|
|
412
|
+
TMDataGridTypedColumn<{
|
|
413
|
+
id: TMDataGridAccessorColumnId<TAccessor, TId>;
|
|
414
|
+
type: TType;
|
|
415
|
+
value: TValue;
|
|
416
|
+
operators: TOperator;
|
|
417
|
+
filterable: TFilterable;
|
|
418
|
+
sortable: TSortable;
|
|
419
|
+
url: TUrl;
|
|
420
|
+
}>;
|
|
381
421
|
columns: ColumnHelper<TMDataGridFeatures, TData>["columns"];
|
|
382
422
|
display: (
|
|
383
423
|
column: WithRowTypedMeta<DisplayColumnDef<TMDataGridFeatures, TData>, TData>,
|
|
384
424
|
) => DisplayColumnDef<TMDataGridFeatures, TData, unknown>;
|
|
385
|
-
group
|
|
425
|
+
/** A header group. Carries the column info of every column under it. */
|
|
426
|
+
group: <
|
|
427
|
+
const TChildren extends ReadonlyArray<
|
|
428
|
+
ColumnDef<TMDataGridFeatures, TData, any>
|
|
429
|
+
>,
|
|
430
|
+
>(
|
|
386
431
|
column: WithRowTypedMeta<
|
|
387
|
-
GroupColumnDef<TMDataGridFeatures, TData, unknown>,
|
|
432
|
+
Omit<GroupColumnDef<TMDataGridFeatures, TData, unknown>, "columns">,
|
|
388
433
|
TData
|
|
389
|
-
|
|
390
|
-
) => GroupColumnDef<TMDataGridFeatures, TData, unknown
|
|
434
|
+
> & { columns: TChildren },
|
|
435
|
+
) => GroupColumnDef<TMDataGridFeatures, TData, unknown> &
|
|
436
|
+
TMDataGridTypedColumn<TMDataGridColumnInfoOf<TChildren[number]>>;
|
|
391
437
|
};
|
|
392
438
|
|
|
393
439
|
export function createTMDataGridColumnHelper<
|
|
@@ -819,8 +865,9 @@ export type UseTMDataGridOptions<TData extends RowData> = Omit<
|
|
|
819
865
|
}
|
|
820
866
|
) & {
|
|
821
867
|
/**
|
|
822
|
-
* Restore and persist table state across mounts. Two keys, because
|
|
823
|
-
* kinds of state have different lifetimes
|
|
868
|
+
* Restore and persist table state across mounts. Two storage keys, because
|
|
869
|
+
* the two kinds of state have different lifetimes, and a URL search param
|
|
870
|
+
* for the data slices a link should carry - see {@link TMDataGridPersistence}.
|
|
824
871
|
*
|
|
825
872
|
* Keep the object referentially stable (module scope or `useMemo`); it is a
|
|
826
873
|
* dependency of the subscription that writes back.
|
|
@@ -1132,6 +1179,17 @@ function withoutControlColumnVisibility(
|
|
|
1132
1179
|
return scrubbed;
|
|
1133
1180
|
}
|
|
1134
1181
|
|
|
1182
|
+
/**
|
|
1183
|
+
* The pagination the table starts with before anything is restored. Shared by
|
|
1184
|
+
* the table's `initialState` and the persistence baseline, so the URL param
|
|
1185
|
+
* compares against the page size the table really starts on.
|
|
1186
|
+
*/
|
|
1187
|
+
function startingPagination(
|
|
1188
|
+
initial: Partial<TableState<TMDataGridFeatures>> | undefined,
|
|
1189
|
+
): TableState<TMDataGridFeatures>["pagination"] {
|
|
1190
|
+
return { pageIndex: 0, pageSize: 25, ...initial?.pagination };
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1135
1193
|
/**
|
|
1136
1194
|
* Point every column at the operator-dispatching filter function, and take
|
|
1137
1195
|
* the aggregation defaults back off, unless the column opted into its own.
|
|
@@ -1377,13 +1435,34 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1377
1435
|
editColumnLabel,
|
|
1378
1436
|
]);
|
|
1379
1437
|
|
|
1438
|
+
// Leaf columns whose filter stays out of the `urlKey` param. Follows the
|
|
1439
|
+
// columns, since the write subscription below outlives a column change.
|
|
1440
|
+
const urlExcludedColumnIds = useMemo(
|
|
1441
|
+
() => collectUrlExcludedColumnIds(columns),
|
|
1442
|
+
[columns],
|
|
1443
|
+
);
|
|
1444
|
+
// What the table would start with if nothing were restored: TanStack's
|
|
1445
|
+
// feature defaults under the consumer's `initialState`, with the grid's own
|
|
1446
|
+
// page size. The `urlKey` param carries only the slices that differ from
|
|
1447
|
+
// it, and an opted-out column keeps its filter from it on read. Only the
|
|
1448
|
+
// data slices are used, so the layout part of the `initialState` recipe
|
|
1449
|
+
// below is not repeated here. Read once on mount, like `initialState`.
|
|
1450
|
+
const [persistBaseline] = useState(() =>
|
|
1451
|
+
getInitialTableState(tmDataGridFeatures, {
|
|
1452
|
+
...options.initialState,
|
|
1453
|
+
pagination: startingPagination(options.initialState),
|
|
1454
|
+
}),
|
|
1455
|
+
);
|
|
1380
1456
|
// Read once on mount: `initialState` is only consumed on the first render,
|
|
1381
1457
|
// and re-reading later would fight the user's live edits.
|
|
1382
1458
|
// Realigned against the ids this render is about to construct - lanes
|
|
1383
1459
|
// included - so a column removed between deploys does not leave a ghost
|
|
1384
1460
|
// sort, filter or width behind.
|
|
1385
1461
|
const [persistedState] = useState(() =>
|
|
1386
|
-
readPersistedState(persist, collectLeafColumnIds(columns)
|
|
1462
|
+
readPersistedState(persist, collectLeafColumnIds(columns), {
|
|
1463
|
+
baseline: persistBaseline,
|
|
1464
|
+
excludedFilterColumnIds: urlExcludedColumnIds,
|
|
1465
|
+
}),
|
|
1387
1466
|
);
|
|
1388
1467
|
|
|
1389
1468
|
// Restored grouping decides whether the tree column starts out visible, so a
|
|
@@ -1731,9 +1810,7 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1731
1810
|
options.initialState?.columnPinning?.end,
|
|
1732
1811
|
}),
|
|
1733
1812
|
pagination: {
|
|
1734
|
-
|
|
1735
|
-
pageSize: 25,
|
|
1736
|
-
...options.initialState?.pagination,
|
|
1813
|
+
...startingPagination(options.initialState),
|
|
1737
1814
|
...persistedState.pagination,
|
|
1738
1815
|
},
|
|
1739
1816
|
},
|
|
@@ -1826,6 +1903,19 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1826
1903
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1827
1904
|
}, []);
|
|
1828
1905
|
|
|
1906
|
+
// A slice listed under both `dataKey` and `urlKey` is handled by `urlKey`
|
|
1907
|
+
// alone - see resolveKeys in persistence.ts. Reported once, because the
|
|
1908
|
+
// consumer most likely expects the stored copy to still apply.
|
|
1909
|
+
useEffect(() => {
|
|
1910
|
+
for (const slice of findOverlappingSlices(persist)) {
|
|
1911
|
+
console.error(
|
|
1912
|
+
`TMDataGrid: persist.dataKey and persist.urlKey both list ${slice} - a slice lives in one group; urlKey takes it.`,
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
// A mount-time contract, like the checks above.
|
|
1916
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1917
|
+
}, []);
|
|
1918
|
+
|
|
1829
1919
|
// The tree column appears with the first grouped column and goes away with
|
|
1830
1920
|
// the last, so an ungrouped grid looks exactly as it did before grouping
|
|
1831
1921
|
// existed. Driven from a subscription rather than by rebuilding the column
|
|
@@ -1892,33 +1982,67 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1892
1982
|
// selection - and `setItem` serialises synchronously on the main thread. The
|
|
1893
1983
|
// trailing edge is enough: storage only has to agree with the table once the
|
|
1894
1984
|
// user stops.
|
|
1985
|
+
//
|
|
1986
|
+
// The `urlKey` inputs are read through a ref: the excluded columns follow
|
|
1987
|
+
// the column defs, and re-subscribing on a column change would write once
|
|
1988
|
+
// more for nothing.
|
|
1989
|
+
const urlPersistInputsRef = useRef({
|
|
1990
|
+
baseline: persistBaseline,
|
|
1991
|
+
excludedFilterColumnIds: urlExcludedColumnIds,
|
|
1992
|
+
});
|
|
1993
|
+
urlPersistInputsRef.current = {
|
|
1994
|
+
baseline: persistBaseline,
|
|
1995
|
+
excludedFilterColumnIds: urlExcludedColumnIds,
|
|
1996
|
+
};
|
|
1997
|
+
// Keyed on the store, not the table: `useTable` returns a new copy of the
|
|
1998
|
+
// table on every render (it memoizes on the options object, built inline
|
|
1999
|
+
// above), while every copy shares the one store. Keyed on the table, each
|
|
2000
|
+
// render re-ran this effect, whose cleanup flushed and whose setup wrote
|
|
2001
|
+
// again at once - no debounce at all.
|
|
2002
|
+
const tableStore = table.store;
|
|
1895
2003
|
useEffect(() => {
|
|
1896
2004
|
if (!hasPersistenceKeys(persist)) return;
|
|
1897
|
-
writePersistedState(
|
|
2005
|
+
writePersistedState(tableStore.state, persist, urlPersistInputsRef.current);
|
|
1898
2006
|
|
|
1899
2007
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
1900
2008
|
let pending: TableState<TMDataGridFeatures> | null = null;
|
|
1901
2009
|
|
|
1902
|
-
const flush = () => {
|
|
2010
|
+
const flush = ({ writeUrl }: { writeUrl: boolean }) => {
|
|
1903
2011
|
timeout = undefined;
|
|
1904
2012
|
if (pending === null) return;
|
|
1905
|
-
writePersistedState(pending, persist
|
|
2013
|
+
writePersistedState(pending, persist, {
|
|
2014
|
+
...urlPersistInputsRef.current,
|
|
2015
|
+
writeUrl,
|
|
2016
|
+
});
|
|
1906
2017
|
pending = null;
|
|
1907
2018
|
};
|
|
1908
2019
|
|
|
1909
|
-
|
|
2020
|
+
// Where the grid was when it subscribed, to tell an unmount by navigation
|
|
2021
|
+
// from an in-page one - see the cleanup.
|
|
2022
|
+
const subscribedPathname =
|
|
2023
|
+
typeof window === "undefined" ? null : window.location.pathname;
|
|
2024
|
+
|
|
2025
|
+
const subscription = tableStore.subscribe((state) => {
|
|
1910
2026
|
pending = state;
|
|
1911
2027
|
if (timeout !== undefined) clearTimeout(timeout);
|
|
1912
|
-
timeout = setTimeout(flush, PERSIST_DEBOUNCE_MS);
|
|
2028
|
+
timeout = setTimeout(() => flush({ writeUrl: true }), PERSIST_DEBOUNCE_MS);
|
|
1913
2029
|
});
|
|
1914
2030
|
|
|
1915
2031
|
return () => {
|
|
1916
2032
|
subscription.unsubscribe();
|
|
1917
2033
|
if (timeout !== undefined) clearTimeout(timeout);
|
|
1918
|
-
// An unmount mid-debounce would otherwise drop the last change.
|
|
1919
|
-
|
|
2034
|
+
// An unmount mid-debounce would otherwise drop the last change. The URL
|
|
2035
|
+
// only while still on the page: an SPA navigation changes the pathname
|
|
2036
|
+
// before React unmounts the grid, so the param would land on the next
|
|
2037
|
+
// page's URL. An in-page unmount - a modal closing, a tab panel
|
|
2038
|
+
// switching - leaves the pathname alone and keeps its last filter.
|
|
2039
|
+
flush({
|
|
2040
|
+
writeUrl:
|
|
2041
|
+
subscribedPathname !== null &&
|
|
2042
|
+
subscribedPathname === window.location.pathname,
|
|
2043
|
+
});
|
|
1920
2044
|
};
|
|
1921
|
-
}, [persist,
|
|
2045
|
+
}, [persist, tableStore]);
|
|
1922
2046
|
|
|
1923
2047
|
const ui = useCreateStore<TMDataGridUiState, TMDataGridUiActions>(
|
|
1924
2048
|
{
|