@jielga/tmdatagrid 2.0.0-beta.3 → 2.0.0-beta.4
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 +37 -9
- package/dist/index.js +1021 -948
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/appearance/SKILL.md +1 -1
- package/skills/cell-selection/SKILL.md +1 -1
- package/skills/columns/SKILL.md +1 -1
- package/skills/data/SKILL.md +1 -1
- package/skills/editing/SKILL.md +18 -6
- package/skills/filtering/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +1 -1
- package/skills/grouping/SKILL.md +1 -1
- package/skills/options/SKILL.md +32 -2
- package/skills/rows/SKILL.md +1 -1
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +3 -0
- package/src/tmdatagrid/components/TMDataGridTable.tsx +13 -0
- package/src/tmdatagrid/core/controlledState.ts +179 -0
- package/src/tmdatagrid/core/editEngine.ts +92 -15
- package/src/tmdatagrid/index.ts +2 -0
- package/src/tmdatagrid/useTMDataGrid.tsx +115 -4
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
type TMDataGridEditApi,
|
|
53
53
|
type TMDataGridEditCommitArgs,
|
|
54
54
|
type TMDataGridSaveDraftsArgs,
|
|
55
|
+
type TMDataGridSaveDraftsResult,
|
|
55
56
|
type TMDataGridEditEngineContext,
|
|
56
57
|
type TMDataGridColumnEditOptions,
|
|
57
58
|
type TMDataGridEditMode,
|
|
@@ -93,6 +94,11 @@ import {
|
|
|
93
94
|
isSameCell,
|
|
94
95
|
type TMDataGridCellPosition,
|
|
95
96
|
} from "./core/cellNavigation";
|
|
97
|
+
import {
|
|
98
|
+
findFrozenStateSlices,
|
|
99
|
+
stabilizeControlledState,
|
|
100
|
+
withoutUndefinedSlices,
|
|
101
|
+
} from "./core/controlledState";
|
|
96
102
|
import type { TMDataGridCellRange } from "./core/cellRange";
|
|
97
103
|
import {
|
|
98
104
|
createSelectColumn,
|
|
@@ -527,12 +533,16 @@ export type TMDataGridEditingOptions<TData extends RowData> =
|
|
|
527
533
|
* `saveDrafts` falls back to the per-row
|
|
528
534
|
* {@link TMDataGridEditingCallbacks.onCommit} loop.
|
|
529
535
|
*
|
|
530
|
-
* Rows still open are not in the payload and stay open
|
|
531
|
-
*
|
|
536
|
+
* Rows still open are not in the payload and stay open. Returning
|
|
537
|
+
* nothing saves the whole store and throwing saves none of it;
|
|
538
|
+
* return a {@link TMDataGridSaveDraftsResult} to save part of it.
|
|
532
539
|
*/
|
|
533
540
|
onSaveDrafts?: (
|
|
534
541
|
args: TMDataGridSaveDraftsArgs<TData>,
|
|
535
|
-
) =>
|
|
542
|
+
) =>
|
|
543
|
+
| void
|
|
544
|
+
| TMDataGridSaveDraftsResult
|
|
545
|
+
| Promise<void | TMDataGridSaveDraftsResult>;
|
|
536
546
|
/**
|
|
537
547
|
* @deprecated Renamed to {@link onSaveDrafts} - it fires when the
|
|
538
548
|
* draft store is saved, not when a row commits into it. Still
|
|
@@ -540,7 +550,10 @@ export type TMDataGridEditingOptions<TData extends RowData> =
|
|
|
540
550
|
*/
|
|
541
551
|
onCommitDrafts?: (
|
|
542
552
|
args: TMDataGridSaveDraftsArgs<TData>,
|
|
543
|
-
) =>
|
|
553
|
+
) =>
|
|
554
|
+
| void
|
|
555
|
+
| TMDataGridSaveDraftsResult
|
|
556
|
+
| Promise<void | TMDataGridSaveDraftsResult>;
|
|
544
557
|
/**
|
|
545
558
|
* Keep committed entry rows pinned in the sticky entry block until
|
|
546
559
|
* the draft store is saved. Off by default: a committed row joins
|
|
@@ -1019,6 +1032,58 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1019
1032
|
const initialGrouping =
|
|
1020
1033
|
persistedState.grouping ?? options.initialState?.grouping ?? [];
|
|
1021
1034
|
|
|
1035
|
+
// Current grouping state, feeding the tree column's entry in a controlled
|
|
1036
|
+
// `columnVisibility` (below). A ref, not state: it is only read while
|
|
1037
|
+
// building the options, and the store change that updates it re-renders the
|
|
1038
|
+
// hook anyway. A controlled `grouping` takes precedence over the persisted
|
|
1039
|
+
// one; grouping held in an external atom is not readable here and is
|
|
1040
|
+
// corrected by the effect below with one extra write on mount.
|
|
1041
|
+
const groupingActiveRef = useRef(
|
|
1042
|
+
(options.state?.grouping ?? initialGrouping).length > 0,
|
|
1043
|
+
);
|
|
1044
|
+
|
|
1045
|
+
// Removes keys set to `undefined` before TanStack writes them into the
|
|
1046
|
+
// slice atoms; such a key means the slice is not controlled.
|
|
1047
|
+
const consumerState = withoutUndefinedSlices(options.state);
|
|
1048
|
+
|
|
1049
|
+
// A controlled `columnVisibility` replaces the whole map on every options
|
|
1050
|
+
// sync, including the grid's own entries, so the tree column's entry must be
|
|
1051
|
+
// re-applied here. Entries for the control columns are removed, as they are
|
|
1052
|
+
// for `initialState`: their visibility follows the feature options, not the
|
|
1053
|
+
// visibility map.
|
|
1054
|
+
const controlledColumnVisibility = consumerState?.columnVisibility;
|
|
1055
|
+
const requestedState =
|
|
1056
|
+
consumerState !== undefined && controlledColumnVisibility !== undefined
|
|
1057
|
+
? {
|
|
1058
|
+
...consumerState,
|
|
1059
|
+
columnVisibility: {
|
|
1060
|
+
...withoutControlColumnVisibility(controlledColumnVisibility),
|
|
1061
|
+
...(groupColumnEnabled
|
|
1062
|
+
? { [GROUP_COLUMN_ID]: groupingActiveRef.current }
|
|
1063
|
+
: {}),
|
|
1064
|
+
},
|
|
1065
|
+
}
|
|
1066
|
+
: consumerState;
|
|
1067
|
+
|
|
1068
|
+
// Controlled state is synced to the table on every render and compared by
|
|
1069
|
+
// identity, so a `state` object built in the consumer's render body would
|
|
1070
|
+
// cause an infinite render loop. Unchanged slices are forwarded with the
|
|
1071
|
+
// previous render's identity instead - see controlledState.ts.
|
|
1072
|
+
const controlledStateRef = useRef<Partial<TableState<TMDataGridFeatures>>>(
|
|
1073
|
+
undefined,
|
|
1074
|
+
);
|
|
1075
|
+
// Exception: the grouping workaround below republishes `columnOrder` and
|
|
1076
|
+
// `columnVisibility` with unchanged contents and a new identity to repair
|
|
1077
|
+
// table-core's missing memo deps - the exact write stabilization cancels.
|
|
1078
|
+
// After it runs, one render forwards the controlled state unstabilized so
|
|
1079
|
+
// the new identities reach the atoms. A single render cannot loop.
|
|
1080
|
+
const republishControlledStateRef = useRef(false);
|
|
1081
|
+
const controlledState = republishControlledStateRef.current
|
|
1082
|
+
? requestedState
|
|
1083
|
+
: stabilizeControlledState(requestedState, controlledStateRef.current);
|
|
1084
|
+
republishControlledStateRef.current = false;
|
|
1085
|
+
controlledStateRef.current = controlledState;
|
|
1086
|
+
|
|
1022
1087
|
const table = useTable({
|
|
1023
1088
|
columnResizeMode: "onChange",
|
|
1024
1089
|
enableSorting: true,
|
|
@@ -1057,6 +1122,8 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1057
1122
|
: options.enableRowPinning === true),
|
|
1058
1123
|
features: tmDataGridFeatures,
|
|
1059
1124
|
columns: columns as TableOptions<TMDataGridFeatures, TData>["columns"],
|
|
1125
|
+
// The stabilized controlled state; `undefined` when nothing is controlled.
|
|
1126
|
+
state: controlledState,
|
|
1060
1127
|
initialState: {
|
|
1061
1128
|
...options.initialState,
|
|
1062
1129
|
...persistedState,
|
|
@@ -1164,6 +1231,20 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1164
1231
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1165
1232
|
}, []);
|
|
1166
1233
|
|
|
1234
|
+
// A controlled slice without its `onXChange` cannot change: TanStack routes
|
|
1235
|
+
// every write through the callback, and the next options sync restores the
|
|
1236
|
+
// consumer's value. Warned once in development; `initialState` is the option
|
|
1237
|
+
// for a starting value.
|
|
1238
|
+
useEffect(() => {
|
|
1239
|
+
for (const { slice, handler } of findFrozenStateSlices(options)) {
|
|
1240
|
+
console.warn(
|
|
1241
|
+
`TMDataGrid: state.${slice} is controlled but no ${handler} was passed - the slice cannot change. Add ${handler}, or use initialState.${slice} for a starting value.`,
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
// A mount-time contract, like the check above.
|
|
1245
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1246
|
+
}, []);
|
|
1247
|
+
|
|
1167
1248
|
// Two things have to happen whenever `grouping` changes.
|
|
1168
1249
|
//
|
|
1169
1250
|
// One: the tree column appears with the first grouped column and goes away
|
|
@@ -1203,10 +1284,40 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1203
1284
|
useEffect(() => {
|
|
1204
1285
|
if (!groupColumnEnabled) return;
|
|
1205
1286
|
let previousGrouping = table.store.state.grouping;
|
|
1287
|
+
// Update the ref before any write: it feeds the visibility injection
|
|
1288
|
+
// above, and a write below re-renders the hook. With a stale ref the
|
|
1289
|
+
// injection would restore the old value on every render. This also
|
|
1290
|
+
// corrects the mount value when an external atom owns `grouping`, which
|
|
1291
|
+
// the ref's initializer cannot read.
|
|
1292
|
+
groupingActiveRef.current = previousGrouping.length > 0;
|
|
1293
|
+
|
|
1294
|
+
// The tree column's entry in `initialState` only reaches a slice the
|
|
1295
|
+
// table owns. With `columnVisibility` in an external atom the entry is
|
|
1296
|
+
// missing, and a missing entry means visible: the tree column would
|
|
1297
|
+
// render in an ungrouped grid. The entry is seeded here through the table
|
|
1298
|
+
// API instead, so the write reaches whichever store owns the slice. No-op
|
|
1299
|
+
// when the entry is already correct. `?.`: an external atom can hold
|
|
1300
|
+
// `undefined`.
|
|
1301
|
+
if (
|
|
1302
|
+
table.store.state.columnVisibility?.[GROUP_COLUMN_ID] !==
|
|
1303
|
+
(previousGrouping.length > 0)
|
|
1304
|
+
) {
|
|
1305
|
+
table.setColumnVisibility((old) => ({
|
|
1306
|
+
...old,
|
|
1307
|
+
[GROUP_COLUMN_ID]: previousGrouping.length > 0,
|
|
1308
|
+
}));
|
|
1309
|
+
}
|
|
1206
1310
|
|
|
1207
1311
|
const subscription = table.store.subscribe((state) => {
|
|
1208
1312
|
if (state.grouping === previousGrouping) return;
|
|
1209
1313
|
previousGrouping = state.grouping;
|
|
1314
|
+
// Keeps the entry injected into a controlled `columnVisibility` (see
|
|
1315
|
+
// requestedState) in sync with grouping.
|
|
1316
|
+
groupingActiveRef.current = state.grouping.length > 0;
|
|
1317
|
+
// On a controlled slice the writes below round-trip through the
|
|
1318
|
+
// consumer's handler; the next render must forward them unstabilized.
|
|
1319
|
+
// See republishControlledStateRef.
|
|
1320
|
+
republishControlledStateRef.current = true;
|
|
1210
1321
|
|
|
1211
1322
|
table.setColumnVisibility((old) => ({
|
|
1212
1323
|
...old,
|