@marimo-team/islands 0.24.1-dev79 → 0.24.1-dev80

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 (25) hide show
  1. package/dist/{chat-ui-DM12Y9is.js → chat-ui-Dcb8pQhM.js} +8 -8
  2. package/dist/click-outside-container-D_URcm3D.js +167 -0
  3. package/dist/{common-DuuFqqGa.js → common-C8LWIius.js} +571 -571
  4. package/dist/{data-grid-overlay-editor-D-p0q7YM.js → data-grid-overlay-editor-DZM4BAaI.js} +49 -48
  5. package/dist/{click-outside-container-CVJZE0x6.js → data-grid-types-BnZwWG8z.js} +150 -312
  6. package/dist/{dropdown-menu-C8N-kZzD.js → dropdown-menu-B39I9ARA.js} +20 -31
  7. package/dist/{glide-data-editor-64-L_DON.js → glide-data-editor-DEJqXbly.js} +2598 -2780
  8. package/dist/glide-utils-C0dRuWI_.js +201 -0
  9. package/dist/{html-to-image-B7RQgQQy.js → html-to-image-CvCnIqTI.js} +4476 -4465
  10. package/dist/main.js +5971 -5913
  11. package/dist/{process-output-BCqcNNll.js → process-output-C2joqP7d.js} +1 -1
  12. package/dist/{reveal-component-BDQwrD89.js → reveal-component-CdEcs6WV.js} +3 -3
  13. package/package.json +1 -1
  14. package/src/plugins/impl/DataEditorPlugin.tsx +38 -70
  15. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +152 -1
  16. package/src/plugins/impl/data-editor/__tests__/data-utils.test.ts +73 -78
  17. package/src/plugins/impl/data-editor/__tests__/editor-state.test.ts +113 -0
  18. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +49 -13
  19. package/src/plugins/impl/data-editor/__tests__/glide-utils.test.ts +44 -64
  20. package/src/plugins/impl/data-editor/data-utils.ts +76 -53
  21. package/src/plugins/impl/data-editor/editor-state.ts +108 -0
  22. package/src/plugins/impl/data-editor/glide-data-editor.tsx +40 -223
  23. package/src/plugins/impl/data-editor/glide-utils.ts +71 -31
  24. package/src/plugins/impl/data-editor/types.ts +30 -16
  25. package/dist/types-eyYyHvz7.js +0 -72
@@ -2,79 +2,99 @@
2
2
 
3
3
  import type { FieldTypes } from "@/components/data-table/types";
4
4
  import type { DataType } from "@/core/kernel/messages";
5
+ import { logNever } from "@/utils/assertNever";
5
6
  import { Logger } from "@/utils/Logger";
6
-
7
- /**
8
- * For modifying data, we do not worry about the order of the columns.
9
- * Because we define getCellContent based on columnTitle, the order does not matter.
10
- *
11
- * For column fields, we do care about the order
12
- */
13
-
14
- export function removeColumn<T>(data: T[], columnIdx: number): T[] {
15
- return data.map((row) => {
16
- const rowData = row as Record<string, unknown>;
17
- const keys = Object.keys(rowData);
18
-
19
- // If the column index is out of bounds, return the original row
20
- if (columnIdx < 0 || columnIdx >= keys.length) {
21
- return rowData as T;
7
+ import { BulkEdit, type EditorRow } from "./types";
8
+
9
+ export function orderColumnFields(
10
+ columnFields: FieldTypes,
11
+ columnNames: string[],
12
+ ): FieldTypes {
13
+ const ordered = new Map<string, DataType>();
14
+ for (const columnName of columnNames) {
15
+ ordered.set(columnName, columnFields.get(columnName) ?? "unknown");
16
+ }
17
+ for (const [columnName, dataType] of columnFields) {
18
+ if (!ordered.has(columnName)) {
19
+ ordered.set(columnName, dataType);
22
20
  }
21
+ }
22
+ return ordered;
23
+ }
23
24
 
24
- const keyToRemove = keys[columnIdx];
25
-
26
- // Create new object without the specified key
27
- const { [keyToRemove]: _, ...rest } = rowData;
28
- return rest as T;
25
+ export function removeColumn(
26
+ data: EditorRow[],
27
+ columnName: string,
28
+ ): EditorRow[] {
29
+ return data.map((row) => {
30
+ const { [columnName]: _, ...rest } = row;
31
+ return rest;
29
32
  });
30
33
  }
31
34
 
32
- /**
33
- * Insert a new column at the end of the data.
34
- * @param data - The data to insert the column into
35
- * @param newName - The name of the new column
36
- * @returns The data with the new column inserted at the end
37
- */
38
- export function insertColumn<T>(data: T[], newName?: string): T[] {
35
+ export function insertColumn(
36
+ data: EditorRow[],
37
+ newName?: string,
38
+ columnIdx?: number,
39
+ ): EditorRow[] {
39
40
  if (!newName) {
40
41
  return data;
41
42
  }
42
43
 
43
- return data.map((row) => ({
44
- ...(row as Record<string, unknown>),
45
- [newName]: "",
46
- })) as T[];
44
+ return data.map((row) => {
45
+ const entries = Object.entries(row);
46
+ const insertAt = Math.max(
47
+ 0,
48
+ Math.min(columnIdx ?? entries.length, entries.length),
49
+ );
50
+ entries.splice(insertAt, 0, [newName, ""]);
51
+ return Object.fromEntries(entries);
52
+ });
47
53
  }
48
54
 
49
- export function renameColumn<T>(
50
- data: T[],
55
+ export function renameColumn(
56
+ data: EditorRow[],
51
57
  oldName: string,
52
58
  newName: string,
53
- ): T[] {
59
+ ): EditorRow[] {
54
60
  if (!oldName || !newName || oldName === newName) {
55
61
  return data;
56
62
  }
63
+ if (data.some((row) => Object.hasOwn(row, newName))) {
64
+ return data;
65
+ }
57
66
 
58
67
  return data.map((row) => {
59
- const rowData = row as Record<string, unknown>;
60
- const { [oldName]: _, ...rest } = rowData;
61
- return { ...rest, [newName]: rowData[oldName] } as T;
68
+ return Object.fromEntries(
69
+ Object.entries(row).map(([columnName, value]) => [
70
+ columnName === oldName ? newName : columnName,
71
+ value,
72
+ ]),
73
+ );
62
74
  });
63
75
  }
64
76
 
65
- // Order of columns is important
66
- export function modifyColumnFields(opts: {
77
+ type ModifyColumnFieldsOptions = {
67
78
  columnFields: FieldTypes;
68
79
  columnIdx: number;
69
- type: "insert" | "remove" | "rename";
70
- dataType?: DataType;
71
- newColumnName?: string;
72
- }): FieldTypes {
73
- const { columnFields, columnIdx, type, dataType, newColumnName } = opts;
80
+ } & (
81
+ | { type: typeof BulkEdit.Remove }
82
+ | {
83
+ type: typeof BulkEdit.Insert | typeof BulkEdit.Rename;
84
+ newColumnName: string;
85
+ dataType?: DataType;
86
+ }
87
+ );
74
88
 
75
- switch (type) {
76
- case "insert": {
77
- if (!newColumnName) {
89
+ // Order of columns is important
90
+ export function modifyColumnFields(
91
+ opts: ModifyColumnFieldsOptions,
92
+ ): FieldTypes {
93
+ const { columnFields, columnIdx } = opts;
94
+
95
+ switch (opts.type) {
96
+ case BulkEdit.Insert: {
97
+ if (!opts.newColumnName) {
78
98
  Logger.error("newName is required for insert");
79
99
  return columnFields;
80
100
  }
@@ -82,12 +102,12 @@ export function modifyColumnFields(opts: {
82
102
  const entries = [...columnFields.entries()];
83
103
  const newEntries: Array<[string, DataType]> = [
84
104
  ...entries.slice(0, columnIdx),
85
- [newColumnName, dataType || "string"],
105
+ [opts.newColumnName, opts.dataType ?? "string"],
86
106
  ...entries.slice(columnIdx),
87
107
  ];
88
108
  return new Map(newEntries);
89
109
  }
90
- case "remove": {
110
+ case BulkEdit.Remove: {
91
111
  if (columnIdx < 0 || columnIdx >= columnFields.size) {
92
112
  return columnFields;
93
113
  }
@@ -101,8 +121,8 @@ export function modifyColumnFields(opts: {
101
121
  }
102
122
  return columnFields;
103
123
  }
104
- case "rename": {
105
- if (!newColumnName) {
124
+ case BulkEdit.Rename: {
125
+ if (!opts.newColumnName) {
106
126
  Logger.error("newName is required for rename");
107
127
  return columnFields;
108
128
  }
@@ -115,10 +135,13 @@ export function modifyColumnFields(opts: {
115
135
  const entries = [...columnFields.entries()];
116
136
  const newEntries: Array<[string, DataType]> = [
117
137
  ...entries.slice(0, columnIdx),
118
- [newColumnName, dataType || "string"],
138
+ [opts.newColumnName, opts.dataType ?? "string"],
119
139
  ...entries.slice(columnIdx + 1),
120
140
  ];
121
141
  return new Map(newEntries);
122
142
  }
143
+ default:
144
+ logNever(opts);
145
+ return columnFields;
123
146
  }
124
147
  }
@@ -0,0 +1,108 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { FieldTypes } from "@/components/data-table/types";
4
+ import { logNever } from "@/utils/assertNever";
5
+ import {
6
+ insertColumn,
7
+ modifyColumnFields,
8
+ removeColumn,
9
+ renameColumn,
10
+ } from "./data-utils";
11
+ import { isColumnEdit, isPositionalEdit, isRowEdit } from "./glide-utils";
12
+ import { BulkEdit, type EditorState, type Edits } from "./types";
13
+
14
+ export function applyEditorEdits(
15
+ state: EditorState,
16
+ edits: Edits["edits"],
17
+ ): EditorState {
18
+ let nextData = [...state.data];
19
+ let nextColumnFields: FieldTypes = new Map(state.columnFields);
20
+
21
+ for (const edit of edits) {
22
+ if (isPositionalEdit(edit)) {
23
+ if (edit.rowIdx < 0) {
24
+ continue;
25
+ }
26
+ while (nextData.length <= edit.rowIdx) {
27
+ nextData.push({});
28
+ }
29
+ nextData[edit.rowIdx] = {
30
+ ...nextData[edit.rowIdx],
31
+ [edit.columnId]: edit.value,
32
+ };
33
+ if (!nextColumnFields.has(edit.columnId)) {
34
+ nextColumnFields.set(edit.columnId, "unknown");
35
+ }
36
+ continue;
37
+ }
38
+
39
+ if (isRowEdit(edit)) {
40
+ if (
41
+ edit.type === BulkEdit.Remove &&
42
+ edit.rowIdx >= 0 &&
43
+ edit.rowIdx < nextData.length
44
+ ) {
45
+ nextData.splice(edit.rowIdx, 1);
46
+ }
47
+ continue;
48
+ }
49
+
50
+ if (!isColumnEdit(edit)) {
51
+ continue;
52
+ }
53
+
54
+ const columnName = [...nextColumnFields.keys()][edit.columnIdx];
55
+ switch (edit.type) {
56
+ case BulkEdit.Remove:
57
+ if (columnName === undefined) {
58
+ break;
59
+ }
60
+ nextData = removeColumn(nextData, columnName);
61
+ nextColumnFields = modifyColumnFields({
62
+ columnFields: nextColumnFields,
63
+ columnIdx: edit.columnIdx,
64
+ type: "remove",
65
+ });
66
+ break;
67
+ case BulkEdit.Insert:
68
+ if (
69
+ !edit.newName ||
70
+ edit.columnIdx < 0 ||
71
+ edit.columnIdx > nextColumnFields.size ||
72
+ nextColumnFields.has(edit.newName)
73
+ ) {
74
+ break;
75
+ }
76
+ nextData = insertColumn(nextData, edit.newName, edit.columnIdx);
77
+ nextColumnFields = modifyColumnFields({
78
+ columnFields: nextColumnFields,
79
+ columnIdx: edit.columnIdx,
80
+ type: "insert",
81
+ dataType: edit.dataType,
82
+ newColumnName: edit.newName,
83
+ });
84
+ break;
85
+ case BulkEdit.Rename:
86
+ if (
87
+ columnName === undefined ||
88
+ !edit.newName ||
89
+ nextColumnFields.has(edit.newName)
90
+ ) {
91
+ break;
92
+ }
93
+ nextData = renameColumn(nextData, columnName, edit.newName);
94
+ nextColumnFields = modifyColumnFields({
95
+ columnFields: nextColumnFields,
96
+ columnIdx: edit.columnIdx,
97
+ type: "rename",
98
+ dataType: nextColumnFields.get(columnName),
99
+ newColumnName: edit.newName,
100
+ });
101
+ break;
102
+ default:
103
+ logNever(edit);
104
+ }
105
+ }
106
+
107
+ return { data: nextData, columnFields: nextColumnFields };
108
+ }
@@ -14,13 +14,7 @@ import DataEditor, {
14
14
  type Rectangle,
15
15
  } from "@glideapps/glide-data-grid";
16
16
  import { CopyIcon, TrashIcon } from "lucide-react";
17
- import React, {
18
- useCallback,
19
- useEffect,
20
- useMemo,
21
- useRef,
22
- useState,
23
- } from "react";
17
+ import React, { useCallback, useMemo, useRef, useState } from "react";
24
18
  import useEvent from "react-use-event-hook";
25
19
  import type { FieldTypes } from "@/components/data-table/types";
26
20
  import {
@@ -34,59 +28,39 @@ import { copyToClipboard } from "@/utils/copy";
34
28
  import {
35
29
  getColumnHeaderIcon,
36
30
  getColumnKind,
37
- isColumnEdit,
38
- isPositionalEdit,
39
- isRowEdit,
31
+ isValidCellValue,
40
32
  pasteCells,
41
33
  } from "./glide-utils";
42
34
  import { getGlideTheme } from "./themes";
43
- import { BulkEdit, type Edits, type ModifiedGridColumn } from "./types";
44
35
  import "@glideapps/glide-data-grid/dist/index.css"; // TODO: We are reimporting this
45
36
  import { ErrorBoundary } from "@/components/editor/boundary/ErrorBoundary";
46
37
  import { Button } from "@/components/ui/button";
47
38
  import { toast } from "@/components/ui/use-toast";
48
39
  import type { DataType } from "@/core/kernel/messages";
49
- import { useNonce } from "@/hooks/useNonce";
50
40
  import { logNever } from "@/utils/assertNever";
51
41
  import { Events } from "@/utils/events";
52
42
  import { AddColumnSub, RenameColumnSub } from "./components";
53
- import {
54
- insertColumn,
55
- modifyColumnFields,
56
- removeColumn,
57
- renameColumn,
58
- } from "./data-utils";
59
43
  import { GlideDataEditorPortal } from "./glide-portal";
60
-
61
- interface GlideDataEditorProps<T> {
62
- data: T[];
63
- setData: (data: T[] | ((prev: T[]) => T[])) => void;
44
+ import {
45
+ BulkEdit,
46
+ type EditorRow,
47
+ type Edits,
48
+ type ModifiedGridColumn,
49
+ } from "./types";
50
+
51
+ interface GlideDataEditorProps {
52
+ data: EditorRow[];
64
53
  columnFields: FieldTypes;
65
- setColumnFields: React.Dispatch<React.SetStateAction<FieldTypes>>;
66
54
  editableColumns: string[] | "all";
67
- edits: Edits["edits"];
68
55
  onAddEdits: (edits: Edits["edits"]) => void;
69
- onAddRows: (newRows: object[]) => void;
70
- onDeleteRows: (rows: number[]) => void;
71
- onRenameColumn: (columnIdx: number, newName: string) => void;
72
- onDeleteColumn: (columnIdx: number) => void;
73
- onAddColumn: (columnIdx: number, newName: string) => void;
74
56
  }
75
57
 
76
- export const GlideDataEditor = <T,>({
58
+ export const GlideDataEditor = ({
77
59
  data,
78
- setData,
79
60
  columnFields,
80
- setColumnFields,
81
61
  editableColumns,
82
- edits,
83
62
  onAddEdits,
84
- onAddRows,
85
- onDeleteRows,
86
- onRenameColumn,
87
- onDeleteColumn,
88
- onAddColumn,
89
- }: GlideDataEditorProps<T>) => {
63
+ }: GlideDataEditorProps) => {
90
64
  const { theme } = useTheme();
91
65
  const dataEditorRef = useRef<DataEditorRef>(null);
92
66
  const portalElementRef = useRef<HTMLDivElement>(null);
@@ -99,8 +73,6 @@ export const GlideDataEditor = <T,>({
99
73
  });
100
74
 
101
75
  const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
102
- const rerender = useNonce();
103
- const hasAppliedEdits = useRef(false);
104
76
 
105
77
  const columns: ModifiedGridColumn[] = useMemo(() => {
106
78
  const columns: ModifiedGridColumn[] = [];
@@ -130,114 +102,12 @@ export const GlideDataEditor = <T,>({
130
102
  return columns;
131
103
  }, [columnFields, columnWidths, editableColumns, theme]);
132
104
 
133
- // Apply initial edits after data has loaded
134
- useEffect(() => {
135
- // Don't apply if already applied or data hasn't loaded yet
136
- if (hasAppliedEdits.current || data.length === 0) {
137
- return;
138
- }
139
-
140
- // Mark as applied once data loads - prevents re-applying user edits
141
- hasAppliedEdits.current = true;
142
-
143
- // No initial edits to apply
144
- if (edits.length === 0) {
145
- return;
146
- }
147
-
148
- // Group edits by row index to build new rows
149
- const newRows = new Map<number, Record<string, unknown>>();
150
-
151
- for (const edit of edits) {
152
- if (isPositionalEdit(edit)) {
153
- if (edit.rowIdx >= data.length) {
154
- // This is a new row
155
- if (!newRows.has(edit.rowIdx)) {
156
- newRows.set(edit.rowIdx, {});
157
- }
158
- const row = newRows.get(edit.rowIdx);
159
- if (row) {
160
- row[edit.columnId] = edit.value;
161
- }
162
- } else {
163
- // This is an existing row, update the data
164
- setData((prev) => {
165
- const newData = [...prev];
166
- newData[edit.rowIdx][edit.columnId as keyof T] =
167
- edit.value as T[keyof T];
168
- return newData;
169
- });
170
- }
171
- } else if (isRowEdit(edit) && edit.type === BulkEdit.Remove) {
172
- // Add rows is currently handled under positional edits, so we only cover deletes here
173
- setData((prev) => prev.filter((_, i) => i !== edit.rowIdx));
174
- } else if (isColumnEdit(edit)) {
175
- switch (edit.type) {
176
- case BulkEdit.Remove:
177
- // Remove the column from the data
178
- setData((prev) => removeColumn(prev, edit.columnIdx));
179
- setColumnFields((prev) =>
180
- modifyColumnFields({
181
- columnFields: prev,
182
- columnIdx: edit.columnIdx,
183
- type: "remove",
184
- }),
185
- );
186
- break;
187
- case BulkEdit.Insert:
188
- setColumnFields((prev) =>
189
- modifyColumnFields({
190
- columnFields: prev,
191
- columnIdx: edit.columnIdx,
192
- type: "insert",
193
- newColumnName: edit.newName,
194
- }),
195
- );
196
- setData((prev) => insertColumn(prev, edit.newName));
197
- break;
198
- case BulkEdit.Rename: {
199
- const oldName = columns[edit.columnIdx].title;
200
- const newName = edit.newName;
201
- if (!oldName || !newName) {
202
- return;
203
- }
204
-
205
- setColumnFields((prev) =>
206
- modifyColumnFields({
207
- columnFields: prev,
208
- columnIdx: edit.columnIdx,
209
- type: "rename",
210
- newColumnName: newName,
211
- }),
212
- );
213
-
214
- setData((prev) => renameColumn(prev, oldName, newName));
215
- break;
216
- }
217
- }
218
- }
219
- }
220
-
221
- // Add new rows in order
222
- const sortedNewRows = [...newRows.entries()]
223
- .toSorted(([a], [b]) => a - b)
224
- .map(([, row]) => row);
225
-
226
- if (sortedNewRows.length > 0) {
227
- setData((prev) => [...prev, ...(sortedNewRows as T[])]);
228
- }
229
-
230
- // Force re-render to update the total rows
231
- rerender();
232
- // oxlint-disable-next-line react-hooks/exhaustive-deps
233
- }, [data.length]);
234
-
235
105
  const getCellContent = useCallback(
236
106
  (cell: Item): GridCell => {
237
107
  const [col, row] = cell;
238
108
  const dataRow = data[row];
239
109
 
240
- const dataItem = dataRow[columns[col].title as keyof T];
110
+ const dataItem = dataRow[columns[col].title];
241
111
  const columnKind = columns[col].kind;
242
112
  const editable =
243
113
  editableColumns === "all" ||
@@ -289,15 +159,9 @@ export const GlideDataEditor = <T,>({
289
159
  newData = null;
290
160
  }
291
161
 
292
- setData((prev) => {
293
- const data = [...prev];
294
- data[row][key as keyof T] = newData as T[keyof T];
295
- return data;
296
- });
297
-
298
162
  onAddEdits([{ rowIdx: row, columnId: key, value: newData }]);
299
163
  },
300
- [columns, onAddEdits, setData],
164
+ [columns, onAddEdits],
301
165
  );
302
166
 
303
167
  const onColumnResize = useCallback((column: GridColumn, newSize: number) => {
@@ -314,22 +178,7 @@ export const GlideDataEditor = <T,>({
314
178
  const key = columns[col].title;
315
179
 
316
180
  const columnType = columnFields.get(key);
317
- // Verify the new value is of the correct type
318
- switch (columnType) {
319
- case "number":
320
- case "integer":
321
- if (Number.isNaN(Number(newValue.data))) {
322
- return false;
323
- }
324
- break;
325
- case "boolean":
326
- if (typeof newValue.data !== "boolean") {
327
- return false;
328
- }
329
- break;
330
- }
331
-
332
- return true;
181
+ return isValidCellValue(columnType, newValue.data);
333
182
  },
334
183
  [columnFields, columns],
335
184
  );
@@ -351,7 +200,6 @@ export const GlideDataEditor = <T,>({
351
200
  pasteCells({
352
201
  selection,
353
202
  data,
354
- setData,
355
203
  columns,
356
204
  editableColumns,
357
205
  onAddEdits,
@@ -371,11 +219,11 @@ export const GlideDataEditor = <T,>({
371
219
  return;
372
220
  }
373
221
  },
374
- [columns, data, editableColumns, onAddEdits, selection, setData],
222
+ [columns, data, editableColumns, onAddEdits, selection],
375
223
  );
376
224
 
377
225
  const onRowAppend = useCallback(() => {
378
- const newRow: Record<string, unknown> = Object.fromEntries(
226
+ const newRow: EditorRow = Object.fromEntries(
379
227
  columns.map((column) => {
380
228
  const dataType = column.dataType;
381
229
  switch (dataType) {
@@ -399,22 +247,23 @@ export const GlideDataEditor = <T,>({
399
247
  }
400
248
  }),
401
249
  );
402
- onAddRows([newRow]);
403
-
404
- // Update data
405
- setData((prev) => [...prev, newRow as T]);
406
- }, [columns, onAddRows, setData]);
250
+ onAddEdits(
251
+ Object.entries(newRow).map(([columnId, value]) => ({
252
+ rowIdx: data.length,
253
+ columnId,
254
+ value,
255
+ })),
256
+ );
257
+ }, [columns, data.length, onAddEdits]);
407
258
 
408
259
  const handleDeleteRows = () => {
409
260
  const rows = selection.rows.toArray();
410
- onDeleteRows(rows);
411
-
412
- let index = 0;
413
- for (const row of rows) {
414
- const adjustedRow = row - index; // Adjust for previously deleted rows
415
- setData((prev) => prev.filter((_, i) => i !== adjustedRow));
416
- index++;
417
- }
261
+ onAddEdits(
262
+ rows.map((rowIdx, index) => ({
263
+ rowIdx: rowIdx - index,
264
+ type: BulkEdit.Remove,
265
+ })),
266
+ );
418
267
 
419
268
  // Clear selection
420
269
  setSelection({
@@ -445,45 +294,20 @@ export const GlideDataEditor = <T,>({
445
294
 
446
295
  const handleRenameColumn = (newName: string) => {
447
296
  if (menu) {
448
- const oldColumnName = columns[menu.col].title;
449
-
450
297
  // Validate the new column name
451
298
  if (columnFields.has(newName)) {
452
299
  toastColumnExists(newName);
453
300
  return;
454
301
  }
455
302
 
456
- const dataType = columns[menu.col].dataType;
457
-
458
- onRenameColumn(menu.col, newName);
459
- setColumnFields((prev) =>
460
- modifyColumnFields({
461
- columnFields: prev,
462
- columnIdx: menu.col,
463
- type: "rename",
464
- dataType,
465
- newColumnName: newName,
466
- }),
467
- );
468
-
469
- // Update the data
470
- setData((prev) => renameColumn(prev, oldColumnName, newName));
303
+ onAddEdits([{ columnIdx: menu.col, newName, type: BulkEdit.Rename }]);
471
304
  setMenu(undefined);
472
305
  }
473
306
  };
474
307
 
475
308
  const handleDeleteColumn = () => {
476
309
  if (menu) {
477
- onDeleteColumn(menu.col);
478
- setColumnFields((prev) =>
479
- modifyColumnFields({
480
- columnFields: prev,
481
- columnIdx: menu.col,
482
- type: "remove",
483
- }),
484
- );
485
-
486
- setData((prev) => removeColumn(prev, menu.col));
310
+ onAddEdits([{ columnIdx: menu.col, type: BulkEdit.Remove }]);
487
311
  setMenu(undefined);
488
312
  }
489
313
  };
@@ -506,21 +330,14 @@ export const GlideDataEditor = <T,>({
506
330
  return;
507
331
  }
508
332
 
509
- onAddColumn(clampedColumnIdx, columnName);
510
-
511
- setColumnFields((prev) =>
512
- modifyColumnFields({
513
- columnFields: prev,
333
+ onAddEdits([
334
+ {
514
335
  columnIdx: clampedColumnIdx,
515
- type: "insert",
336
+ newName: columnName,
516
337
  dataType,
517
- newColumnName: columnName,
518
- }),
519
- );
520
-
521
- // Update the data - add the new column to all rows,
522
- // ordering does not matter as we call getCellContent based on columnTitle
523
- setData((prev) => insertColumn(prev, columnName));
338
+ type: BulkEdit.Insert,
339
+ },
340
+ ]);
524
341
  setMenu(undefined);
525
342
  }
526
343
  };