@jielga/tmdatagrid 2.0.0-beta.0 → 2.0.0-beta.2

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 (52) hide show
  1. package/README.md +32 -34
  2. package/dist/index.d.ts +164 -102
  3. package/dist/index.js +2060 -1705
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/package.json +1 -1
  7. package/skills/appearance/SKILL.md +37 -34
  8. package/skills/cell-selection/SKILL.md +19 -19
  9. package/skills/columns/SKILL.md +9 -9
  10. package/skills/data/SKILL.md +53 -53
  11. package/skills/editing/SKILL.md +152 -311
  12. package/skills/editing/references/common-mistakes.md +177 -0
  13. package/skills/editing/references/editing-api.md +56 -28
  14. package/skills/editing/references/editors-and-validation.md +26 -19
  15. package/skills/filtering/SKILL.md +51 -50
  16. package/skills/getting-started/SKILL.md +10 -10
  17. package/skills/grouping/SKILL.md +41 -43
  18. package/skills/options/SKILL.md +4 -4
  19. package/skills/rows/SKILL.md +59 -59
  20. package/skills/rows/references/rows-api.md +7 -7
  21. package/skills/server-side/SKILL.md +1 -1
  22. package/skills/testing/SKILL.md +11 -10
  23. package/src/tmdatagrid/components/TMDataGrid.module.css +13 -1
  24. package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +20 -8
  25. package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +29 -18
  26. package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +5 -8
  27. package/src/tmdatagrid/components/TMDataGridEditActions.tsx +2 -2
  28. package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +246 -86
  29. package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +176 -77
  30. package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +11 -6
  31. package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +9 -5
  32. package/src/tmdatagrid/components/TMDataGridTable.module.css +47 -3
  33. package/src/tmdatagrid/components/TMDataGridTable.tsx +101 -40
  34. package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +1 -1
  35. package/src/tmdatagrid/components/icons.ts +1 -0
  36. package/src/tmdatagrid/core/autosize.ts +30 -6
  37. package/src/tmdatagrid/core/capabilities.ts +15 -5
  38. package/src/tmdatagrid/core/cellExport.ts +6 -7
  39. package/src/tmdatagrid/core/cellNavigation.ts +2 -2
  40. package/src/tmdatagrid/core/cellRange.ts +6 -6
  41. package/src/tmdatagrid/core/columnOrdering.ts +30 -1
  42. package/src/tmdatagrid/core/columnUtils.ts +14 -0
  43. package/src/tmdatagrid/core/draftCellContext.ts +68 -0
  44. package/src/tmdatagrid/core/editEngine.ts +111 -37
  45. package/src/tmdatagrid/core/filterOperators.ts +6 -6
  46. package/src/tmdatagrid/core/labels.ts +13 -1
  47. package/src/tmdatagrid/core/labelsSv.ts +4 -0
  48. package/src/tmdatagrid/core/matchHighlight.ts +3 -3
  49. package/src/tmdatagrid/core/persistence.ts +3 -3
  50. package/src/tmdatagrid/core/rowSelection.ts +3 -3
  51. package/src/tmdatagrid/index.ts +3 -1
  52. package/src/tmdatagrid/useTMDataGrid.tsx +141 -99
@@ -51,7 +51,7 @@ import {
51
51
  createEditEngine,
52
52
  type TMDataGridEditApi,
53
53
  type TMDataGridEditCommitArgs,
54
- type TMDataGridEditCommitBatchArgs,
54
+ type TMDataGridEditCommitDraftsArgs,
55
55
  type TMDataGridEditEngineContext,
56
56
  type TMDataGridColumnEditOptions,
57
57
  type TMDataGridEditMode,
@@ -64,7 +64,7 @@ import {
64
64
  type TMDataGridColumnType,
65
65
  tmDataGridFilterFn,
66
66
  } from "./core/filterOperators";
67
- import { getColumnDefaultOperator } from "./core/columnUtils";
67
+ import { getColumnDefaultOperator, isControlColumn } from "./core/columnUtils";
68
68
  import type { TMDataGridColumnFilterOptions } from "./core/filterControls";
69
69
  import {
70
70
  createFuzzyRankedSortedRowModel,
@@ -123,14 +123,14 @@ import {
123
123
  const PERSIST_DEBOUNCE_MS = 200;
124
124
 
125
125
  /**
126
- * Per-column configuration the TMDataGrid chrome reads.
126
+ * Per-column configuration the grid's own components read.
127
127
  *
128
- * A stage that owns behaviour gets a namespace - `filter` and `edit`, each
128
+ * The filter and edit stages each get a namespace, `filter` and `edit`,
129
129
  * mirroring the feature's runtime API. What the column *is* stays flat:
130
130
  * `label`, `type`, `options`, `align`, `flex`, `autoSize`, `enableOrdering`.
131
- * `type` and `options` in particular are shared ground - one declaration of
132
- * each feeds the filter panel and the cell editor alike, which is why they sit
133
- * outside both namespaces.
131
+ * `type` and `options` are read by both stages, so one declaration of each
132
+ * feeds the filter panel and the cell editor, which is why they sit outside
133
+ * both namespaces.
134
134
  */
135
135
  export type TMDataGridColumnMeta = {
136
136
  /** Name shown in menus and the column manager. Falls back to a string header. */
@@ -388,7 +388,7 @@ export type TMDataGridApi<TData extends RowData> = {
388
388
  * The edit engine - open forms, dirty/error projections, and the verbs
389
389
  * (`begin`, `commit`, `cancel`, `submitAll`). `edit.getForm(rowId)` hands
390
390
  * out the same TanStack Form the inline editors write through, so a drawer
391
- * or detail panel can share a row's draft. Inert until `editMode` is set.
391
+ * or detail panel can share a row's draft. Inert until `editing` is set.
392
392
  */
393
393
  edit: TMDataGridEditApi;
394
394
  /** Table-level feature switches, re-read from options on every render. */
@@ -431,20 +431,14 @@ export type TMDataGridApi<TData extends RowData> = {
431
431
  */
432
432
  scrollToRow: (args: TMDataGridScrollToRowArgs) => boolean;
433
433
  /**
434
- * @internal Wiring for `TMDataGrid.Table`, which owns the virtualizer and
435
- * fills this in. Not part of the supported surface.
434
+ * @internal Wiring for `TMDataGrid.Table`, which holds the virtualizer and
435
+ * fills this in. Not part of the public API.
436
436
  */
437
437
  scrollerRef: MutableRefObject<TMDataGridScroller>;
438
438
  };
439
439
 
440
- /** The editing callbacks every mode shares. See {@link TMDataGridEditingOptions}. */
440
+ /** The editing members every mode shares. See {@link TMDataGridEditingOptions}. */
441
441
  type TMDataGridEditingCallbacks<TData extends RowData> = {
442
- /**
443
- * `getRowId` stops being optional once editing is on: the forms are keyed
444
- * by row id and live outside the DOM, and the index fallback points at a
445
- * different record after any sort.
446
- */
447
- getRowId: NonNullable<TableOptions<TMDataGridFeatures, TData>["getRowId"]>;
448
442
  /**
449
443
  * Form-level validators for the whole editing row - where cross-field
450
444
  * rules live. TanStack Form's own vocabulary, Standard Schema included:
@@ -470,12 +464,10 @@ type TMDataGridEditingCallbacks<TData extends RowData> = {
470
464
  * draft visible with a busy marker - and a rejection keeps the form open
471
465
  * with the error on the row.
472
466
  *
473
- * `changes` is the per-field diff (one entry in cell mode) for consumers
474
- * who PATCH; `value` is the whole edited row for those who save records.
467
+ * `changes` is the per-field diff (one entry in cell mode), for a PATCH.
468
+ * `value` is the entire edited row, for saving a record.
475
469
  */
476
- onEditCommit?: (
477
- args: TMDataGridEditCommitArgs<TData>,
478
- ) => void | Promise<void>;
470
+ onCommit?: (args: TMDataGridEditCommitArgs<TData>) => void | Promise<void>;
479
471
  /**
480
472
  * Seed values for `edit.addRow()` - the entry row's starting point. A
481
473
  * function is called per added row (fresh timestamps, empty arrays).
@@ -483,13 +475,13 @@ type TMDataGridEditingCallbacks<TData extends RowData> = {
483
475
  newRowDefaults?: TData | (() => TData);
484
476
  /**
485
477
  * Called when an entry row commits: `Enter` or the lane's ✓ under the
486
- * immediate modes, `submitAll` under batch. Create the record and let it
478
+ * immediate modes, `submitAll` under draft. Create the record and let it
487
479
  * arrive back through `data`; the engine's `tempId` never leaves the grid.
488
480
  */
489
481
  onRowAdd?: (args: TMDataGridRowAddArgs<TData>) => void | Promise<void>;
490
482
  /**
491
483
  * Called by `edit.deleteRow` under the immediate modes - confirmation, if
492
- * any, belongs in here. Under batch, deletions accumulate in
484
+ * any, belongs in here. Under draft, deletions accumulate in
493
485
  * `edit.state.deletedRowIds` instead and are reported by `submitAll`.
494
486
  * Setting this also puts the trash can in the edit lane.
495
487
  */
@@ -497,21 +489,24 @@ type TMDataGridEditingCallbacks<TData extends RowData> = {
497
489
  };
498
490
 
499
491
  /**
500
- * The editing options travel together, and the type states it: without
501
- * `editMode` none of them has anything to act on, so passing one is a
502
- * compile error rather than a dead option; with it, `getRowId` becomes
503
- * required; and `onEditCommitBatch` exists only under `"batch"` - the one
504
- * mode whose `submitAll` calls it.
505
- *
506
- * `editMode` itself turns cell editing on and picks how commits happen. Off
507
- * by default.
492
+ * The `editing` option: one object that turns editing on and holds
493
+ * everything about it. `mode` picks what counts as a commit and which
494
+ * controls trigger it; the other members act within that mode.
508
495
  *
509
496
  * | Mode | Commit | Cancel |
510
497
  * | ---- | ------ | ------ |
511
498
  * | `"cell"` | Enter, Tab, blur - Sheets | Escape |
512
499
  * | `"cellConfirm"` | ✓ or Enter only; blur keeps the draft | ✕ or Escape |
513
500
  * | `"row"` | Save in the edit lane, or Ctrl+Enter | Cancel, or Escape |
514
- * | `"batch"` | `edit.submitAll()` | `edit.cancelAll()` |
501
+ * | `"draft"` | `edit.submitAll()` | `edit.cancelAll()` |
502
+ *
503
+ * Setting `editing` makes `getRowId` required - drafts are keyed by row id,
504
+ * and the index fallback would name a different record after any sort - and
505
+ * `onCommitDrafts` exists only under `mode: "draft"`, the one mode whose
506
+ * `submitAll` calls it.
507
+ *
508
+ * The object may be written inline: the callbacks are read through a ref
509
+ * every render, so its identity does not matter.
515
510
  *
516
511
  * One TanStack Form per editing row; drafts survive scrolling because the
517
512
  * forms live outside the DOM, keyed by row id. Which columns edit, and with
@@ -520,39 +515,56 @@ type TMDataGridEditingCallbacks<TData extends RowData> = {
520
515
  * override the rest.
521
516
  */
522
517
  export type TMDataGridEditingOptions<TData extends RowData> =
523
- | (TMDataGridEditingCallbacks<TData> & {
524
- editMode: "batch";
525
- /**
526
- * Batch mode's save, called once by `edit.submitAll()` with every
527
- * valid dirty row. Without it, `submitAll` falls back to the per-row
528
- * {@link TMDataGridEditingCallbacks.onEditCommit} loop. Rows failing
529
- * validation stay open either way; a rejection keeps every draft.
530
- */
531
- onEditCommitBatch?: (
532
- args: TMDataGridEditCommitBatchArgs<TData>,
533
- ) => void | Promise<void>;
534
- })
535
- | (TMDataGridEditingCallbacks<TData> & {
536
- editMode: Exclude<TMDataGridEditMode, "batch">;
537
- /** Only `"batch"`'s `submitAll` ever calls it - see the other branch. */
538
- onEditCommitBatch?: never;
539
- })
540
- | {
541
- editMode?: never;
542
- rowValidators?: never;
543
- isRowEditable?: never;
544
- onEditCommit?: never;
545
- onEditCommitBatch?: never;
546
- newRowDefaults?: never;
547
- onRowAdd?: never;
548
- onRowDelete?: never;
549
- };
518
+ TMDataGridEditingCallbacks<TData> &
519
+ (
520
+ | {
521
+ mode: "draft";
522
+ /**
523
+ * Draft mode's save, called once by `edit.submitAll()` with every
524
+ * valid dirty row. Without it, `submitAll` falls back to the per-row
525
+ * {@link TMDataGridEditingCallbacks.onCommit} loop. Rows failing
526
+ * validation stay open either way; a rejection keeps every draft.
527
+ */
528
+ onCommitDrafts?: (
529
+ args: TMDataGridEditCommitDraftsArgs<TData>,
530
+ ) => void | Promise<void>;
531
+ /**
532
+ * Keep confirmed entry rows pinned in the sticky entry block until
533
+ * Save all. Off by default: a confirmed row joins the scrolling
534
+ * flow above the body rows instead - the block a row is *typed*
535
+ * into is always sticky, but entered rows scroll, so entering many
536
+ * cannot fill the viewport with sticky chrome.
537
+ */
538
+ newRowsSticky?: boolean;
539
+ }
540
+ | {
541
+ mode: Exclude<TMDataGridEditMode, "draft">;
542
+ /** Only `"draft"`'s `submitAll` ever calls it - see the other branch. */
543
+ onCommitDrafts?: never;
544
+ /** Confirmed entry rows exist only under `"draft"` - see there. */
545
+ newRowsSticky?: never;
546
+ }
547
+ );
550
548
 
551
549
  export type UseTMDataGridOptions<TData extends RowData> = Omit<
552
550
  TableOptions<TMDataGridFeatures, TData>,
553
551
  "features"
554
552
  > &
555
- TMDataGridEditingOptions<TData> & {
553
+ (
554
+ | { editing?: undefined }
555
+ | {
556
+ /** Turns editing on. See {@link TMDataGridEditingOptions}. */
557
+ editing: TMDataGridEditingOptions<TData>;
558
+ /**
559
+ * Required once `editing` is set: the forms are keyed by row id and
560
+ * live outside the DOM, and the index fallback points at a different
561
+ * record after any sort.
562
+ */
563
+ getRowId: NonNullable<
564
+ TableOptions<TMDataGridFeatures, TData>["getRowId"]
565
+ >;
566
+ }
567
+ ) & {
556
568
  /**
557
569
  * Restore and persist table state across mounts. Two keys, because the two
558
570
  * kinds of state have different lifetimes - see {@link TMDataGridPersistence}.
@@ -772,9 +784,9 @@ export type UseTMDataGridOptions<TData extends RowData> = Omit<
772
784
  * Defaults to 160.
773
785
  *
774
786
  * An estimate, not a height: every mounted row is measured, so the real one
775
- * takes over as soon as the panel is on screen. It keeps the scrollbar honest
776
- * for panels that open off screen (restored `expanded` state, say), and being
777
- * roughly right is enough.
787
+ * takes over as soon as the panel is on screen. It keeps the scrollbar
788
+ * accurate for panels that open off screen, such as restored `expanded`
789
+ * state. An approximate value is enough.
778
790
  */
779
791
  renderDetailsEstHeight?: number;
780
792
  /**
@@ -807,6 +819,26 @@ type TMDataGridColumnDef<TData extends RowData> = ColumnDef<
807
819
  * request for one. Cleared here, so a grouped grid is a tree until a column
808
820
  * says otherwise - `aggregationFn: "sum"` on the column that wants it.
809
821
  */
822
+ /**
823
+ * Drops the control lanes' entries from a visibility map.
824
+ *
825
+ * They are all `enableHiding: false`, but TanStack applies `columnVisibility`
826
+ * regardless - the option only gates `toggleVisibility` - so a stale `false`
827
+ * would hide a lane nothing in the grid can bring back: restored from storage
828
+ * written before 2.1 (when the checkbox lane was still hideable), or passed in
829
+ * `initialState`. The tree column is not scrubbed; its entry is the grid's own,
830
+ * written after this.
831
+ */
832
+ function withoutControlColumnVisibility(
833
+ visibility: Record<string, boolean>,
834
+ ): Record<string, boolean> {
835
+ const scrubbed = { ...visibility };
836
+ for (const id of Object.keys(scrubbed)) {
837
+ if (isControlColumn(id)) delete scrubbed[id];
838
+ }
839
+ return scrubbed;
840
+ }
841
+
810
842
  function withTMDataGridDefaults<TData extends RowData>(
811
843
  columns: ReadonlyArray<TMDataGridColumnDef<TData>>,
812
844
  ): Array<TMDataGridColumnDef<TData>> {
@@ -850,19 +882,16 @@ export function useTMDataGrid<TData extends RowData>({
850
882
  onHighlightedRowChange,
851
883
  cellSelection,
852
884
  onFocusedCellChange,
853
- editMode,
854
- rowValidators,
855
- isRowEditable,
856
- onEditCommit,
857
- onEditCommitBatch,
858
- newRowDefaults,
859
- onRowAdd,
860
- onRowDelete,
885
+ editing,
861
886
  renderDetails,
862
887
  renderDetailsEstHeight = DEFAULT_DETAILS_EST_HEIGHT,
863
888
  overscan = DEFAULT_OVERSCAN,
864
889
  ...options
865
890
  }: UseTMDataGridOptions<TData>): TMDataGridApi<TData> {
891
+ // The one place `editing` is unpacked - the engine and the flags keep their
892
+ // own vocabulary (`editMode`, `onEditCommit`), so the mapping lives here.
893
+ const editMode = editing?.mode;
894
+
866
895
  // Derived up here, rather than just before the return, because the rest of the
867
896
  // hook needs `selectColumn` - one place decides what each mode means.
868
897
  //
@@ -877,7 +906,7 @@ export function useTMDataGrid<TData extends RowData>({
877
906
  selectionMode,
878
907
  showSelectedBackground,
879
908
  cellSelection,
880
- editMode,
909
+ editing,
881
910
  });
882
911
 
883
912
  // Resolved on the override's identity, so a module-scope dictionary costs one
@@ -891,12 +920,15 @@ export function useTMDataGrid<TData extends RowData>({
891
920
  // `renderDetails` has nothing for it to open.
892
921
  const detailsColumnEnabled = renderDetails !== undefined;
893
922
  // Row mode's Save sits at the end of the row - the lane is its chrome. It
894
- // also appears wherever the trash can has somewhere to report to.
923
+ // also appears wherever the trash can has somewhere to report to, and
924
+ // always under draft mode, where it is the change marker and the per-row
925
+ // revert - with or without `onCommitDrafts`, since the per-row `submitAll`
926
+ // fallback is a first-class configuration.
895
927
  const editColumnEnabled =
896
- editMode !== undefined &&
897
- (editMode === "row" ||
898
- onRowDelete !== undefined ||
899
- (editMode === "batch" && onEditCommitBatch !== undefined));
928
+ editing !== undefined &&
929
+ (editing.mode === "row" ||
930
+ editing.mode === "draft" ||
931
+ editing.onRowDelete !== undefined);
900
932
 
901
933
  // The generated lanes bake `meta.label` into their definitions, so the memo
902
934
  // depends on the strings rather than on the labels object - a fresh
@@ -907,6 +939,9 @@ export function useTMDataGrid<TData extends RowData>({
907
939
  const editColumnLabel = labels.editColumnLabel;
908
940
  const rowNumberColumnLabel = labels.rowNumberColumnLabel;
909
941
  const rowNumbersEnabled = features.rowNumbers;
942
+ // Draft mode's lane holds three controls (state icon, undo, trash) where
943
+ // the other modes hold two - it gets the wider track.
944
+ const editIsDraftMode = editing?.mode === "draft";
910
945
 
911
946
  const columns = useMemo(() => {
912
947
  const base = withTMDataGridDefaults<TData>(
@@ -936,7 +971,9 @@ export function useTMDataGrid<TData extends RowData>({
936
971
  : []),
937
972
  ...base,
938
973
  // Last and pinned right - the row's Save belongs at the end of the row.
939
- ...(editColumnEnabled ? [createEditColumn<TData>(editColumnLabel)] : []),
974
+ ...(editColumnEnabled
975
+ ? [createEditColumn<TData>(editColumnLabel, editIsDraftMode)]
976
+ : []),
940
977
  ];
941
978
  }, [
942
979
  options.columns,
@@ -945,6 +982,7 @@ export function useTMDataGrid<TData extends RowData>({
945
982
  detailsColumnEnabled,
946
983
  groupColumnEnabled,
947
984
  editColumnEnabled,
985
+ editIsDraftMode,
948
986
  rowNumberColumnLabel,
949
987
  selectColumnLabel,
950
988
  groupColumnLabel,
@@ -1008,8 +1046,10 @@ export function useTMDataGrid<TData extends RowData>({
1008
1046
  ...options.initialState,
1009
1047
  ...persistedState,
1010
1048
  columnVisibility: {
1011
- ...options.initialState?.columnVisibility,
1012
- ...persistedState.columnVisibility,
1049
+ ...withoutControlColumnVisibility({
1050
+ ...options.initialState?.columnVisibility,
1051
+ ...persistedState.columnVisibility,
1052
+ }),
1013
1053
  // Last word, because the tree column's visibility is not a user setting
1014
1054
  // - it tracks the grouping state. See the effect below.
1015
1055
  ...(groupColumnEnabled
@@ -1064,24 +1104,26 @@ export function useTMDataGrid<TData extends RowData>({
1064
1104
  editContextRef.current = {
1065
1105
  table: table as unknown as TMDataGridTable<TMDataGridRowData>,
1066
1106
  editMode: editMode ?? "cell",
1067
- rowValidators,
1068
- isRowEditable: isRowEditable as TMDataGridEditEngineContext["isRowEditable"],
1107
+ rowValidators: editing?.rowValidators,
1108
+ isRowEditable:
1109
+ editing?.isRowEditable as TMDataGridEditEngineContext["isRowEditable"],
1069
1110
  onEditCommit:
1070
- onEditCommit as TMDataGridEditEngineContext["onEditCommit"],
1071
- onEditCommitBatch:
1072
- onEditCommitBatch as TMDataGridEditEngineContext["onEditCommitBatch"],
1111
+ editing?.onCommit as TMDataGridEditEngineContext["onEditCommit"],
1112
+ onEditCommitDrafts:
1113
+ editing?.onCommitDrafts as TMDataGridEditEngineContext["onEditCommitDrafts"],
1073
1114
  newRowDefaults:
1074
- newRowDefaults as TMDataGridEditEngineContext["newRowDefaults"],
1075
- onRowAdd: onRowAdd as TMDataGridEditEngineContext["onRowAdd"],
1076
- onRowDelete: onRowDelete as TMDataGridEditEngineContext["onRowDelete"],
1115
+ editing?.newRowDefaults as TMDataGridEditEngineContext["newRowDefaults"],
1116
+ onRowAdd: editing?.onRowAdd as TMDataGridEditEngineContext["onRowAdd"],
1117
+ onRowDelete:
1118
+ editing?.onRowDelete as TMDataGridEditEngineContext["onRowDelete"],
1077
1119
  };
1078
1120
  const [edit] = useState(() =>
1079
1121
  createEditEngine(() => editContextRef.current),
1080
1122
  );
1081
1123
 
1082
1124
  // Switching modes mid-flight drops every draft: the policies disagree about
1083
- // what an open form means, and carrying one across is how a "batch" draft
1084
- // silently commits under "cell".
1125
+ // what an open form means, and carrying one across is how a draft parked
1126
+ // under "draft" silently commits under "cell".
1085
1127
  const previousEditModeRef = useRef(editMode);
1086
1128
  useEffect(() => {
1087
1129
  if (previousEditModeRef.current === editMode) return;
@@ -1092,9 +1134,9 @@ export function useTMDataGrid<TData extends RowData>({
1092
1134
  // Editing without stable ids points every draft at whatever record slides
1093
1135
  // into that index after a sort. Loud, once, in development.
1094
1136
  useEffect(() => {
1095
- if (editMode !== undefined && options.getRowId === undefined) {
1137
+ if (editing !== undefined && options.getRowId === undefined) {
1096
1138
  console.error(
1097
- "TMDataGrid: editMode requires getRowId - drafts are keyed by row id, and the index fallback names a different record after any sort or filter.",
1139
+ "TMDataGrid: editing requires getRowId - drafts are keyed by row id, and the index fallback names a different record after any sort or filter.",
1098
1140
  );
1099
1141
  }
1100
1142
  // The check is a mount-time contract, not something to re-run per render.
@@ -1240,9 +1282,9 @@ export function useTMDataGrid<TData extends RowData>({
1240
1282
  ),
1241
1283
  setCellRange: (range) =>
1242
1284
  setState((prev) =>
1243
- // Same guard as above, and it earns more here: a drag publishes a new
1244
- // corner on every mouse move, and most of those land on the cell the
1245
- // range already ends at.
1285
+ // Same guard as above, and it matters more here: a drag publishes a
1286
+ // new corner on every mouse move, and most of those land on the cell
1287
+ // the range already ends at.
1246
1288
  isSameCell(prev.cellRange?.anchor ?? null, range?.anchor ?? null) &&
1247
1289
  isSameCell(prev.cellRange?.focus ?? null, range?.focus ?? null)
1248
1290
  ? prev
@@ -1302,7 +1344,7 @@ export function useTMDataGrid<TData extends RowData>({
1302
1344
  const grouping = [...(initial?.grouping ?? [])];
1303
1345
  table.setGrouping(grouping);
1304
1346
  table.setColumnVisibility({
1305
- ...initial?.columnVisibility,
1347
+ ...withoutControlColumnVisibility({ ...initial?.columnVisibility }),
1306
1348
  // The tree lane's visibility tracks the grouping, never the user.
1307
1349
  ...(groupColumnEnabled ? { [GROUP_COLUMN_ID]: grouping.length > 0 } : {}),
1308
1350
  });
@@ -1333,8 +1375,8 @@ export function useTMDataGrid<TData extends RowData>({
1333
1375
  const resetSettings = useCallback(() => resetSettingsRef.current(), []);
1334
1376
 
1335
1377
  // Filled in by `TMDataGrid.Table` on every render, because only the body
1336
- // holds the virtualizer and the current view. Answers `false` until one is
1337
- // mounted, which is the honest answer: there is nothing to scroll yet.
1378
+ // holds the virtualizer and the current view. Returns `false` until one is
1379
+ // mounted, since there is nothing to scroll before then.
1338
1380
  const scrollerRef = useRef<TMDataGridScroller>(() => false);
1339
1381
  const scrollToRow = useCallback(
1340
1382
  (args: TMDataGridScrollToRowArgs) => scrollerRef.current(args),