@multiplatform.one/table 7.29.0 → 7.30.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/cjs/components/DataTableCell/index.cjs +7 -0
- package/dist/cjs/components/DataTableCell/index.native.js +9 -0
- package/dist/cjs/components/DataTableCell/index.native.js.map +1 -1
- package/dist/cjs/components/KnobDialog.cjs +8 -9
- package/dist/cjs/components/KnobDialog.native.js +8 -9
- package/dist/cjs/components/KnobDialog.native.js.map +1 -1
- package/dist/cjs/components/TableInput/RowDetailEditor.cjs +79 -81
- package/dist/cjs/components/TableInput/index.cjs +139 -110
- package/dist/esm/components/DataTableCell/index.mjs +7 -0
- package/dist/esm/components/DataTableCell/index.mjs.map +1 -1
- package/dist/esm/components/DataTableCell/index.native.js +9 -0
- package/dist/esm/components/DataTableCell/index.native.js.map +1 -1
- package/dist/esm/components/KnobDialog.mjs +1 -2
- package/dist/esm/components/KnobDialog.mjs.map +1 -1
- package/dist/esm/components/KnobDialog.native.js +1 -2
- package/dist/esm/components/KnobDialog.native.js.map +1 -1
- package/dist/esm/components/TableInput/RowDetailEditor.mjs +79 -81
- package/dist/esm/components/TableInput/RowDetailEditor.mjs.map +1 -1
- package/dist/esm/components/TableInput/index.mjs +140 -111
- package/dist/esm/components/TableInput/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableCell/index.mjs +7 -0
- package/dist/jsx/components/DataTableCell/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableCell/index.native.js +9 -0
- package/dist/jsx/components/DataTableCell/index.native.js.map +1 -1
- package/dist/jsx/components/KnobDialog.mjs +1 -2
- package/dist/jsx/components/KnobDialog.mjs.map +1 -1
- package/dist/jsx/components/KnobDialog.native.js +8 -9
- package/dist/jsx/components/KnobDialog.native.js.map +1 -1
- package/dist/jsx/components/TableInput/RowDetailEditor.mjs +79 -81
- package/dist/jsx/components/TableInput/RowDetailEditor.mjs.map +1 -1
- package/dist/jsx/components/TableInput/index.mjs +140 -111
- package/dist/jsx/components/TableInput/index.mjs.map +1 -1
- package/package.json +12 -12
- package/src/components/ChildTable/childTable.rowDetail.spec.tsx +69 -0
- package/src/components/DataTableCell/cellDraftPolicy.spec.tsx +60 -0
- package/src/components/DataTableCell/index.tsx +10 -1
- package/src/components/KnobDialog.tsx +1 -2
- package/src/components/TableInput/RowDetailEditor.tsx +71 -70
- package/src/components/TableInput/index.tsx +136 -128
- package/src/components/TableInput/tableInput.cellIdentity.spec.tsx +196 -0
- package/types/components/KnobDialog.d.ts.map +1 -1
- package/types/components/TableInput/RowDetailEditor.d.ts +4 -0
- package/types/components/TableInput/RowDetailEditor.d.ts.map +1 -1
- package/types/components/TableInput/index.d.ts.map +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A row commit re-renders the grid's cells in place (MPO-475). TanStack's
|
|
3
|
+
* flexRender mounts each column's `cell` as a component, so a renderer that
|
|
4
|
+
* changes identity on a commit remounts every cell, and focus that a Tab or
|
|
5
|
+
* Enter just placed in the grid lands on a detached node.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
10
|
+
import { useForm } from "@multiplatform.one/forms";
|
|
11
|
+
import { act, fireEvent, screen, waitFor, within } from "@testing-library/react";
|
|
12
|
+
import userEvent from "@testing-library/user-event";
|
|
13
|
+
import { describe, expect, it, vi } from "vitest";
|
|
14
|
+
import type { DataTableColumn, DataTableData } from "../../types";
|
|
15
|
+
import { ChildTable } from "../ChildTable";
|
|
16
|
+
import { TableInput } from "./index";
|
|
17
|
+
|
|
18
|
+
interface Line extends DataTableData {
|
|
19
|
+
id: string;
|
|
20
|
+
item: string;
|
|
21
|
+
qty: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const initial = (): Line[] => [
|
|
25
|
+
{ id: "a", item: "Bracket", qty: 12 },
|
|
26
|
+
{ id: "b", item: "Bolts", qty: 3 },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const columns: DataTableColumn<Line>[] = [
|
|
30
|
+
{ accessorKey: "item", header: "Item", field: "input" },
|
|
31
|
+
{ accessorKey: "qty", header: "Qty", field: "stepper", fieldProps: { min: 0 } },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function Harness({
|
|
35
|
+
onChange,
|
|
36
|
+
freshSchema = false,
|
|
37
|
+
}: {
|
|
38
|
+
onChange?: (rows: Line[]) => void;
|
|
39
|
+
freshSchema?: boolean;
|
|
40
|
+
}) {
|
|
41
|
+
const [rows, setRows] = React.useState(initial);
|
|
42
|
+
return (
|
|
43
|
+
<ChildTable<Line>
|
|
44
|
+
value={rows}
|
|
45
|
+
columns={freshSchema ? columns.map((col) => ({ ...col })) : columns}
|
|
46
|
+
isCellHidden={freshSchema ? () => false : undefined}
|
|
47
|
+
rowDetail
|
|
48
|
+
onChange={(next) => {
|
|
49
|
+
setRows(next);
|
|
50
|
+
onChange?.(next);
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function FormHarness({ onChange }: { onChange?: (rows: Line[]) => void }) {
|
|
57
|
+
const form = useForm({ defaultValues: { lines: initial() } });
|
|
58
|
+
return (
|
|
59
|
+
<TableInput<Line>
|
|
60
|
+
form={form as any}
|
|
61
|
+
name="lines"
|
|
62
|
+
columns={columns}
|
|
63
|
+
rowDetail
|
|
64
|
+
onChange={onChange}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function cell(rowIndex: number, label: string) {
|
|
70
|
+
const row = document.querySelector<HTMLElement>(`[data-child-row-index="${rowIndex}"]`);
|
|
71
|
+
if (!row) throw new Error(`no grid row ${rowIndex}`);
|
|
72
|
+
return within(row).getByLabelText(label) as HTMLInputElement;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function gridControls() {
|
|
76
|
+
return [cell(0, "Item"), cell(0, "Qty"), cell(1, "Item"), cell(1, "Qty")];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function expectConnected(nodes: HTMLElement[]) {
|
|
80
|
+
expect(nodes.map((node) => node.isConnected)).toEqual(nodes.map(() => true));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const flushBlurTimer = () => act(() => new Promise<void>((resolve) => setTimeout(resolve, 15)));
|
|
84
|
+
|
|
85
|
+
describe("TableInput cells survive a row commit (MPO-475)", () => {
|
|
86
|
+
it("Enter commits an edit and focus stays on the same connected input", async () => {
|
|
87
|
+
const onChange = vi.fn();
|
|
88
|
+
renderWithProviders(<Harness onChange={onChange} />);
|
|
89
|
+
const before = gridControls();
|
|
90
|
+
const item = cell(0, "Item");
|
|
91
|
+
|
|
92
|
+
await userEvent.click(item);
|
|
93
|
+
await userEvent.type(item, "X");
|
|
94
|
+
fireEvent.keyDown(item, { key: "Enter" });
|
|
95
|
+
|
|
96
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
97
|
+
expect(onChange.mock.calls[0][0][0].item).toBe("BracketX");
|
|
98
|
+
expectConnected(before);
|
|
99
|
+
expect(document.activeElement).toBe(item);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("Tab after an edit commits and lands on the next cell's connected control", async () => {
|
|
103
|
+
const onChange = vi.fn();
|
|
104
|
+
renderWithProviders(<Harness onChange={onChange} />);
|
|
105
|
+
const before = gridControls();
|
|
106
|
+
const item = cell(0, "Item");
|
|
107
|
+
const qty = cell(0, "Qty");
|
|
108
|
+
|
|
109
|
+
await userEvent.click(item);
|
|
110
|
+
await userEvent.type(item, "X");
|
|
111
|
+
await userEvent.tab();
|
|
112
|
+
|
|
113
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
114
|
+
expect(onChange.mock.calls[0][0][0].item).toBe("BracketX");
|
|
115
|
+
expectConnected(before);
|
|
116
|
+
expect(document.activeElement).toBe(qty);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("Shift+Tab after a Stepper edit commits and lands on the previous cell's connected control", async () => {
|
|
120
|
+
const onChange = vi.fn();
|
|
121
|
+
renderWithProviders(<Harness onChange={onChange} />);
|
|
122
|
+
const before = gridControls();
|
|
123
|
+
const item = cell(0, "Item");
|
|
124
|
+
const qty = cell(0, "Qty");
|
|
125
|
+
|
|
126
|
+
await userEvent.click(qty);
|
|
127
|
+
await userEvent.clear(qty);
|
|
128
|
+
await userEvent.type(qty, "7");
|
|
129
|
+
await userEvent.tab({ shift: true });
|
|
130
|
+
await flushBlurTimer();
|
|
131
|
+
|
|
132
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect(onChange.mock.calls[0][0][0].qty).toBe(7);
|
|
134
|
+
expectConnected(before);
|
|
135
|
+
expect(document.activeElement).toBe(item);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("a commit in one row leaves the other row's cells connected in the form branch", async () => {
|
|
139
|
+
const onChange = vi.fn();
|
|
140
|
+
renderWithProviders(<FormHarness onChange={onChange} />);
|
|
141
|
+
const before = gridControls();
|
|
142
|
+
const item = cell(1, "Item");
|
|
143
|
+
|
|
144
|
+
await userEvent.click(item);
|
|
145
|
+
await userEvent.type(item, "X");
|
|
146
|
+
fireEvent.keyDown(item, { key: "Enter" });
|
|
147
|
+
|
|
148
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(onChange.mock.calls[0][0][1].item).toBe("BoltsX");
|
|
150
|
+
expectConnected(before);
|
|
151
|
+
expect(document.activeElement).toBe(item);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("a caller that rebuilds columns and isCellHidden every render keeps the cells mounted", async () => {
|
|
155
|
+
const onChange = vi.fn();
|
|
156
|
+
renderWithProviders(<Harness onChange={onChange} freshSchema />);
|
|
157
|
+
const before = gridControls();
|
|
158
|
+
const item = cell(0, "Item");
|
|
159
|
+
|
|
160
|
+
await userEvent.click(item);
|
|
161
|
+
await userEvent.type(item, "X");
|
|
162
|
+
await userEvent.tab();
|
|
163
|
+
|
|
164
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
165
|
+
expectConnected(before);
|
|
166
|
+
expect(document.activeElement).toBe(cell(0, "Qty"));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("a row detail Done leaves the grid mounted and shows the new values in its cells", async () => {
|
|
170
|
+
const onChange = vi.fn();
|
|
171
|
+
renderWithProviders(<Harness onChange={onChange} />);
|
|
172
|
+
const before = gridControls();
|
|
173
|
+
|
|
174
|
+
fireEvent.click(screen.getByRole("button", { name: "Edit row 2" }));
|
|
175
|
+
const dialog = await waitFor(() => screen.getByRole("dialog", { name: "Row 2" }));
|
|
176
|
+
const detailItem = within(dialog).getByLabelText("Item") as HTMLInputElement;
|
|
177
|
+
fireEvent.focus(detailItem);
|
|
178
|
+
fireEvent.change(detailItem, { target: { value: "Hinge" } });
|
|
179
|
+
fireEvent.blur(detailItem);
|
|
180
|
+
const detailQty = within(dialog).getByLabelText("Qty") as HTMLInputElement;
|
|
181
|
+
fireEvent.focus(detailQty);
|
|
182
|
+
fireEvent.change(detailQty, { target: { value: "9" } });
|
|
183
|
+
fireEvent.blur(detailQty);
|
|
184
|
+
await flushBlurTimer();
|
|
185
|
+
fireEvent.click(within(dialog).getByRole("button", { name: "Done" }));
|
|
186
|
+
|
|
187
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
188
|
+
expect(onChange.mock.calls[0][0][1]).toMatchObject({ item: "Hinge", qty: 9 });
|
|
189
|
+
await waitFor(() => expect(screen.queryByRole("dialog", { name: "Row 2" })).toBeNull(), {
|
|
190
|
+
timeout: 3000,
|
|
191
|
+
});
|
|
192
|
+
expectConnected(before.slice(0, 2));
|
|
193
|
+
expect(cell(1, "Item").value).toBe("Hinge");
|
|
194
|
+
expect(cell(1, "Qty").value).toBe("9");
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnobDialog.d.ts","sourceRoot":"","sources":["../../src/components/KnobDialog.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"KnobDialog.d.ts","sourceRoot":"","sources":["../../src/components/KnobDialog.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAY,EAAE,QAAQ,EAAE,EAAE,eAAe,2CAMzF;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,sBAAsB,2CAoC/E;AAGD,eAAO,MAAM,iBAAiB;;;;wFAAiB,CAAC;AAChD,eAAO,MAAM,eAAe;;;;;;;;;;6CAAe,CAAC;AAC5C,eAAO,MAAM,qBAAqB;;;;;;;;;;6CAAqB,CAAC;AACxD,eAAO,MAAM,eAAe;;;;;;;;wFAAe,CAAC"}
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* copy of the row seeded on open. "Done" and an outside click commit the
|
|
8
8
|
* draft once; "Cancel" and Escape discard it. A read-only row opens for
|
|
9
9
|
* viewing with a single "Close".
|
|
10
|
+
*
|
|
11
|
+
* The pencil is the dialog's `Dialog.Trigger`: tamagui's modal close returns
|
|
12
|
+
* focus to the trigger ref and nowhere else, so without it every close
|
|
13
|
+
* dropped focus on <body> (MPO-471).
|
|
10
14
|
*/
|
|
11
15
|
import type { DataTableColumn, DataTableData } from "../../types";
|
|
12
16
|
import { type ChildCellHidden } from "./shared";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RowDetailEditor.d.ts","sourceRoot":"","sources":["../../../src/components/TableInput/RowDetailEditor.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"RowDetailEditor.d.ts","sourceRoot":"","sources":["../../../src/components/TableInput/RowDetailEditor.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EAGd,MAAM,aAAa,CAAC;AAIrB,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3E,MAAM,WAAW,oBAAoB,CAAC,KAAK,SAAS,aAAa;IAC/D,GAAG,EAAE,KAAK,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IACtC,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACnD;AAED,wBAAgB,eAAe,CAAC,KAAK,SAAS,aAAa,EAAE,EAC3D,GAAG,EACH,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,QAAQ,GACT,EAAE,oBAAoB,CAAC,KAAK,CAAC,2CAyH7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TableInput/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAQH,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TableInput/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAQH,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AAalC,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,cAAc,EACf,MAAM,aAAa,CAAC;AAIrB,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,UAAU,CAAC;AA+GlB,MAAM,WAAW,eAAe,CAAC,KAAK,SAAS,aAAa,GAAG,aAAa;IAC1E,mFAAmF;IACnF,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC;IACf,4DAA4D;IAC5D,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACnC,sEAAsE;IACtE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACvC,iFAAiF;IACjF,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5E,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,mDAAmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kCAAkC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qCAAqC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yCAAyC;IACzC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5B,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;IACzC,kEAAkE;IAClE,YAAY,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;CACvC;AAED,wBAAgB,UAAU,CAAC,KAAK,SAAS,aAAa,GAAG,aAAa,EAAE,EACtE,IAAS,EACT,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,IAAI,EACJ,IAAI,EAAE,QAAQ,EACd,UAAU,EACV,QAAQ,EACR,QAAgB,EAChB,QAAgB,EAChB,cAAqB,EACrB,UAAiB,EACjB,aAAoB,EACpB,OAAO,EACP,OAAO,EACP,UAAU,EACV,YAAY,EACZ,YAAY,EAAE,SAAS,EACvB,OAAc,EACd,aAAqB,EACrB,SAAiB,EACjB,aAAa,EACb,YAAY,GACb,EAAE,eAAe,CAAC,KAAK,CAAC,2CA0ExB;AAiZD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC"}
|