@jielga/tmdatagrid 2.0.0-beta.1 → 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.
- package/dist/index.d.ts +60 -23
- package/dist/index.js +2020 -1704
- package/dist/index.js.map +1 -1
- package/dist/styles.css +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 +2 -2
- package/skills/data/SKILL.md +1 -1
- package/skills/editing/SKILL.md +68 -213
- package/skills/editing/references/common-mistakes.md +177 -0
- package/skills/editing/references/editing-api.md +39 -13
- package/skills/editing/references/editors-and-validation.md +5 -2
- 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 +2 -2
- package/skills/rows/SKILL.md +1 -1
- package/skills/rows/references/rows-api.md +1 -1
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +3 -2
- package/src/tmdatagrid/components/TMDataGrid.module.css +7 -0
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +20 -8
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +1 -1
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +242 -86
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +176 -77
- package/src/tmdatagrid/components/TMDataGridTable.module.css +47 -3
- package/src/tmdatagrid/components/TMDataGridTable.tsx +44 -4
- package/src/tmdatagrid/components/icons.ts +1 -0
- package/src/tmdatagrid/core/capabilities.ts +10 -0
- package/src/tmdatagrid/core/draftCellContext.ts +68 -0
- package/src/tmdatagrid/core/editEngine.ts +106 -32
- package/src/tmdatagrid/core/labels.ts +13 -1
- package/src/tmdatagrid/core/labelsSv.ts +4 -0
- package/src/tmdatagrid/index.ts +1 -1
- package/src/tmdatagrid/useTMDataGrid.tsx +39 -20
|
@@ -22,7 +22,7 @@ import type { TMDataGridSize } from "./sizes";
|
|
|
22
22
|
* How commits happen - one axis, each mode a thin policy over the same
|
|
23
23
|
* engine. See `editMode` on `UseTMDataGridOptions`.
|
|
24
24
|
*/
|
|
25
|
-
export type TMDataGridEditMode = "cell" | "cellConfirm" | "row" | "
|
|
25
|
+
export type TMDataGridEditMode = "cell" | "cellConfirm" | "row" | "draft";
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* One editing row's live form. TanStack Form's `FormApi`, not a wrapper: the
|
|
@@ -113,6 +113,12 @@ export type TMDataGridEditRowProjection = {
|
|
|
113
113
|
/** A row-level error - a pathless `.refine()`, or a rejected commit. */
|
|
114
114
|
hasRowError: boolean;
|
|
115
115
|
isSubmitting: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* The row as drafted - the form's `state.values`. Reference-stable while no
|
|
118
|
+
* value changes, so a cell may subscribe to it and repaint only when its
|
|
119
|
+
* row's draft actually moves.
|
|
120
|
+
*/
|
|
121
|
+
values: TMDataGridRowData;
|
|
116
122
|
};
|
|
117
123
|
|
|
118
124
|
/**
|
|
@@ -130,13 +136,18 @@ export type TMDataGridEditState = {
|
|
|
130
136
|
active: { rowId: string; columnId: string | null } | null;
|
|
131
137
|
/**
|
|
132
138
|
* Rows with a live form. In cell mode at most one; in row, cellConfirm and
|
|
133
|
-
*
|
|
139
|
+
* draft, as many as the user opened - which rows are editing.
|
|
134
140
|
*/
|
|
135
141
|
openRowIds: ReadonlyArray<string>;
|
|
136
142
|
rows: Record<string, TMDataGridEditRowProjection>;
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Rows being created, not yet in `data`. `confirmed` is draft mode's
|
|
145
|
+
* "entered, awaiting Save all": the entry row renders as a value row until
|
|
146
|
+
* `begin` re-opens it. Under the immediate modes a confirm commits through
|
|
147
|
+
* `onRowAdd` and the entry is dropped, so there it never turns `true`.
|
|
148
|
+
*/
|
|
149
|
+
newRows: ReadonlyArray<{ tempId: string; confirmed: boolean }>;
|
|
150
|
+
/** Rows marked deleted under draft mode. */
|
|
140
151
|
deletedRowIds: ReadonlyArray<string>;
|
|
141
152
|
};
|
|
142
153
|
|
|
@@ -278,13 +289,13 @@ export type TMDataGridRowDeleteArgs<TData extends RowData> = {
|
|
|
278
289
|
row: Row<TMDataGridFeatures, TData>;
|
|
279
290
|
};
|
|
280
291
|
|
|
281
|
-
/** What `submitAll` hands `
|
|
282
|
-
export type
|
|
292
|
+
/** What `submitAll` hands `onEditCommitDrafts` - everything pending at once. */
|
|
293
|
+
export type TMDataGridEditCommitDraftsArgs<TData extends RowData> = {
|
|
283
294
|
/** Every valid dirty existing row. */
|
|
284
295
|
rows: Array<TMDataGridEditCommitArgs<TData>>;
|
|
285
296
|
/** Every valid new row from the entry block. */
|
|
286
297
|
added: Array<TMDataGridRowAddArgs<TData>>;
|
|
287
|
-
/** Ids marked deleted while the
|
|
298
|
+
/** Ids marked deleted while the drafts accumulated. */
|
|
288
299
|
deleted: Array<string>;
|
|
289
300
|
};
|
|
290
301
|
|
|
@@ -297,8 +308,8 @@ export type TMDataGridEditEngineContext = {
|
|
|
297
308
|
onEditCommit?: (
|
|
298
309
|
args: TMDataGridEditCommitArgs<TMDataGridRowData>,
|
|
299
310
|
) => void | Promise<void>;
|
|
300
|
-
|
|
301
|
-
args:
|
|
311
|
+
onEditCommitDrafts?: (
|
|
312
|
+
args: TMDataGridEditCommitDraftsArgs<TMDataGridRowData>,
|
|
302
313
|
) => void | Promise<void>;
|
|
303
314
|
/** Seed values for `addRow`. A function is called per added row. */
|
|
304
315
|
newRowDefaults?: TMDataGridRowData | (() => TMDataGridRowData);
|
|
@@ -387,7 +398,7 @@ export type TMDataGridEditApi = {
|
|
|
387
398
|
deactivate: () => void;
|
|
388
399
|
/** Drops every draft. */
|
|
389
400
|
cancelAll: () => void;
|
|
390
|
-
/** Commits every open row -
|
|
401
|
+
/** Commits every open row - draft mode's save. `true` when all landed. */
|
|
391
402
|
submitAll: () => Promise<boolean>;
|
|
392
403
|
/** Writes the type's empty value into a cell and commits it - Delete. */
|
|
393
404
|
clearCell: (rowId: string, columnId: string) => Promise<boolean>;
|
|
@@ -395,12 +406,12 @@ export type TMDataGridEditApi = {
|
|
|
395
406
|
* Opens a new entry row (the sticky block under the header) seeded from
|
|
396
407
|
* `newRowDefaults`. Returns its temporary id - a form with no backing row
|
|
397
408
|
* yet. Committing it calls `onRowAdd` (immediate modes) or joins
|
|
398
|
-
* `submitAll`'s `added` (
|
|
409
|
+
* `submitAll`'s `added` (draft).
|
|
399
410
|
*/
|
|
400
411
|
addRow: () => string;
|
|
401
412
|
/**
|
|
402
413
|
* Deletes a row: `onRowDelete` straight away under the immediate modes;
|
|
403
|
-
* under
|
|
414
|
+
* under draft it toggles the id in `deletedRowIds` - the row renders
|
|
404
415
|
* struck through until `submitAll` reports it. On an uncommitted entry
|
|
405
416
|
* row it just discards the entry.
|
|
406
417
|
*/
|
|
@@ -467,6 +478,13 @@ export function createEditEngine(
|
|
|
467
478
|
isNew: boolean;
|
|
468
479
|
/** Set by the wrapped onSubmit when the consumer's commit resolved. */
|
|
469
480
|
lastSubmitOk: boolean;
|
|
481
|
+
/**
|
|
482
|
+
* Draft mode's park: this submit validates and holds the draft in the
|
|
483
|
+
* grid instead of calling the consumer. Set by `commit` per attempt -
|
|
484
|
+
* `true` only under draft outside `submitAll`, which is what closes the
|
|
485
|
+
* per-row escape hatches (the lane's ✓, Delete-to-clear) at the engine.
|
|
486
|
+
*/
|
|
487
|
+
parkOnly: boolean;
|
|
470
488
|
/** A commit already running - Enter and blur race on the same edit. */
|
|
471
489
|
pendingCommit: Promise<boolean> | null;
|
|
472
490
|
unsubscribe: () => void;
|
|
@@ -474,15 +492,17 @@ export function createEditEngine(
|
|
|
474
492
|
};
|
|
475
493
|
const forms = new Map<string, FormEntry>();
|
|
476
494
|
let newRowCounter = 0;
|
|
495
|
+
/** Lets `commit` tell `submitAll`'s per-row loop apart from a lone call. */
|
|
496
|
+
let submitAllRunning = false;
|
|
477
497
|
|
|
478
498
|
/**
|
|
479
|
-
* While `submitAll` runs with an `
|
|
499
|
+
* While `submitAll` runs with an `onEditCommitDrafts`, each row's wrapped
|
|
480
500
|
* onSubmit contributes its args here instead of calling `onEditCommit` -
|
|
481
501
|
* validation stays per row (Form's), the consumer call becomes one.
|
|
482
502
|
*/
|
|
483
|
-
let
|
|
503
|
+
let draftCollector: Array<TMDataGridEditCommitArgs<TMDataGridRowData>> | null =
|
|
484
504
|
null;
|
|
485
|
-
let
|
|
505
|
+
let draftAddCollector: Array<TMDataGridRowAddArgs<TMDataGridRowData>> | null =
|
|
486
506
|
null;
|
|
487
507
|
|
|
488
508
|
const editableColumns = (): Array<ErasedColumn> =>
|
|
@@ -530,6 +550,7 @@ export function createEditEngine(
|
|
|
530
550
|
errorFields,
|
|
531
551
|
hasRowError: hasAnyError(state.errors),
|
|
532
552
|
isSubmitting: state.isSubmitting,
|
|
553
|
+
values: state.values as TMDataGridRowData,
|
|
533
554
|
};
|
|
534
555
|
};
|
|
535
556
|
|
|
@@ -547,6 +568,19 @@ export function createEditEngine(
|
|
|
547
568
|
store.setState((prev) => ({ ...prev, active }));
|
|
548
569
|
};
|
|
549
570
|
|
|
571
|
+
const setNewRowConfirmed = (tempId: string, confirmed: boolean) => {
|
|
572
|
+
store.setState((prev) => {
|
|
573
|
+
const target = prev.newRows.find((newRow) => newRow.tempId === tempId);
|
|
574
|
+
if (target === undefined || target.confirmed === confirmed) return prev;
|
|
575
|
+
return {
|
|
576
|
+
...prev,
|
|
577
|
+
newRows: prev.newRows.map((newRow) =>
|
|
578
|
+
newRow.tempId === tempId ? { ...newRow, confirmed } : newRow,
|
|
579
|
+
),
|
|
580
|
+
};
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
|
|
550
584
|
const drop = (rowId: string) => {
|
|
551
585
|
const entry = forms.get(rowId);
|
|
552
586
|
if (entry === undefined) return;
|
|
@@ -578,6 +612,7 @@ export function createEditEngine(
|
|
|
578
612
|
original,
|
|
579
613
|
isNew,
|
|
580
614
|
lastSubmitOk: false,
|
|
615
|
+
parkOnly: false,
|
|
581
616
|
pendingCommit: null,
|
|
582
617
|
unsubscribe: () => {},
|
|
583
618
|
unmount: () => {},
|
|
@@ -594,8 +629,13 @@ export function createEditEngine(
|
|
|
594
629
|
tempId: rowId,
|
|
595
630
|
value: value as TMDataGridRowData,
|
|
596
631
|
};
|
|
597
|
-
if (
|
|
598
|
-
|
|
632
|
+
if (draftAddCollector !== null) {
|
|
633
|
+
draftAddCollector.push(addArgs);
|
|
634
|
+
entry.lastSubmitOk = true;
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
// Parked: validated and held for Save all - no consumer call.
|
|
638
|
+
if (entry.parkOnly) {
|
|
599
639
|
entry.lastSubmitOk = true;
|
|
600
640
|
return;
|
|
601
641
|
}
|
|
@@ -618,8 +658,13 @@ export function createEditEngine(
|
|
|
618
658
|
changes,
|
|
619
659
|
source: getContext().editMode,
|
|
620
660
|
};
|
|
621
|
-
if (
|
|
622
|
-
|
|
661
|
+
if (draftCollector !== null) {
|
|
662
|
+
draftCollector.push(args);
|
|
663
|
+
entry.lastSubmitOk = true;
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
// Parked: validated and held for Save all - no consumer call.
|
|
667
|
+
if (entry.parkOnly) {
|
|
623
668
|
entry.lastSubmitOk = true;
|
|
624
669
|
return;
|
|
625
670
|
}
|
|
@@ -691,6 +736,7 @@ export function createEditEngine(
|
|
|
691
736
|
// block `canSubmit` forever. The validator puts back whatever still holds.
|
|
692
737
|
clearFormSourcedFieldErrors(entry.form);
|
|
693
738
|
entry.lastSubmitOk = false;
|
|
739
|
+
entry.parkOnly = getContext().editMode === "draft" && !submitAllRunning;
|
|
694
740
|
entry.pendingCommit = (async () => {
|
|
695
741
|
try {
|
|
696
742
|
await entry.form.handleSubmit();
|
|
@@ -698,6 +744,15 @@ export function createEditEngine(
|
|
|
698
744
|
entry.pendingCommit = null;
|
|
699
745
|
}
|
|
700
746
|
if (!entry.lastSubmitOk) return false;
|
|
747
|
+
if (entry.parkOnly) {
|
|
748
|
+
// The draft stays in the grid until Save all. An entry row becomes
|
|
749
|
+
// "entered": rendered as a value row until `begin` re-opens it.
|
|
750
|
+
if (entry.isNew) setNewRowConfirmed(rowId, true);
|
|
751
|
+
store.setState((prev) =>
|
|
752
|
+
prev.active?.rowId === rowId ? { ...prev, active: null } : prev,
|
|
753
|
+
);
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
701
756
|
drop(rowId);
|
|
702
757
|
return true;
|
|
703
758
|
})();
|
|
@@ -705,6 +760,14 @@ export function createEditEngine(
|
|
|
705
760
|
};
|
|
706
761
|
|
|
707
762
|
const beginOn = (rowId: string, columnId: string | null) => {
|
|
763
|
+
// An entry row has no backing row in the table; opening one re-arms its
|
|
764
|
+
// editors - under draft, that is how a confirmed row is edited again.
|
|
765
|
+
const entryForm = forms.get(rowId);
|
|
766
|
+
if (entryForm?.isNew === true) {
|
|
767
|
+
setNewRowConfirmed(rowId, false);
|
|
768
|
+
setActive({ rowId, columnId });
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
708
771
|
const row = getRow(rowId);
|
|
709
772
|
if (row === undefined) return;
|
|
710
773
|
if (columnId !== null) {
|
|
@@ -724,7 +787,7 @@ export function createEditEngine(
|
|
|
724
787
|
// | Mode | Another row open |
|
|
725
788
|
// | ---- | ---------------- |
|
|
726
789
|
// | cell | committed - Sheets; a failed commit keeps holding the edit |
|
|
727
|
-
// | row, cellConfirm,
|
|
790
|
+
// | row, cellConfirm, draft | accumulates. Every draft waits for its own
|
|
728
791
|
// save, so a second row opening can neither discard the first nor be
|
|
729
792
|
// refused by it - nothing about one row's form bears on another's |
|
|
730
793
|
if (editMode === "cell") {
|
|
@@ -763,7 +826,7 @@ export function createEditEngine(
|
|
|
763
826
|
createForm(tempId, defaults, true);
|
|
764
827
|
store.setState((prev) => ({
|
|
765
828
|
...prev,
|
|
766
|
-
newRows: [...prev.newRows, { tempId }],
|
|
829
|
+
newRows: [...prev.newRows, { tempId, confirmed: false }],
|
|
767
830
|
}));
|
|
768
831
|
return tempId;
|
|
769
832
|
};
|
|
@@ -776,7 +839,7 @@ export function createEditEngine(
|
|
|
776
839
|
return;
|
|
777
840
|
}
|
|
778
841
|
const context = getContext();
|
|
779
|
-
if (context.editMode === "
|
|
842
|
+
if (context.editMode === "draft") {
|
|
780
843
|
// A toggle: the second press unmarks - the mark is a draft too.
|
|
781
844
|
store.setState((prev) => ({
|
|
782
845
|
...prev,
|
|
@@ -794,10 +857,10 @@ export function createEditEngine(
|
|
|
794
857
|
|
|
795
858
|
const canDeleteRows = (): boolean => {
|
|
796
859
|
const context = getContext();
|
|
797
|
-
if (context.editMode === "
|
|
860
|
+
if (context.editMode === "draft") {
|
|
798
861
|
return (
|
|
799
862
|
context.onRowDelete !== undefined ||
|
|
800
|
-
context.
|
|
863
|
+
context.onEditCommitDrafts !== undefined
|
|
801
864
|
);
|
|
802
865
|
}
|
|
803
866
|
return context.onRowDelete !== undefined;
|
|
@@ -813,7 +876,18 @@ export function createEditEngine(
|
|
|
813
876
|
};
|
|
814
877
|
|
|
815
878
|
const submitAll = async (): Promise<boolean> => {
|
|
816
|
-
|
|
879
|
+
// While this runs, `commit` really commits - under draft it parks
|
|
880
|
+
// otherwise. Single-threaded flag, same idiom as the collectors.
|
|
881
|
+
submitAllRunning = true;
|
|
882
|
+
try {
|
|
883
|
+
return await submitAllInner();
|
|
884
|
+
} finally {
|
|
885
|
+
submitAllRunning = false;
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
const submitAllInner = async (): Promise<boolean> => {
|
|
890
|
+
if (getContext().onEditCommitDrafts === undefined) {
|
|
817
891
|
// The default: the per-row loop - edits through `onEditCommit`, entry
|
|
818
892
|
// rows through `onRowAdd`, marked deletions through `onRowDelete`.
|
|
819
893
|
const results = await Promise.all(
|
|
@@ -830,12 +904,12 @@ export function createEditEngine(
|
|
|
830
904
|
|
|
831
905
|
// One consumer call for the lot. Validation stays Form's, per row: rows
|
|
832
906
|
// that fail keep their forms and markers; the valid ones travel
|
|
833
|
-
// together, and only a resolved
|
|
907
|
+
// together, and only a resolved save drops them - a rejected save keeps
|
|
834
908
|
// every draft, deletions included.
|
|
835
909
|
const collected: Array<TMDataGridEditCommitArgs<TMDataGridRowData>> = [];
|
|
836
910
|
const added: Array<TMDataGridRowAddArgs<TMDataGridRowData>> = [];
|
|
837
|
-
|
|
838
|
-
|
|
911
|
+
draftCollector = collected;
|
|
912
|
+
draftAddCollector = added;
|
|
839
913
|
let allValid = true;
|
|
840
914
|
try {
|
|
841
915
|
for (const rowId of [...forms.keys()]) {
|
|
@@ -853,13 +927,13 @@ export function createEditEngine(
|
|
|
853
927
|
if (!entry.lastSubmitOk) allValid = false;
|
|
854
928
|
}
|
|
855
929
|
} finally {
|
|
856
|
-
|
|
857
|
-
|
|
930
|
+
draftCollector = null;
|
|
931
|
+
draftAddCollector = null;
|
|
858
932
|
}
|
|
859
933
|
const deleted = [...store.state.deletedRowIds];
|
|
860
934
|
if (collected.length > 0 || added.length > 0 || deleted.length > 0) {
|
|
861
935
|
try {
|
|
862
|
-
await getContext().
|
|
936
|
+
await getContext().onEditCommitDrafts?.({ rows: collected, added, deleted });
|
|
863
937
|
} catch {
|
|
864
938
|
return false;
|
|
865
939
|
}
|
|
@@ -121,8 +121,16 @@ export type TMDataGridLabels = {
|
|
|
121
121
|
discardNewRow: string;
|
|
122
122
|
/** The lane's trash can. */
|
|
123
123
|
deleteRow: string;
|
|
124
|
-
/** The lane's undo on a row marked deleted (
|
|
124
|
+
/** The lane's undo on a row marked deleted (draft). */
|
|
125
125
|
restoreRow: string;
|
|
126
|
+
/** The lane's undo on a row with a dirty draft (draft) - drops it. */
|
|
127
|
+
revertRow: string;
|
|
128
|
+
/** The lane's state icon on a confirmed entry row (draft). */
|
|
129
|
+
rowStateNew: string;
|
|
130
|
+
/** The lane's state icon on a row with a dirty draft (draft). */
|
|
131
|
+
rowStateEdited: string;
|
|
132
|
+
/** The lane's state icon on a row marked deleted (draft). */
|
|
133
|
+
rowStateDeleted: string;
|
|
126
134
|
|
|
127
135
|
// Cell selection menu
|
|
128
136
|
cellCount: (count: number) => string;
|
|
@@ -239,6 +247,10 @@ export const TMDATAGRID_LABELS_EN: TMDataGridLabels = {
|
|
|
239
247
|
discardNewRow: "Discard new row",
|
|
240
248
|
deleteRow: "Delete row",
|
|
241
249
|
restoreRow: "Restore row",
|
|
250
|
+
revertRow: "Revert changes",
|
|
251
|
+
rowStateNew: "New row",
|
|
252
|
+
rowStateEdited: "Edited row",
|
|
253
|
+
rowStateDeleted: "Marked for deletion",
|
|
242
254
|
|
|
243
255
|
cellCount: (count) => (count === 1 ? "1 cell" : `${count} cells`),
|
|
244
256
|
copy: "Copy",
|
|
@@ -107,6 +107,10 @@ export const TMDATAGRID_LABELS_SV: TMDataGridLabels = {
|
|
|
107
107
|
discardNewRow: "Släng ny rad",
|
|
108
108
|
deleteRow: "Ta bort rad",
|
|
109
109
|
restoreRow: "Återställ rad",
|
|
110
|
+
revertRow: "Ångra ändringar",
|
|
111
|
+
rowStateNew: "Ny rad",
|
|
112
|
+
rowStateEdited: "Ändrad rad",
|
|
113
|
+
rowStateDeleted: "Markerad för borttagning",
|
|
110
114
|
|
|
111
115
|
cellCount: (count) => (count === 1 ? "1 cell" : `${count} celler`),
|
|
112
116
|
copy: "Kopiera",
|
package/src/tmdatagrid/index.ts
CHANGED
|
@@ -82,7 +82,7 @@ export {
|
|
|
82
82
|
type TMDataGridEditApi,
|
|
83
83
|
type TMDataGridEditChange,
|
|
84
84
|
type TMDataGridEditCommitArgs,
|
|
85
|
-
type
|
|
85
|
+
type TMDataGridEditCommitDraftsArgs,
|
|
86
86
|
type TMDataGridEditField,
|
|
87
87
|
type TMDataGridEditMode,
|
|
88
88
|
type TMDataGridEditorArgs,
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
createEditEngine,
|
|
52
52
|
type TMDataGridEditApi,
|
|
53
53
|
type TMDataGridEditCommitArgs,
|
|
54
|
-
type
|
|
54
|
+
type TMDataGridEditCommitDraftsArgs,
|
|
55
55
|
type TMDataGridEditEngineContext,
|
|
56
56
|
type TMDataGridColumnEditOptions,
|
|
57
57
|
type TMDataGridEditMode,
|
|
@@ -475,13 +475,13 @@ type TMDataGridEditingCallbacks<TData extends RowData> = {
|
|
|
475
475
|
newRowDefaults?: TData | (() => TData);
|
|
476
476
|
/**
|
|
477
477
|
* Called when an entry row commits: `Enter` or the lane's ✓ under the
|
|
478
|
-
* immediate modes, `submitAll` under
|
|
478
|
+
* immediate modes, `submitAll` under draft. Create the record and let it
|
|
479
479
|
* arrive back through `data`; the engine's `tempId` never leaves the grid.
|
|
480
480
|
*/
|
|
481
481
|
onRowAdd?: (args: TMDataGridRowAddArgs<TData>) => void | Promise<void>;
|
|
482
482
|
/**
|
|
483
483
|
* Called by `edit.deleteRow` under the immediate modes - confirmation, if
|
|
484
|
-
* any, belongs in here. Under
|
|
484
|
+
* any, belongs in here. Under draft, deletions accumulate in
|
|
485
485
|
* `edit.state.deletedRowIds` instead and are reported by `submitAll`.
|
|
486
486
|
* Setting this also puts the trash can in the edit lane.
|
|
487
487
|
*/
|
|
@@ -498,11 +498,11 @@ type TMDataGridEditingCallbacks<TData extends RowData> = {
|
|
|
498
498
|
* | `"cell"` | Enter, Tab, blur - Sheets | Escape |
|
|
499
499
|
* | `"cellConfirm"` | ✓ or Enter only; blur keeps the draft | ✕ or Escape |
|
|
500
500
|
* | `"row"` | Save in the edit lane, or Ctrl+Enter | Cancel, or Escape |
|
|
501
|
-
* | `"
|
|
501
|
+
* | `"draft"` | `edit.submitAll()` | `edit.cancelAll()` |
|
|
502
502
|
*
|
|
503
503
|
* Setting `editing` makes `getRowId` required - drafts are keyed by row id,
|
|
504
504
|
* and the index fallback would name a different record after any sort - and
|
|
505
|
-
* `
|
|
505
|
+
* `onCommitDrafts` exists only under `mode: "draft"`, the one mode whose
|
|
506
506
|
* `submitAll` calls it.
|
|
507
507
|
*
|
|
508
508
|
* The object may be written inline: the callbacks are read through a ref
|
|
@@ -518,21 +518,31 @@ export type TMDataGridEditingOptions<TData extends RowData> =
|
|
|
518
518
|
TMDataGridEditingCallbacks<TData> &
|
|
519
519
|
(
|
|
520
520
|
| {
|
|
521
|
-
mode: "
|
|
521
|
+
mode: "draft";
|
|
522
522
|
/**
|
|
523
|
-
*
|
|
523
|
+
* Draft mode's save, called once by `edit.submitAll()` with every
|
|
524
524
|
* valid dirty row. Without it, `submitAll` falls back to the per-row
|
|
525
525
|
* {@link TMDataGridEditingCallbacks.onCommit} loop. Rows failing
|
|
526
526
|
* validation stay open either way; a rejection keeps every draft.
|
|
527
527
|
*/
|
|
528
|
-
|
|
529
|
-
args:
|
|
528
|
+
onCommitDrafts?: (
|
|
529
|
+
args: TMDataGridEditCommitDraftsArgs<TData>,
|
|
530
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;
|
|
531
539
|
}
|
|
532
540
|
| {
|
|
533
|
-
mode: Exclude<TMDataGridEditMode, "
|
|
534
|
-
/** Only `"
|
|
535
|
-
|
|
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;
|
|
536
546
|
}
|
|
537
547
|
);
|
|
538
548
|
|
|
@@ -910,12 +920,15 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
910
920
|
// `renderDetails` has nothing for it to open.
|
|
911
921
|
const detailsColumnEnabled = renderDetails !== undefined;
|
|
912
922
|
// Row mode's Save sits at the end of the row - the lane is its chrome. It
|
|
913
|
-
// 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.
|
|
914
927
|
const editColumnEnabled =
|
|
915
928
|
editing !== undefined &&
|
|
916
929
|
(editing.mode === "row" ||
|
|
917
|
-
editing.
|
|
918
|
-
|
|
930
|
+
editing.mode === "draft" ||
|
|
931
|
+
editing.onRowDelete !== undefined);
|
|
919
932
|
|
|
920
933
|
// The generated lanes bake `meta.label` into their definitions, so the memo
|
|
921
934
|
// depends on the strings rather than on the labels object - a fresh
|
|
@@ -926,6 +939,9 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
926
939
|
const editColumnLabel = labels.editColumnLabel;
|
|
927
940
|
const rowNumberColumnLabel = labels.rowNumberColumnLabel;
|
|
928
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";
|
|
929
945
|
|
|
930
946
|
const columns = useMemo(() => {
|
|
931
947
|
const base = withTMDataGridDefaults<TData>(
|
|
@@ -955,7 +971,9 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
955
971
|
: []),
|
|
956
972
|
...base,
|
|
957
973
|
// Last and pinned right - the row's Save belongs at the end of the row.
|
|
958
|
-
...(editColumnEnabled
|
|
974
|
+
...(editColumnEnabled
|
|
975
|
+
? [createEditColumn<TData>(editColumnLabel, editIsDraftMode)]
|
|
976
|
+
: []),
|
|
959
977
|
];
|
|
960
978
|
}, [
|
|
961
979
|
options.columns,
|
|
@@ -964,6 +982,7 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
964
982
|
detailsColumnEnabled,
|
|
965
983
|
groupColumnEnabled,
|
|
966
984
|
editColumnEnabled,
|
|
985
|
+
editIsDraftMode,
|
|
967
986
|
rowNumberColumnLabel,
|
|
968
987
|
selectColumnLabel,
|
|
969
988
|
groupColumnLabel,
|
|
@@ -1090,8 +1109,8 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1090
1109
|
editing?.isRowEditable as TMDataGridEditEngineContext["isRowEditable"],
|
|
1091
1110
|
onEditCommit:
|
|
1092
1111
|
editing?.onCommit as TMDataGridEditEngineContext["onEditCommit"],
|
|
1093
|
-
|
|
1094
|
-
editing?.
|
|
1112
|
+
onEditCommitDrafts:
|
|
1113
|
+
editing?.onCommitDrafts as TMDataGridEditEngineContext["onEditCommitDrafts"],
|
|
1095
1114
|
newRowDefaults:
|
|
1096
1115
|
editing?.newRowDefaults as TMDataGridEditEngineContext["newRowDefaults"],
|
|
1097
1116
|
onRowAdd: editing?.onRowAdd as TMDataGridEditEngineContext["onRowAdd"],
|
|
@@ -1103,8 +1122,8 @@ export function useTMDataGrid<TData extends RowData>({
|
|
|
1103
1122
|
);
|
|
1104
1123
|
|
|
1105
1124
|
// Switching modes mid-flight drops every draft: the policies disagree about
|
|
1106
|
-
// what an open form means, and carrying one across is how a
|
|
1107
|
-
// 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".
|
|
1108
1127
|
const previousEditModeRef = useRef(editMode);
|
|
1109
1128
|
useEffect(() => {
|
|
1110
1129
|
if (previousEditModeRef.current === editMode) return;
|