@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.
- package/dist/index.d.ts +78 -5
- package/dist/index.js +2194 -1992
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/cell-selection.md +1 -1
- package/docs/column-layout.md +13 -8
- package/docs/columns.md +59 -3
- package/docs/components.md +1 -0
- package/docs/export.md +2 -2
- package/docs/filtering.md +108 -15
- package/docs/getting-started.md +40 -0
- package/docs/grouping.md +9 -6
- package/docs/loading-and-empty.md +29 -9
- package/docs/localization.md +130 -3
- package/docs/menu.md +1 -1
- package/docs/pagination.md +26 -7
- package/docs/persistence.md +101 -2
- package/docs/row-details.md +41 -4
- package/docs/row-interaction.md +2 -2
- package/docs/row-selection.md +6 -7
- package/docs/row-styling.md +17 -0
- package/docs/scrolling.md +1 -1
- package/docs/server-side.md +3 -1
- package/docs/testing.md +18 -0
- package/docs/use-tm-data-grid.md +75 -36
- package/package.json +1 -1
- package/skills/appearance/SKILL.md +6 -4
- package/skills/appearance/references/labels.md +126 -0
- package/skills/cell-selection/SKILL.md +2 -2
- package/skills/columns/SKILL.md +44 -43
- package/skills/columns/references/columns-api.md +16 -1
- package/skills/data/SKILL.md +77 -50
- package/skills/editing/SKILL.md +1 -1
- package/skills/filtering/SKILL.md +94 -60
- package/skills/filtering/references/custom-filters.md +118 -0
- package/skills/getting-started/SKILL.md +37 -2
- package/skills/grouping/SKILL.md +16 -9
- package/skills/migrating-to-2/SKILL.md +1 -1
- package/skills/options/SKILL.md +206 -55
- package/skills/rows/SKILL.md +70 -39
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +12 -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/TMDataGridTable.module.css +15 -6
- package/src/components/TMDataGridTable.tsx +253 -41
- package/src/components/sticky.module.css +27 -7
- package/src/core/autosize.ts +19 -7
- package/src/core/cellRange.ts +11 -2
- package/src/core/colSpan.ts +78 -0
- package/src/core/columnUtils.ts +7 -0
- package/src/core/filterControls.ts +8 -0
- package/src/core/filterOperators.ts +29 -1
- package/src/core/persistence.ts +322 -26
- package/src/index.ts +5 -0
- package/src/useTMDataGrid.tsx +133 -14
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,
|
|
@@ -79,6 +80,7 @@ import {
|
|
|
79
80
|
tmDataGridFilterFn,
|
|
80
81
|
} from "./core/filterOperators";
|
|
81
82
|
import {
|
|
83
|
+
DEFAULT_COLUMN_MIN_SIZE,
|
|
82
84
|
getColumnDefaultOperator,
|
|
83
85
|
isControlColumn,
|
|
84
86
|
isGeneratedColumn,
|
|
@@ -102,6 +104,8 @@ import {
|
|
|
102
104
|
} from "./core/labels";
|
|
103
105
|
import {
|
|
104
106
|
collectLeafColumnIds,
|
|
107
|
+
collectUrlExcludedColumnIds,
|
|
108
|
+
findOverlappingSlices,
|
|
105
109
|
hasPersistenceKeys,
|
|
106
110
|
readPersistedState,
|
|
107
111
|
type TMDataGridPersistence,
|
|
@@ -194,6 +198,28 @@ export type TMDataGridColumnMeta<TData extends RowData = TMDataGridRowData> = {
|
|
|
194
198
|
/** Share of the leftover width this column claims. Defaults to `1`. */
|
|
195
199
|
flex?: number;
|
|
196
200
|
align?: "left" | "right" | "center";
|
|
201
|
+
/**
|
|
202
|
+
* How many columns this column's cell covers on a row, counting itself.
|
|
203
|
+
* Defaults to `1`.
|
|
204
|
+
*
|
|
205
|
+
* A cell spanning `n` is one cell `n` columns wide; the `n - 1` cells to its
|
|
206
|
+
* right do not render on that row. A function decides per row:
|
|
207
|
+
*
|
|
208
|
+
* ```tsx
|
|
209
|
+
* meta: {
|
|
210
|
+
* // An open row's title takes the rest of the row.
|
|
211
|
+
* colSpan: ({ row }) => (row.getIsExpanded() ? Infinity : 1),
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*
|
|
215
|
+
* Counted over the displayed columns, so hiding or moving a column changes
|
|
216
|
+
* what a span covers. A span stops at the last of your columns - it never
|
|
217
|
+
* covers the checkbox, tree, details or edit lane - and at the edge of its
|
|
218
|
+
* pinned block. Group rows, and a row open in row edit mode, ignore it.
|
|
219
|
+
*
|
|
220
|
+
* Display only: sorting, filtering, copy and export still see every column.
|
|
221
|
+
*/
|
|
222
|
+
colSpan?: number | ((args: TMDataGridColSpanArgs<TData>) => number);
|
|
197
223
|
/**
|
|
198
224
|
* Size the column to its widest mounted content once, after the first rows
|
|
199
225
|
* render - unless a persisted or user-set width already covers it. The same
|
|
@@ -385,6 +411,12 @@ export type TMDataGridDetailsArgs<TData extends RowData> = {
|
|
|
385
411
|
table: TMDataGridTable<TData>;
|
|
386
412
|
};
|
|
387
413
|
|
|
414
|
+
/** What a `meta.colSpan` function receives. */
|
|
415
|
+
export type TMDataGridColSpanArgs<TData extends RowData> = {
|
|
416
|
+
row: Row<TMDataGridFeatures, TData>;
|
|
417
|
+
table: TMDataGridTable<TData>;
|
|
418
|
+
};
|
|
419
|
+
|
|
388
420
|
/** Builds the panel shown underneath an expanded row. See `renderDetails`. */
|
|
389
421
|
export type TMDataGridDetailsRenderer<TData extends RowData> = (
|
|
390
422
|
args: TMDataGridDetailsArgs<TData>,
|
|
@@ -790,8 +822,9 @@ export type UseTMDataGridOptions<TData extends RowData> = Omit<
|
|
|
790
822
|
}
|
|
791
823
|
) & {
|
|
792
824
|
/**
|
|
793
|
-
* Restore and persist table state across mounts. Two keys, because
|
|
794
|
-
* kinds of state have different lifetimes
|
|
825
|
+
* Restore and persist table state across mounts. Two storage keys, because
|
|
826
|
+
* the two kinds of state have different lifetimes, and a URL search param
|
|
827
|
+
* for the data slices a link should carry - see {@link TMDataGridPersistence}.
|
|
795
828
|
*
|
|
796
829
|
* Keep the object referentially stable (module scope or `useMemo`); it is a
|
|
797
830
|
* dependency of the subscription that writes back.
|
|
@@ -1103,6 +1136,17 @@ function withoutControlColumnVisibility(
|
|
|
1103
1136
|
return scrubbed;
|
|
1104
1137
|
}
|
|
1105
1138
|
|
|
1139
|
+
/**
|
|
1140
|
+
* The pagination the table starts with before anything is restored. Shared by
|
|
1141
|
+
* the table's `initialState` and the persistence baseline, so the URL param
|
|
1142
|
+
* compares against the page size the table really starts on.
|
|
1143
|
+
*/
|
|
1144
|
+
function startingPagination(
|
|
1145
|
+
initial: Partial<TableState<TMDataGridFeatures>> | undefined,
|
|
1146
|
+
): TableState<TMDataGridFeatures>["pagination"] {
|
|
1147
|
+
return { pageIndex: 0, pageSize: 25, ...initial?.pagination };
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1106
1150
|
/**
|
|
1107
1151
|
* Point every column at the operator-dispatching filter function, and take
|
|
1108
1152
|
* the aggregation defaults back off, unless the column opted into its own.
|
|
@@ -1348,13 +1392,34 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1348
1392
|
editColumnLabel,
|
|
1349
1393
|
]);
|
|
1350
1394
|
|
|
1395
|
+
// Leaf columns whose filter stays out of the `urlKey` param. Follows the
|
|
1396
|
+
// columns, since the write subscription below outlives a column change.
|
|
1397
|
+
const urlExcludedColumnIds = useMemo(
|
|
1398
|
+
() => collectUrlExcludedColumnIds(columns),
|
|
1399
|
+
[columns],
|
|
1400
|
+
);
|
|
1401
|
+
// What the table would start with if nothing were restored: TanStack's
|
|
1402
|
+
// feature defaults under the consumer's `initialState`, with the grid's own
|
|
1403
|
+
// page size. The `urlKey` param carries only the slices that differ from
|
|
1404
|
+
// it, and an opted-out column keeps its filter from it on read. Only the
|
|
1405
|
+
// data slices are used, so the layout part of the `initialState` recipe
|
|
1406
|
+
// below is not repeated here. Read once on mount, like `initialState`.
|
|
1407
|
+
const [persistBaseline] = useState(() =>
|
|
1408
|
+
getInitialTableState(tmDataGridFeatures, {
|
|
1409
|
+
...options.initialState,
|
|
1410
|
+
pagination: startingPagination(options.initialState),
|
|
1411
|
+
}),
|
|
1412
|
+
);
|
|
1351
1413
|
// Read once on mount: `initialState` is only consumed on the first render,
|
|
1352
1414
|
// and re-reading later would fight the user's live edits.
|
|
1353
1415
|
// Realigned against the ids this render is about to construct - lanes
|
|
1354
1416
|
// included - so a column removed between deploys does not leave a ghost
|
|
1355
1417
|
// sort, filter or width behind.
|
|
1356
1418
|
const [persistedState] = useState(() =>
|
|
1357
|
-
readPersistedState(persist, collectLeafColumnIds(columns)
|
|
1419
|
+
readPersistedState(persist, collectLeafColumnIds(columns), {
|
|
1420
|
+
baseline: persistBaseline,
|
|
1421
|
+
excludedFilterColumnIds: urlExcludedColumnIds,
|
|
1422
|
+
}),
|
|
1358
1423
|
);
|
|
1359
1424
|
|
|
1360
1425
|
// Restored grouping decides whether the tree column starts out visible, so a
|
|
@@ -1556,6 +1621,14 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1556
1621
|
() => tableRef.current as unknown as TMDataGridQueryTable,
|
|
1557
1622
|
[],
|
|
1558
1623
|
);
|
|
1624
|
+
// The grid's `minSize` floor under the consumer's `defaultColumn`, which
|
|
1625
|
+
// still wins per key. Keyed on the consumer's object, because TanStack
|
|
1626
|
+
// memoizes the default column def on `options.defaultColumn` identity.
|
|
1627
|
+
const consumerDefaultColumn = options.defaultColumn;
|
|
1628
|
+
const defaultColumn = useMemo(
|
|
1629
|
+
() => ({ minSize: DEFAULT_COLUMN_MIN_SIZE, ...consumerDefaultColumn }),
|
|
1630
|
+
[consumerDefaultColumn],
|
|
1631
|
+
);
|
|
1559
1632
|
|
|
1560
1633
|
// The sync of `state` into the table's atoms happens inside this call, in
|
|
1561
1634
|
// the render body, and publishing from there makes React warn about the
|
|
@@ -1624,6 +1697,7 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1624
1697
|
) as TableOptions<TMDataGridFeatures, TData>["onGroupingChange"],
|
|
1625
1698
|
}
|
|
1626
1699
|
: {}),
|
|
1700
|
+
defaultColumn,
|
|
1627
1701
|
// The rows as shown - see `shown` above. The consumer's own array passes
|
|
1628
1702
|
// through untouched while nothing is committed.
|
|
1629
1703
|
data: shown.rows,
|
|
@@ -1693,9 +1767,7 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1693
1767
|
options.initialState?.columnPinning?.end,
|
|
1694
1768
|
}),
|
|
1695
1769
|
pagination: {
|
|
1696
|
-
|
|
1697
|
-
pageSize: 25,
|
|
1698
|
-
...options.initialState?.pagination,
|
|
1770
|
+
...startingPagination(options.initialState),
|
|
1699
1771
|
...persistedState.pagination,
|
|
1700
1772
|
},
|
|
1701
1773
|
},
|
|
@@ -1788,6 +1860,19 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1788
1860
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1789
1861
|
}, []);
|
|
1790
1862
|
|
|
1863
|
+
// A slice listed under both `dataKey` and `urlKey` is handled by `urlKey`
|
|
1864
|
+
// alone - see resolveKeys in persistence.ts. Reported once, because the
|
|
1865
|
+
// consumer most likely expects the stored copy to still apply.
|
|
1866
|
+
useEffect(() => {
|
|
1867
|
+
for (const slice of findOverlappingSlices(persist)) {
|
|
1868
|
+
console.error(
|
|
1869
|
+
`TMDataGrid: persist.dataKey and persist.urlKey both list ${slice} - a slice lives in one group; urlKey takes it.`,
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
// A mount-time contract, like the checks above.
|
|
1873
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1874
|
+
}, []);
|
|
1875
|
+
|
|
1791
1876
|
// The tree column appears with the first grouped column and goes away with
|
|
1792
1877
|
// the last, so an ungrouped grid looks exactly as it did before grouping
|
|
1793
1878
|
// existed. Driven from a subscription rather than by rebuilding the column
|
|
@@ -1854,33 +1939,67 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1854
1939
|
// selection - and `setItem` serialises synchronously on the main thread. The
|
|
1855
1940
|
// trailing edge is enough: storage only has to agree with the table once the
|
|
1856
1941
|
// user stops.
|
|
1942
|
+
//
|
|
1943
|
+
// The `urlKey` inputs are read through a ref: the excluded columns follow
|
|
1944
|
+
// the column defs, and re-subscribing on a column change would write once
|
|
1945
|
+
// more for nothing.
|
|
1946
|
+
const urlPersistInputsRef = useRef({
|
|
1947
|
+
baseline: persistBaseline,
|
|
1948
|
+
excludedFilterColumnIds: urlExcludedColumnIds,
|
|
1949
|
+
});
|
|
1950
|
+
urlPersistInputsRef.current = {
|
|
1951
|
+
baseline: persistBaseline,
|
|
1952
|
+
excludedFilterColumnIds: urlExcludedColumnIds,
|
|
1953
|
+
};
|
|
1954
|
+
// Keyed on the store, not the table: `useTable` returns a new copy of the
|
|
1955
|
+
// table on every render (it memoizes on the options object, built inline
|
|
1956
|
+
// above), while every copy shares the one store. Keyed on the table, each
|
|
1957
|
+
// render re-ran this effect, whose cleanup flushed and whose setup wrote
|
|
1958
|
+
// again at once - no debounce at all.
|
|
1959
|
+
const tableStore = table.store;
|
|
1857
1960
|
useEffect(() => {
|
|
1858
1961
|
if (!hasPersistenceKeys(persist)) return;
|
|
1859
|
-
writePersistedState(
|
|
1962
|
+
writePersistedState(tableStore.state, persist, urlPersistInputsRef.current);
|
|
1860
1963
|
|
|
1861
1964
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
1862
1965
|
let pending: TableState<TMDataGridFeatures> | null = null;
|
|
1863
1966
|
|
|
1864
|
-
const flush = () => {
|
|
1967
|
+
const flush = ({ writeUrl }: { writeUrl: boolean }) => {
|
|
1865
1968
|
timeout = undefined;
|
|
1866
1969
|
if (pending === null) return;
|
|
1867
|
-
writePersistedState(pending, persist
|
|
1970
|
+
writePersistedState(pending, persist, {
|
|
1971
|
+
...urlPersistInputsRef.current,
|
|
1972
|
+
writeUrl,
|
|
1973
|
+
});
|
|
1868
1974
|
pending = null;
|
|
1869
1975
|
};
|
|
1870
1976
|
|
|
1871
|
-
|
|
1977
|
+
// Where the grid was when it subscribed, to tell an unmount by navigation
|
|
1978
|
+
// from an in-page one - see the cleanup.
|
|
1979
|
+
const subscribedPathname =
|
|
1980
|
+
typeof window === "undefined" ? null : window.location.pathname;
|
|
1981
|
+
|
|
1982
|
+
const subscription = tableStore.subscribe((state) => {
|
|
1872
1983
|
pending = state;
|
|
1873
1984
|
if (timeout !== undefined) clearTimeout(timeout);
|
|
1874
|
-
timeout = setTimeout(flush, PERSIST_DEBOUNCE_MS);
|
|
1985
|
+
timeout = setTimeout(() => flush({ writeUrl: true }), PERSIST_DEBOUNCE_MS);
|
|
1875
1986
|
});
|
|
1876
1987
|
|
|
1877
1988
|
return () => {
|
|
1878
1989
|
subscription.unsubscribe();
|
|
1879
1990
|
if (timeout !== undefined) clearTimeout(timeout);
|
|
1880
|
-
// An unmount mid-debounce would otherwise drop the last change.
|
|
1881
|
-
|
|
1991
|
+
// An unmount mid-debounce would otherwise drop the last change. The URL
|
|
1992
|
+
// only while still on the page: an SPA navigation changes the pathname
|
|
1993
|
+
// before React unmounts the grid, so the param would land on the next
|
|
1994
|
+
// page's URL. An in-page unmount - a modal closing, a tab panel
|
|
1995
|
+
// switching - leaves the pathname alone and keeps its last filter.
|
|
1996
|
+
flush({
|
|
1997
|
+
writeUrl:
|
|
1998
|
+
subscribedPathname !== null &&
|
|
1999
|
+
subscribedPathname === window.location.pathname,
|
|
2000
|
+
});
|
|
1882
2001
|
};
|
|
1883
|
-
}, [persist,
|
|
2002
|
+
}, [persist, tableStore]);
|
|
1884
2003
|
|
|
1885
2004
|
const ui = useCreateStore<TMDataGridUiState, TMDataGridUiActions>(
|
|
1886
2005
|
{
|