@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.
- package/dist/{chat-ui-DM12Y9is.js → chat-ui-Dcb8pQhM.js} +8 -8
- package/dist/click-outside-container-D_URcm3D.js +167 -0
- package/dist/{common-DuuFqqGa.js → common-C8LWIius.js} +571 -571
- package/dist/{data-grid-overlay-editor-D-p0q7YM.js → data-grid-overlay-editor-DZM4BAaI.js} +49 -48
- package/dist/{click-outside-container-CVJZE0x6.js → data-grid-types-BnZwWG8z.js} +150 -312
- package/dist/{dropdown-menu-C8N-kZzD.js → dropdown-menu-B39I9ARA.js} +20 -31
- package/dist/{glide-data-editor-64-L_DON.js → glide-data-editor-DEJqXbly.js} +2598 -2780
- package/dist/glide-utils-C0dRuWI_.js +201 -0
- package/dist/{html-to-image-B7RQgQQy.js → html-to-image-CvCnIqTI.js} +4476 -4465
- package/dist/main.js +5971 -5913
- package/dist/{process-output-BCqcNNll.js → process-output-C2joqP7d.js} +1 -1
- package/dist/{reveal-component-BDQwrD89.js → reveal-component-CdEcs6WV.js} +3 -3
- package/package.json +1 -1
- package/src/plugins/impl/DataEditorPlugin.tsx +38 -70
- package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +152 -1
- package/src/plugins/impl/data-editor/__tests__/data-utils.test.ts +73 -78
- package/src/plugins/impl/data-editor/__tests__/editor-state.test.ts +113 -0
- package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +49 -13
- package/src/plugins/impl/data-editor/__tests__/glide-utils.test.ts +44 -64
- package/src/plugins/impl/data-editor/data-utils.ts +76 -53
- package/src/plugins/impl/data-editor/editor-state.ts +108 -0
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +40 -223
- package/src/plugins/impl/data-editor/glide-utils.ts +71 -31
- package/src/plugins/impl/data-editor/types.ts +30 -16
- 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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
50
|
-
data:
|
|
55
|
+
export function renameColumn(
|
|
56
|
+
data: EditorRow[],
|
|
51
57
|
oldName: string,
|
|
52
58
|
newName: string,
|
|
53
|
-
):
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
export function modifyColumnFields(opts: {
|
|
77
|
+
type ModifyColumnFieldsOptions = {
|
|
67
78
|
columnFields: FieldTypes;
|
|
68
79
|
columnIdx: number;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
105
|
+
[opts.newColumnName, opts.dataType ?? "string"],
|
|
86
106
|
...entries.slice(columnIdx),
|
|
87
107
|
];
|
|
88
108
|
return new Map(newEntries);
|
|
89
109
|
}
|
|
90
|
-
case
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 =
|
|
58
|
+
export const GlideDataEditor = ({
|
|
77
59
|
data,
|
|
78
|
-
setData,
|
|
79
60
|
columnFields,
|
|
80
|
-
setColumnFields,
|
|
81
61
|
editableColumns,
|
|
82
|
-
edits,
|
|
83
62
|
onAddEdits,
|
|
84
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
222
|
+
[columns, data, editableColumns, onAddEdits, selection],
|
|
375
223
|
);
|
|
376
224
|
|
|
377
225
|
const onRowAppend = useCallback(() => {
|
|
378
|
-
const newRow:
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
setColumnFields((prev) =>
|
|
512
|
-
modifyColumnFields({
|
|
513
|
-
columnFields: prev,
|
|
333
|
+
onAddEdits([
|
|
334
|
+
{
|
|
514
335
|
columnIdx: clampedColumnIdx,
|
|
515
|
-
|
|
336
|
+
newName: columnName,
|
|
516
337
|
dataType,
|
|
517
|
-
|
|
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
|
};
|