@dxos/react-ui-table 0.8.4-main.5acf9ea → 0.8.4-main.5ea62a8
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/lib/browser/chunk-63UUF4IG.mjs +66 -0
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5XLSHLOG.mjs → chunk-RQYXNWWN.mjs} +531 -460
- package/dist/lib/browser/chunk-RQYXNWWN.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +685 -623
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +6 -4
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +3 -3
- package/dist/lib/node-esm/chunk-K734DJB7.mjs +67 -0
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-Y66YLSTC.mjs → chunk-QNDUGIS7.mjs} +531 -460
- package/dist/lib/node-esm/chunk-QNDUGIS7.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +685 -623
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +6 -4
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +50 -2
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +44 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +3 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +42 -3
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -3
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -15
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +2 -1
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +7 -5
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +2 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +2 -2
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +4 -9
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +24 -4
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +48 -47
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +1 -1
- package/src/components/Table/CreateRefPanel.tsx +25 -8
- package/src/components/Table/DynamicTable.stories.tsx +7 -4
- package/src/components/Table/DynamicTable.tsx +4 -3
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +40 -23
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +34 -23
- package/src/components/Table/Table.tsx +131 -110
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +143 -42
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +11 -23
- package/src/components/TableCellEditor/TableCellEditor.tsx +64 -119
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +1 -1
- package/src/hooks/useAddRow.ts +6 -4
- package/src/hooks/useTableModel.ts +1 -1
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +3 -2
- package/src/model/table-model.test.ts +3 -4
- package/src/model/table-model.ts +28 -23
- package/src/model/table-presentation.test.ts +4 -4
- package/src/model/table-presentation.ts +72 -30
- package/src/model/table-sorting.ts +3 -3
- package/src/playwright/smoke.spec.ts +129 -37
- package/src/testing/useTestTableModel.ts +4 -3
- package/src/translations.ts +2 -2
- package/src/types/index.ts +1 -1
- package/src/types/schema.ts +62 -5
- package/src/util/dynamic-table.ts +5 -5
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +1 -1
- package/src/util/table-buttons.ts +4 -3
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +0 -7
- package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -11
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/util/table.ts +0 -49
|
@@ -3,64 +3,60 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
6
7
|
import { type Schema } from 'effect/Schema';
|
|
7
|
-
import React, { useCallback,
|
|
8
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
8
9
|
|
|
10
|
+
import { debounce } from '@dxos/async';
|
|
11
|
+
import type { Client } from '@dxos/client';
|
|
9
12
|
import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
|
|
10
13
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { type
|
|
14
|
+
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
12
15
|
import { useThemeContext } from '@dxos/react-ui';
|
|
13
16
|
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
14
17
|
import {
|
|
15
|
-
|
|
16
|
-
editorKeys,
|
|
17
|
-
parseCellIndex,
|
|
18
|
-
useGridContext,
|
|
19
|
-
type DxGridPlanePosition,
|
|
18
|
+
type EditorBlurHandler,
|
|
20
19
|
type EditorKeyEvent,
|
|
21
20
|
type EditorKeyOrBlurHandler,
|
|
22
|
-
type EditorBlurHandler,
|
|
23
21
|
GridCellEditor,
|
|
24
22
|
type GridCellEditorProps,
|
|
25
23
|
type GridScopedProps,
|
|
24
|
+
editorKeys,
|
|
25
|
+
parseCellIndex,
|
|
26
|
+
useGridContext,
|
|
26
27
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import {
|
|
28
|
+
import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
|
|
28
29
|
import { type FieldProjection } from '@dxos/schema';
|
|
29
30
|
|
|
31
|
+
import { type ModalController, type TableModel } from '../../model';
|
|
32
|
+
|
|
30
33
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
34
|
import { FormCellEditor } from './FormCellEditor';
|
|
32
|
-
import { completion } from './extension';
|
|
33
|
-
import { type TableModel, type ModalController } from '../../model';
|
|
34
|
-
|
|
35
|
-
const newValue = Symbol.for('newValue');
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
37
|
* Option to create new object/value.
|
|
39
38
|
*/
|
|
40
|
-
export const createOption = (text: string) => ({ [newValue]: true, text });
|
|
41
|
-
|
|
42
|
-
const isCreateOption = (data: any) => typeof data === 'object' && data[newValue];
|
|
43
|
-
|
|
44
39
|
export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
45
40
|
|
|
46
41
|
export type TableCellEditorProps = {
|
|
47
42
|
model?: TableModel;
|
|
48
43
|
modals?: ModalController;
|
|
49
44
|
schema?: Schema.AnyNoContext;
|
|
50
|
-
|
|
51
|
-
onFocus?: DxGrid['refocus'];
|
|
45
|
+
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
52
46
|
onSave?: () => void;
|
|
53
|
-
|
|
47
|
+
client?: Client;
|
|
54
48
|
};
|
|
55
49
|
|
|
50
|
+
const adaptValidationMessage = (message: string | null) =>
|
|
51
|
+
message ? (message.endsWith('is missing') ? 'Can’t be blank' : message) : null;
|
|
52
|
+
|
|
56
53
|
export const TableValueEditor = ({
|
|
57
54
|
model,
|
|
58
55
|
modals,
|
|
59
56
|
schema,
|
|
60
|
-
onEnter,
|
|
61
57
|
onFocus,
|
|
62
58
|
onSave,
|
|
63
|
-
|
|
59
|
+
client,
|
|
64
60
|
__gridScope,
|
|
65
61
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
66
62
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
@@ -79,7 +75,8 @@ export const TableValueEditor = ({
|
|
|
79
75
|
|
|
80
76
|
if (
|
|
81
77
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
82
|
-
fieldProjection?.props.format === FormatEnum.SingleSelect
|
|
78
|
+
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
79
|
+
fieldProjection?.props.format === FormatEnum.Ref
|
|
83
80
|
// TODO(thure): Support `FormatEnum.MultiSelect`
|
|
84
81
|
) {
|
|
85
82
|
return (
|
|
@@ -89,22 +86,14 @@ export const TableValueEditor = ({
|
|
|
89
86
|
schema={schema}
|
|
90
87
|
__gridScope={__gridScope}
|
|
91
88
|
onSave={onSave}
|
|
89
|
+
client={client}
|
|
90
|
+
modals={modals}
|
|
92
91
|
/>
|
|
93
92
|
);
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
// For all other types, use the existing cell editor
|
|
97
|
-
return
|
|
98
|
-
<TableCellEditor
|
|
99
|
-
model={model}
|
|
100
|
-
modals={modals}
|
|
101
|
-
onEnter={onEnter}
|
|
102
|
-
onFocus={onFocus}
|
|
103
|
-
onQuery={onQuery}
|
|
104
|
-
onSave={onSave}
|
|
105
|
-
__gridScope={__gridScope}
|
|
106
|
-
/>
|
|
107
|
-
);
|
|
96
|
+
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
108
97
|
};
|
|
109
98
|
|
|
110
99
|
const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
@@ -112,17 +101,15 @@ const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-paddin
|
|
|
112
101
|
export const TableCellEditor = ({
|
|
113
102
|
model,
|
|
114
103
|
modals,
|
|
115
|
-
onEnter,
|
|
116
104
|
onFocus,
|
|
117
|
-
onQuery,
|
|
118
105
|
onSave,
|
|
119
106
|
__gridScope,
|
|
120
107
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
121
108
|
const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
122
109
|
const suppressNextBlur = useRef(false);
|
|
123
110
|
const { themeMode } = useThemeContext();
|
|
124
|
-
const [
|
|
125
|
-
const [
|
|
111
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
112
|
+
const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
126
113
|
|
|
127
114
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
128
115
|
if (!model || !editing) {
|
|
@@ -136,38 +123,6 @@ export const TableCellEditor = ({
|
|
|
136
123
|
return fieldProjection;
|
|
137
124
|
}, [model, editing]);
|
|
138
125
|
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
// Check for existing validation errors when editing starts (for draft rows).
|
|
141
|
-
if (!model || !editing || !fieldProjection) {
|
|
142
|
-
setValidationError(null);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const cell = parseCellIndex(editing.index);
|
|
147
|
-
const { row, col } = cell;
|
|
148
|
-
|
|
149
|
-
if (model.isDraftCell(cell)) {
|
|
150
|
-
const field = model.projection.fields[col];
|
|
151
|
-
const hasValidationError = model.hasDraftRowValidationError(row, field.path);
|
|
152
|
-
|
|
153
|
-
if (hasValidationError) {
|
|
154
|
-
const draftRows = model.draftRows.value;
|
|
155
|
-
if (row >= 0 && row < draftRows.length) {
|
|
156
|
-
const draftRow = draftRows[row];
|
|
157
|
-
const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
|
|
158
|
-
if (validationError) {
|
|
159
|
-
setValidationError(validationError.message);
|
|
160
|
-
setValidationVariant('warning');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
setValidationError(null);
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
setValidationError(null);
|
|
168
|
-
}
|
|
169
|
-
}, [model, editing, fieldProjection]);
|
|
170
|
-
|
|
171
126
|
const handleEnter = useCallback(
|
|
172
127
|
async (value: any) => {
|
|
173
128
|
if (!model || !editing) {
|
|
@@ -180,28 +135,34 @@ export const TableCellEditor = ({
|
|
|
180
135
|
if (validationResult.valid) {
|
|
181
136
|
setValidationError(null);
|
|
182
137
|
model.setCellData(cell, value);
|
|
183
|
-
onEnter?.(cell);
|
|
184
138
|
onFocus?.();
|
|
185
139
|
setEditing(null);
|
|
186
140
|
onSave?.();
|
|
187
141
|
} else {
|
|
188
|
-
setValidationError(validationResult.error);
|
|
142
|
+
setValidationError(adaptValidationMessage(validationResult.error));
|
|
189
143
|
setValidationVariant('error');
|
|
190
144
|
}
|
|
191
145
|
},
|
|
192
|
-
[model, editing,
|
|
146
|
+
[model, editing, onFocus, setEditing],
|
|
193
147
|
);
|
|
194
148
|
|
|
195
149
|
const handleBlur = useCallback<EditorBlurHandler>(
|
|
196
150
|
async (value) => {
|
|
197
|
-
if (!model || !editing) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
151
|
if (suppressNextBlur.current) {
|
|
201
152
|
suppressNextBlur.current = false;
|
|
153
|
+
} else if (model && editing) {
|
|
154
|
+
// Save silently if validation passes
|
|
155
|
+
const cell = parseCellIndex(editing.index);
|
|
156
|
+
if (value !== undefined) {
|
|
157
|
+
const result = await model.validateCellData(cell, value);
|
|
158
|
+
if (result.valid) {
|
|
159
|
+
setValidationError(null);
|
|
160
|
+
model.setCellData(cell, value);
|
|
161
|
+
setEditing(null);
|
|
162
|
+
onSave?.();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
202
165
|
}
|
|
203
|
-
|
|
204
|
-
// Don't save on blur - let handleClose handle validation and saving
|
|
205
166
|
},
|
|
206
167
|
[model, editing],
|
|
207
168
|
);
|
|
@@ -219,19 +180,20 @@ export const TableCellEditor = ({
|
|
|
219
180
|
const result = await model.validateCellData(cell, value);
|
|
220
181
|
|
|
221
182
|
if (result.valid) {
|
|
183
|
+
suppressNextBlur.current = true;
|
|
222
184
|
setValidationError(null);
|
|
223
185
|
model.setCellData(cell, value);
|
|
224
186
|
setEditing(null);
|
|
225
187
|
onSave?.();
|
|
226
|
-
onEnter?.(cell);
|
|
227
188
|
if (event && onFocus) {
|
|
228
|
-
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
189
|
+
onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
|
|
229
190
|
}
|
|
230
191
|
} else {
|
|
231
192
|
setValidationError(result.error);
|
|
232
193
|
setValidationVariant('error');
|
|
233
194
|
}
|
|
234
195
|
} else {
|
|
196
|
+
suppressNextBlur.current = true;
|
|
235
197
|
setValidationError(null);
|
|
236
198
|
setEditing(null);
|
|
237
199
|
onSave?.();
|
|
@@ -240,7 +202,7 @@ export const TableCellEditor = ({
|
|
|
240
202
|
}
|
|
241
203
|
}
|
|
242
204
|
},
|
|
243
|
-
[model, editing, onFocus,
|
|
205
|
+
[model, editing, onFocus, fieldProjection, setEditing, onSave],
|
|
244
206
|
);
|
|
245
207
|
|
|
246
208
|
const extension = useMemo(() => {
|
|
@@ -299,39 +261,26 @@ export const TableCellEditor = ({
|
|
|
299
261
|
);
|
|
300
262
|
}
|
|
301
263
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
},
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
void handleEnter(data);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
}),
|
|
331
|
-
]);
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
264
|
+
// Add validation extension to handle content changes
|
|
265
|
+
if (model && editing) {
|
|
266
|
+
extension.push(
|
|
267
|
+
EditorView.updateListener.of(
|
|
268
|
+
debounce((update) => {
|
|
269
|
+
const content = update.state.doc.toString();
|
|
270
|
+
const cell = parseCellIndex(editing.index);
|
|
271
|
+
|
|
272
|
+
// Perform validation on content change
|
|
273
|
+
void model.validateCellData(cell, content).then((result) => {
|
|
274
|
+
if (result.valid) {
|
|
275
|
+
setValidationError(null);
|
|
276
|
+
} else {
|
|
277
|
+
setValidationError(result.error);
|
|
278
|
+
setValidationVariant('error');
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}, 10),
|
|
282
|
+
),
|
|
283
|
+
);
|
|
335
284
|
}
|
|
336
285
|
|
|
337
286
|
return extension;
|
|
@@ -395,11 +344,7 @@ export const TableCellEditor = ({
|
|
|
395
344
|
|
|
396
345
|
return (
|
|
397
346
|
<>
|
|
398
|
-
<CellValidationMessage
|
|
399
|
-
validationError={_validationError}
|
|
400
|
-
variant={_validationVariant}
|
|
401
|
-
__gridScope={__gridScope}
|
|
402
|
-
/>
|
|
347
|
+
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
403
348
|
<GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
404
349
|
</>
|
|
405
350
|
);
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
type ActionGraphEdges,
|
|
12
12
|
type ActionGraphNodes,
|
|
13
13
|
type ActionGraphProps,
|
|
14
|
-
createGapSeparator,
|
|
15
|
-
createMenuAction,
|
|
16
14
|
type MenuAction,
|
|
17
15
|
MenuProvider,
|
|
18
16
|
ToolbarMenu,
|
|
17
|
+
createGapSeparator,
|
|
18
|
+
createMenuAction,
|
|
19
19
|
useMenuActions,
|
|
20
20
|
} from '@dxos/react-ui-menu';
|
|
21
21
|
import { rxFromSignal } from '@dxos/react-ui-menu';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import { FormatEnum, ObjectId,
|
|
8
|
+
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo-schema';
|
|
9
9
|
import { live } from '@dxos/live-object';
|
|
10
10
|
import { faker } from '@dxos/random';
|
|
11
11
|
import { type ProjectionModel } from '@dxos/schema';
|
package/src/hooks/useAddRow.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { useCallback } from 'react';
|
|
|
8
8
|
import { type Space } from '@dxos/client/echo';
|
|
9
9
|
import { Obj } from '@dxos/echo';
|
|
10
10
|
|
|
11
|
+
import { type InsertRowResult } from '../model';
|
|
12
|
+
|
|
11
13
|
export type UseAddRowParams = {
|
|
12
14
|
space?: Space;
|
|
13
15
|
schema?: Schema.Schema.AnyNoContext;
|
|
@@ -20,16 +22,16 @@ export type UseAddRowParams = {
|
|
|
20
22
|
*/
|
|
21
23
|
export const useAddRow = ({ space, schema }: UseAddRowParams) => {
|
|
22
24
|
return useCallback(
|
|
23
|
-
(data?: any) => {
|
|
25
|
+
(data?: any): InsertRowResult => {
|
|
24
26
|
if (space && schema) {
|
|
25
27
|
try {
|
|
26
28
|
space.db.add(Obj.make(schema, data ?? {}));
|
|
27
|
-
return
|
|
29
|
+
return 'final';
|
|
28
30
|
} catch (error) {
|
|
29
|
-
return
|
|
31
|
+
return 'draft';
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
return
|
|
34
|
+
return 'final';
|
|
33
35
|
},
|
|
34
36
|
[space, schema],
|
|
35
37
|
);
|
|
@@ -13,7 +13,7 @@ import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
|
13
13
|
import { type DataType, type ProjectionModel } from '@dxos/schema';
|
|
14
14
|
import { isNonNullable } from '@dxos/util';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
|
|
17
17
|
|
|
18
18
|
export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
19
19
|
view?: DataType.View;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
|
-
import { type
|
|
6
|
+
import { type MutableRefObject, type RefObject } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type Live } from '@dxos/live-object';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type ReadonlySignal, type Signal, computed, effect, signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
8
|
|
|
9
|
-
import { type TableRow } from './table-model';
|
|
10
9
|
import { touch } from '../util';
|
|
11
10
|
|
|
11
|
+
import { type TableRow } from './table-model';
|
|
12
|
+
|
|
12
13
|
export type SelectionMode = 'single' | 'multiple';
|
|
13
14
|
|
|
14
15
|
export class SelectionModel<T extends TableRow> extends Resource {
|
|
@@ -6,7 +6,6 @@ import { computed } from '@preact/signals-core';
|
|
|
6
6
|
import { Schema } from 'effect';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import { Obj } from '@dxos/echo';
|
|
10
9
|
import { TypedObject } from '@dxos/echo-schema';
|
|
11
10
|
import { updateCounter } from '@dxos/echo-schema/testing';
|
|
12
11
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
@@ -14,8 +13,9 @@ import { live } from '@dxos/live-object';
|
|
|
14
13
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
15
14
|
import { createView } from '@dxos/schema';
|
|
16
15
|
|
|
16
|
+
import { Table } from '../types';
|
|
17
|
+
|
|
17
18
|
import { TableModel, type TableModelProps } from './table-model';
|
|
18
|
-
import { TableView } from '../types';
|
|
19
19
|
|
|
20
20
|
// TODO(burdon): Tests are disabled in project.json since they bring in plugin deps.
|
|
21
21
|
// Restore once factored out into react-ui-table.
|
|
@@ -72,7 +72,6 @@ describe('TableModel', () => {
|
|
|
72
72
|
describe('reactivity', () => {
|
|
73
73
|
it('pure signals should nest', () => {
|
|
74
74
|
const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
|
|
75
|
-
|
|
76
75
|
const computed$ = computed(() => {
|
|
77
76
|
return signal$.arr.map((row) =>
|
|
78
77
|
computed(() => {
|
|
@@ -107,7 +106,7 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
107
106
|
|
|
108
107
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
109
108
|
const schema = createEchoSchema(Test);
|
|
110
|
-
const table =
|
|
109
|
+
const table = Table.make();
|
|
111
110
|
const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
|
|
112
111
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
113
112
|
};
|
package/src/model/table-model.ts
CHANGED
|
@@ -2,40 +2,41 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { computed, effect, signal
|
|
5
|
+
import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
8
|
import { Obj, Ref } from '@dxos/echo';
|
|
9
9
|
import {
|
|
10
10
|
type FieldSortType,
|
|
11
11
|
FormatEnum,
|
|
12
|
-
getValue,
|
|
13
|
-
setValue,
|
|
14
12
|
type JsonProp,
|
|
15
|
-
|
|
13
|
+
type JsonSchemaType,
|
|
16
14
|
getSchema,
|
|
15
|
+
getValue,
|
|
16
|
+
setValue,
|
|
17
17
|
toEffectSchema,
|
|
18
|
-
type JsonSchemaType,
|
|
19
18
|
} from '@dxos/echo-schema';
|
|
20
19
|
import { invariant } from '@dxos/invariant';
|
|
21
20
|
import { ObjectId } from '@dxos/keys';
|
|
22
|
-
import { isLiveObject } from '@dxos/live-object';
|
|
21
|
+
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
23
22
|
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
23
|
+
import { type Label } from '@dxos/react-ui';
|
|
24
24
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
25
25
|
import {
|
|
26
26
|
type DxGridAxisMeta,
|
|
27
|
-
type DxGridPlaneRange,
|
|
28
27
|
type DxGridPlanePosition,
|
|
28
|
+
type DxGridPlaneRange,
|
|
29
29
|
type DxGridPosition,
|
|
30
30
|
} from '@dxos/react-ui-grid';
|
|
31
|
-
import { type DataType, ProjectionModel, type PropertyType,
|
|
31
|
+
import { type DataType, ProjectionModel, type PropertyType, type ValidationError, validateSchema } from '@dxos/schema';
|
|
32
32
|
|
|
33
|
-
import {
|
|
34
|
-
import { TableSorting } from './table-sorting';
|
|
35
|
-
import { TableView } from '../types';
|
|
33
|
+
import { Table } from '../types';
|
|
36
34
|
import { touch } from '../util';
|
|
37
35
|
import { extractTagIds } from '../util/tag';
|
|
38
36
|
|
|
37
|
+
import { type SelectionMode, SelectionModel } from './selection-model';
|
|
38
|
+
import { TableSorting } from './table-sorting';
|
|
39
|
+
|
|
39
40
|
// Domain types for cell classification
|
|
40
41
|
export type TableCellType = 'standard' | 'draft' | 'header';
|
|
41
42
|
|
|
@@ -53,7 +54,7 @@ export type TableRow = Record<JsonProp, any> & { id: string };
|
|
|
53
54
|
|
|
54
55
|
export type TableRowAction = {
|
|
55
56
|
id: string;
|
|
56
|
-
|
|
57
|
+
label: Label;
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
export type TableFeatures = {
|
|
@@ -68,6 +69,8 @@ const defaultFeatures: TableFeatures = {
|
|
|
68
69
|
schemaEditable: false,
|
|
69
70
|
};
|
|
70
71
|
|
|
72
|
+
export type InsertRowResult = 'draft' | 'final';
|
|
73
|
+
|
|
71
74
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
72
75
|
view: DataType.View;
|
|
73
76
|
schema: JsonSchemaType;
|
|
@@ -78,7 +81,7 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
78
81
|
pinnedRows?: { top: number[]; bottom: number[] };
|
|
79
82
|
rowActions?: TableRowAction[];
|
|
80
83
|
onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
|
|
81
|
-
onInsertRow?: (data?: any) =>
|
|
84
|
+
onInsertRow?: (data?: any) => InsertRowResult;
|
|
82
85
|
onDeleteRows?: (index: number, obj: T[]) => void;
|
|
83
86
|
onDeleteColumn?: (fieldId: string) => void;
|
|
84
87
|
onCellUpdate?: (cell: DxGridPosition) => void;
|
|
@@ -89,14 +92,14 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
89
92
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
90
93
|
private readonly _view: DataType.View;
|
|
91
94
|
private readonly _projection: ProjectionModel;
|
|
92
|
-
private _table?:
|
|
95
|
+
private _table?: Table.Table;
|
|
93
96
|
|
|
94
97
|
private readonly _visibleRange = signal<DxGridPlaneRange>({
|
|
95
98
|
start: { row: 0, col: 0 },
|
|
96
99
|
end: { row: 0, col: 0 },
|
|
97
100
|
});
|
|
98
101
|
|
|
99
|
-
private readonly _onInsertRow?: (data?: any) =>
|
|
102
|
+
private readonly _onInsertRow?: (data?: any) => InsertRowResult;
|
|
100
103
|
private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
|
|
101
104
|
private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
|
|
102
105
|
private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
|
|
@@ -173,7 +176,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
173
176
|
return this._view;
|
|
174
177
|
}
|
|
175
178
|
|
|
176
|
-
public get table():
|
|
179
|
+
public get table(): Table.Table {
|
|
177
180
|
invariant(this._table, 'Model not initialized');
|
|
178
181
|
return this._table;
|
|
179
182
|
}
|
|
@@ -236,7 +239,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
236
239
|
|
|
237
240
|
protected override async _open(): Promise<void> {
|
|
238
241
|
const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
|
|
239
|
-
invariant(Obj.instanceOf(
|
|
242
|
+
invariant(Obj.instanceOf(Table.Table, presentation));
|
|
240
243
|
this._table = presentation;
|
|
241
244
|
|
|
242
245
|
this.initializeColumnMeta();
|
|
@@ -254,7 +257,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
254
257
|
return {
|
|
255
258
|
grid: meta,
|
|
256
259
|
frozenColsStart: { 0: { size: 30, resizeable: false } },
|
|
257
|
-
frozenColsEnd: { 0: { size:
|
|
260
|
+
frozenColsEnd: { 0: { size: 32, resizeable: false } },
|
|
258
261
|
};
|
|
259
262
|
});
|
|
260
263
|
}
|
|
@@ -334,11 +337,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
334
337
|
|
|
335
338
|
public getColumnCount = (): number => this._projection?.fields.length ?? 0;
|
|
336
339
|
|
|
337
|
-
public insertRow = ():
|
|
340
|
+
public insertRow = (): InsertRowResult => {
|
|
338
341
|
const result = this._onInsertRow?.();
|
|
339
|
-
if (result ===
|
|
342
|
+
if (result === 'draft' && this._draftRows.value.length === 0) {
|
|
340
343
|
this.createDraftRow();
|
|
341
344
|
}
|
|
345
|
+
return result ?? 'final';
|
|
342
346
|
};
|
|
343
347
|
|
|
344
348
|
private createDraftRow(): void {
|
|
@@ -393,14 +397,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
393
397
|
return false;
|
|
394
398
|
}
|
|
395
399
|
|
|
396
|
-
const
|
|
400
|
+
const insertRowResult = this._onInsertRow?.(draftRow.data);
|
|
397
401
|
|
|
398
|
-
if (
|
|
402
|
+
if (insertRowResult === 'final') {
|
|
399
403
|
const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
|
|
400
404
|
this._draftRows.value = newDraftRows;
|
|
401
405
|
}
|
|
402
406
|
|
|
403
|
-
return
|
|
407
|
+
return insertRowResult === 'final';
|
|
404
408
|
};
|
|
405
409
|
|
|
406
410
|
public deleteRow = (rowIndex: number): void => {
|
|
@@ -497,6 +501,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
497
501
|
setValue(snapshot, field.path, transformedValue);
|
|
498
502
|
|
|
499
503
|
const validationErrors = this.validateDraftRowData(snapshot);
|
|
504
|
+
// TODO(thure): These errors sometimes result in a useless message like “is missing” (what is missing?)
|
|
500
505
|
if (validationErrors.length > 0) {
|
|
501
506
|
const error = validationErrors.find((err) => err.path === field.path);
|
|
502
507
|
if (error) {
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
|
-
import { describe, expect, it
|
|
6
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import { Obj } from '@dxos/echo';
|
|
9
8
|
import { TypedObject } from '@dxos/echo-schema';
|
|
10
9
|
import { live } from '@dxos/live-object';
|
|
11
10
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
12
11
|
import { createView } from '@dxos/schema';
|
|
13
12
|
|
|
13
|
+
import { Table } from '../types';
|
|
14
|
+
|
|
14
15
|
import { TableModel, type TableModelProps } from './table-model';
|
|
15
16
|
import { TablePresentation } from './table-presentation';
|
|
16
|
-
import { TableView } from '../types';
|
|
17
17
|
|
|
18
18
|
describe('TablePresentation', () => {
|
|
19
19
|
describe('row reactivity', () => {
|
|
@@ -97,7 +97,7 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
97
97
|
|
|
98
98
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
99
99
|
const schema = createEchoSchema(Test);
|
|
100
|
-
const table =
|
|
100
|
+
const table = Table.make();
|
|
101
101
|
const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
|
|
102
102
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
103
103
|
};
|