@jielga/tmdatagrid 2.0.0-beta.4 → 2.0.0-beta.7
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 +339 -152
- package/dist/index.js +2602 -2450
- 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 +44 -2
- package/skills/data/SKILL.md +14 -10
- package/skills/editing/SKILL.md +140 -68
- package/skills/editing/references/common-mistakes.md +98 -8
- package/skills/editing/references/editing-api.md +40 -30
- package/skills/editing/references/editors-and-validation.md +65 -14
- package/skills/filtering/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +3 -3
- package/skills/grouping/SKILL.md +5 -3
- package/skills/options/SKILL.md +2 -2
- package/skills/rows/SKILL.md +7 -3
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +1 -1
- package/src/tmdatagrid/components/TMDataGrid.tsx +2 -2
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +33 -34
- package/src/tmdatagrid/components/TMDataGridDraftActions.tsx +296 -0
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +40 -33
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +7 -7
- package/src/tmdatagrid/components/TMDataGridTable.tsx +27 -5
- package/src/tmdatagrid/components/TMDataGridToolbar.module.css +4 -0
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +8 -10
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +34 -4
- package/src/tmdatagrid/components/editors/editorShared.ts +1 -29
- package/src/tmdatagrid/core/capabilities.ts +14 -6
- package/src/tmdatagrid/core/editEngine.ts +472 -96
- package/src/tmdatagrid/core/labels.ts +3 -3
- package/src/tmdatagrid/core/summary.ts +10 -2
- package/src/tmdatagrid/index.ts +9 -7
- package/src/tmdatagrid/useTMDataGrid.tsx +107 -47
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +0 -233
|
@@ -364,9 +364,10 @@ function TMDataGridBodyCell({
|
|
|
364
364
|
<span className={classes.cellContent}>
|
|
365
365
|
{(() => {
|
|
366
366
|
// A row with a live form shows its draft, not `data`: the column's
|
|
367
|
-
// own renderer over a context reading the form's values.
|
|
368
|
-
// drafts -
|
|
369
|
-
//
|
|
367
|
+
// own renderer over a context reading the form's values. The
|
|
368
|
+
// drafts that outlive their editor - parked ones, cellConfirm's
|
|
369
|
+
// kept ones - are the point; group cells are excluded because no
|
|
370
|
+
// group row ever has a form.
|
|
370
371
|
const draftContext =
|
|
371
372
|
draftValues !== undefined &&
|
|
372
373
|
contentOverride === undefined &&
|
|
@@ -969,6 +970,21 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
969
970
|
initialRect: { height: 600, width: 1200 },
|
|
970
971
|
});
|
|
971
972
|
|
|
973
|
+
/**
|
|
974
|
+
* A live `size` (or `meta.rowHeight`) change moves what `estimateSize`
|
|
975
|
+
* answers, but the virtualizer's measurement cache does not list that option
|
|
976
|
+
* among its dependencies - by its own docs, changing the estimate means
|
|
977
|
+
* calling `measure()`. Without this the spacers keep the old height until
|
|
978
|
+
* something else invalidates the cache, so the scroll range and the window
|
|
979
|
+
* disagree with the rows actually painted.
|
|
980
|
+
*/
|
|
981
|
+
const prevRowHeightRef = useRef(rowHeight);
|
|
982
|
+
useEffect(() => {
|
|
983
|
+
if (prevRowHeightRef.current === rowHeight) return;
|
|
984
|
+
prevRowHeightRef.current = rowHeight;
|
|
985
|
+
virtualizer.measure();
|
|
986
|
+
}, [rowHeight, virtualizer]);
|
|
987
|
+
|
|
972
988
|
const virtualItems = virtualizer.getVirtualItems();
|
|
973
989
|
const paddingTop = virtualItems[0]?.start ?? 0;
|
|
974
990
|
const paddingBottom =
|
|
@@ -1539,7 +1555,7 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
1539
1555
|
close: TMDataGridCellEditorClose,
|
|
1540
1556
|
) => {
|
|
1541
1557
|
// Where the keyboard goes: Enter moves down, Tab to the next editable
|
|
1542
|
-
// cell (
|
|
1558
|
+
// cell (cellConfirm's deferring variants move the same way, draft kept) -
|
|
1543
1559
|
// everything else, and a saved row, goes back to where the edit was.
|
|
1544
1560
|
const move =
|
|
1545
1561
|
close.via === "enter"
|
|
@@ -2163,8 +2179,14 @@ export function TMDataGridTable<TData extends RowData = TMDataGridRowData>({
|
|
|
2163
2179
|
// open, so every row holding a form shows its editors. Which of
|
|
2164
2180
|
// them holds the caret is not decided here - see
|
|
2165
2181
|
// placeEditCaret, which places it once per gesture.
|
|
2182
|
+
//
|
|
2183
|
+
// A parked row is not one of them. It keeps its form, so it is
|
|
2184
|
+
// still "open", but it has been decided: it renders its draft
|
|
2185
|
+
// through the column's own renderer until `begin` reopens it.
|
|
2166
2186
|
const rowEditing =
|
|
2167
|
-
features.editMode === "row" &&
|
|
2187
|
+
features.editMode === "row" &&
|
|
2188
|
+
editOpenRowIds.includes(row.id) &&
|
|
2189
|
+
!editDraftRowIds.includes(row.id);
|
|
2168
2190
|
// Cell selection takes the body's tab stop off the row and puts
|
|
2169
2191
|
// it on a cell - two stops per row would make Tab a way of
|
|
2170
2192
|
// walking the grid, which is what the arrow keys are for. Space
|
|
@@ -54,22 +54,20 @@ export function TMDataGridSummaryCount({ children }: { children?: ReactNode }) {
|
|
|
54
54
|
const { table, controlSize } = useTMDataGridContext();
|
|
55
55
|
useSelector(table.store);
|
|
56
56
|
|
|
57
|
-
if (children !== undefined) {
|
|
58
|
-
return (
|
|
59
|
-
<Text span size={controlSize} c="dimmed" data-dg-part="summary-count">
|
|
60
|
-
{children}
|
|
61
|
-
</Text>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
57
|
const shown = table.getRowCount();
|
|
66
58
|
const total =
|
|
67
59
|
table.options.meta?.totalRowCount ??
|
|
68
60
|
table.getPreFilteredRowModel().rows.length;
|
|
69
61
|
|
|
70
62
|
return (
|
|
71
|
-
<Text
|
|
72
|
-
|
|
63
|
+
<Text
|
|
64
|
+
span
|
|
65
|
+
size={controlSize}
|
|
66
|
+
c="dimmed"
|
|
67
|
+
className={classes.summaryCount}
|
|
68
|
+
data-dg-part="summary-count"
|
|
69
|
+
>
|
|
70
|
+
{children !== undefined ? children : `${shown} / ${total}`}
|
|
73
71
|
</Text>
|
|
74
72
|
);
|
|
75
73
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NumberInput } from "@mantine/core";
|
|
2
|
+
import { useState } from "react";
|
|
2
3
|
import type { TMDataGridEditorArgs } from "../../core/editEngine";
|
|
3
4
|
import { getColumnLabel } from "../../core/columnUtils";
|
|
4
5
|
import { useTMDataGridContext } from "../../TMDataGridContext";
|
|
@@ -15,6 +16,19 @@ export function TMDataGridNumberEditor({ field, column, size }: TMDataGridEditor
|
|
|
15
16
|
const value = useFieldValue(field);
|
|
16
17
|
const error = useFieldError(field);
|
|
17
18
|
const { inputRef, remember } = useCaretKeeper();
|
|
19
|
+
/**
|
|
20
|
+
* The last text Mantine reported instead of a number - "-" or "1e" on the
|
|
21
|
+
* way to one. Held so a half-typed number stays on screen while the field
|
|
22
|
+
* itself holds no value.
|
|
23
|
+
*/
|
|
24
|
+
const [typed, setTyped] = useState<string | null>(null);
|
|
25
|
+
// The field is the source of truth: the text survives only while it still
|
|
26
|
+
// describes what the field holds, so a write from outside the editor -
|
|
27
|
+
// `edit.setCellValue`, a revert - replaces it rather than being masked.
|
|
28
|
+
const parsedTyped = typed === null || typed === "" ? null : Number(typed);
|
|
29
|
+
const textDescribesField = Number.isFinite(parsedTyped)
|
|
30
|
+
? parsedTyped === value
|
|
31
|
+
: value === null;
|
|
18
32
|
return (
|
|
19
33
|
<NumberInput
|
|
20
34
|
ref={inputRef}
|
|
@@ -24,13 +38,29 @@ export function TMDataGridNumberEditor({ field, column, size }: TMDataGridEditor
|
|
|
24
38
|
hideControls
|
|
25
39
|
aria-label={labels.editCell(getColumnLabel(column))}
|
|
26
40
|
data-dg-part="editor-input"
|
|
27
|
-
value={
|
|
41
|
+
value={
|
|
42
|
+
typed !== null && textDescribesField
|
|
43
|
+
? typed
|
|
44
|
+
: typeof value === "number"
|
|
45
|
+
? value
|
|
46
|
+
: ""
|
|
47
|
+
}
|
|
28
48
|
onChange={(next) => {
|
|
29
49
|
if (inputRef.current !== null) remember(inputRef.current);
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
if (typeof next === "number") {
|
|
51
|
+
setTyped(null);
|
|
52
|
+
field.handleChange(next);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Mantine reports a number once one can be parsed and a string while
|
|
56
|
+
// it cannot: "" for an empty cell, partial input on the way to a
|
|
57
|
+
// number. Anything that does not parse leaves the field empty rather
|
|
58
|
+
// than `NaN` - which is a number to every validator and would commit
|
|
59
|
+
// as one, with no message a user could act on.
|
|
60
|
+
setTyped(next);
|
|
61
|
+
const parsed = Number(next);
|
|
32
62
|
field.handleChange(
|
|
33
|
-
|
|
63
|
+
next !== "" && Number.isFinite(parsed) ? parsed : null,
|
|
34
64
|
);
|
|
35
65
|
}}
|
|
36
66
|
onBlur={field.handleBlur}
|
|
@@ -1,34 +1,6 @@
|
|
|
1
1
|
import { useStore } from "@tanstack/react-form";
|
|
2
2
|
import { useLayoutEffect, useRef, type RefObject } from "react";
|
|
3
|
-
import type
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The first error as text, whatever shape the validator produced - TanStack
|
|
7
|
-
* Form keeps errors verbatim, so a Zod issue arrives as `{ message }`, a
|
|
8
|
-
* plain function's return arrives as it was, and a form-level schema's
|
|
9
|
-
* pathless issues arrive keyed under an empty path (`{ "": [issues] }`).
|
|
10
|
-
* This digs until it finds a message.
|
|
11
|
-
*/
|
|
12
|
-
export function firstErrorText(error: unknown): string | undefined {
|
|
13
|
-
if (error === null || error === undefined) return undefined;
|
|
14
|
-
if (typeof error === "string") return error;
|
|
15
|
-
if (Array.isArray(error)) {
|
|
16
|
-
for (const entry of error) {
|
|
17
|
-
const text = firstErrorText(entry);
|
|
18
|
-
if (text !== undefined) return text;
|
|
19
|
-
}
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
if (typeof error === "object") {
|
|
23
|
-
const message = (error as { message?: unknown }).message;
|
|
24
|
-
if (typeof message === "string") return message;
|
|
25
|
-
for (const value of Object.values(error)) {
|
|
26
|
-
const text = firstErrorText(value);
|
|
27
|
-
if (text !== undefined) return text;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
3
|
+
import { firstErrorText, type TMDataGridEditField } from "../../core/editEngine";
|
|
32
4
|
|
|
33
5
|
/** The field's live value - one subscription, on the field's own store. */
|
|
34
6
|
export function useFieldValue(field: TMDataGridEditField): unknown {
|
|
@@ -120,13 +120,20 @@ export type TMDataGridFeatureFlags = {
|
|
|
120
120
|
grouping: boolean;
|
|
121
121
|
/** Whether cells can be edited at all - the `editing` option was set. */
|
|
122
122
|
editing: boolean;
|
|
123
|
-
/**
|
|
123
|
+
/** What counts as a commit, or `null` while editing is off. */
|
|
124
124
|
editMode: TMDataGridEditMode | null;
|
|
125
125
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
|
|
126
|
+
* Where a commit goes - `editing.draft`. On, commits park in the grid's
|
|
127
|
+
* draft store and leave through `edit.saveDrafts()`; off, each one reaches
|
|
128
|
+
* the consumer as it happens.
|
|
129
|
+
*/
|
|
130
|
+
editDraft: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Whether a parked entry row stays pinned in the sticky entry block until
|
|
133
|
+
* the draft store is saved - `editing.newRowsSticky`, which needs
|
|
134
|
+
* `editing.draft`. Off by default: a parked row joins the scrolling flow
|
|
135
|
+
* instead, so entering many rows cannot fill the viewport with sticky
|
|
136
|
+
* chrome.
|
|
130
137
|
*/
|
|
131
138
|
editNewRowsSticky: boolean;
|
|
132
139
|
/** The generated row-number gutter - `enableRowNumbers`. Off by default. */
|
|
@@ -211,8 +218,9 @@ export function readFeatureFlags<TData extends RowData>(
|
|
|
211
218
|
grouping: options.enableGrouping ?? options.manualPagination !== true,
|
|
212
219
|
editing: options.editing !== undefined,
|
|
213
220
|
editMode: options.editing?.mode ?? null,
|
|
221
|
+
editDraft: options.editing?.draft === true,
|
|
214
222
|
editNewRowsSticky:
|
|
215
|
-
options.editing?.
|
|
223
|
+
options.editing?.draft === true &&
|
|
216
224
|
options.editing.newRowsSticky === true,
|
|
217
225
|
rowNumbers: options.enableRowNumbers === true,
|
|
218
226
|
// A predicate counts as on - some rows may still pin.
|