@dxos/react-ui-table 0.8.4-main.5ea62a8 → 0.8.4-main.72ec0f3
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 → chunk-2PFAPQL4.mjs} +7 -7
- package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +7 -0
- package/dist/lib/browser/{chunk-RQYXNWWN.mjs → chunk-GS2UNGJK.mjs} +451 -498
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +170 -317
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +10 -7
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node-esm/{chunk-K734DJB7.mjs → chunk-6POOFFYF.mjs} +7 -7
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-QNDUGIS7.mjs → chunk-TRR3C5NX.mjs} +451 -498
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +170 -317
- 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 +10 -7
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +3 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +70 -7
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +5 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +5 -6
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -5
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -2
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -3
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +3 -3
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +0 -7
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +4 -4
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +2 -3
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +3 -17
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +6 -6
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +5 -6
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +53 -52
- package/src/components/Table/DynamicTable.stories.tsx +11 -10
- package/src/components/Table/DynamicTable.tsx +4 -7
- package/src/components/Table/Relations.stories.tsx +127 -18
- package/src/components/Table/RowActionsMenu.tsx +21 -13
- package/src/components/Table/Table.stories.tsx +47 -46
- package/src/components/Table/Table.tsx +13 -9
- package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +19 -46
- package/src/components/TableCellEditor/TableCellEditor.tsx +17 -112
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +32 -22
- package/src/components/testing.ts +2 -2
- package/src/hooks/useAddRow.ts +1 -1
- package/src/hooks/useTableModel.ts +5 -5
- package/src/model/modal-controller.ts +0 -28
- package/src/model/table-model.test.ts +17 -7
- package/src/model/table-model.ts +19 -9
- package/src/model/table-presentation.test.ts +20 -18
- package/src/model/table-presentation.ts +4 -5
- package/src/model/table-sorting.ts +16 -7
- package/src/playwright/TableManager.ts +1 -1
- package/src/playwright/smoke.spec.ts +7 -5
- package/src/testing/useTestTableModel.ts +8 -5
- package/src/types/schema.ts +8 -8
- package/src/util/dynamic-table.ts +12 -13
- package/src/util/schema.ts +4 -4
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
- package/dist/lib/browser/chunk-RQYXNWWN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-QNDUGIS7.mjs.map +0 -7
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -103
|
@@ -4,16 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
6
|
import { EditorView } from '@codemirror/view';
|
|
7
|
-
import
|
|
7
|
+
import type * as Schema from 'effect/Schema';
|
|
8
8
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
9
9
|
|
|
10
10
|
import { debounce } from '@dxos/async';
|
|
11
11
|
import type { Client } from '@dxos/client';
|
|
12
|
-
import { FormatEnum, TypeEnum } from '@dxos/echo
|
|
12
|
+
import { FormatEnum, TypeEnum } from '@dxos/echo/internal';
|
|
13
13
|
import { invariant } from '@dxos/invariant';
|
|
14
14
|
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
15
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
16
|
-
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
17
15
|
import {
|
|
18
16
|
type EditorBlurHandler,
|
|
19
17
|
type EditorKeyEvent,
|
|
@@ -25,13 +23,12 @@ import {
|
|
|
25
23
|
parseCellIndex,
|
|
26
24
|
useGridContext,
|
|
27
25
|
} from '@dxos/react-ui-grid';
|
|
28
|
-
import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
|
|
29
26
|
import { type FieldProjection } from '@dxos/schema';
|
|
30
27
|
|
|
31
28
|
import { type ModalController, type TableModel } from '../../model';
|
|
32
29
|
|
|
33
30
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
34
|
-
import { FormCellEditor } from './FormCellEditor';
|
|
31
|
+
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
35
32
|
|
|
36
33
|
/**
|
|
37
34
|
* Option to create new object/value.
|
|
@@ -41,8 +38,9 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
|
41
38
|
export type TableCellEditorProps = {
|
|
42
39
|
model?: TableModel;
|
|
43
40
|
modals?: ModalController;
|
|
44
|
-
schema?: Schema.AnyNoContext;
|
|
41
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
45
42
|
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
43
|
+
onCreate?: OnCreateHandler;
|
|
46
44
|
onSave?: () => void;
|
|
47
45
|
client?: Client;
|
|
48
46
|
};
|
|
@@ -56,6 +54,7 @@ export const TableValueEditor = ({
|
|
|
56
54
|
schema,
|
|
57
55
|
onFocus,
|
|
58
56
|
onSave,
|
|
57
|
+
onCreate,
|
|
59
58
|
client,
|
|
60
59
|
__gridScope,
|
|
61
60
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
@@ -86,6 +85,7 @@ export const TableValueEditor = ({
|
|
|
86
85
|
schema={schema}
|
|
87
86
|
__gridScope={__gridScope}
|
|
88
87
|
onSave={onSave}
|
|
88
|
+
onCreate={onCreate}
|
|
89
89
|
client={client}
|
|
90
90
|
modals={modals}
|
|
91
91
|
/>
|
|
@@ -96,7 +96,7 @@ export const TableValueEditor = ({
|
|
|
96
96
|
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
const editorSlots = {
|
|
99
|
+
const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
100
100
|
|
|
101
101
|
export const TableCellEditor = ({
|
|
102
102
|
model,
|
|
@@ -105,9 +105,8 @@ export const TableCellEditor = ({
|
|
|
105
105
|
onSave,
|
|
106
106
|
__gridScope,
|
|
107
107
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
108
|
-
const {
|
|
108
|
+
const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
109
109
|
const suppressNextBlur = useRef(false);
|
|
110
|
-
const { themeMode } = useThemeContext();
|
|
111
110
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
112
111
|
const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
113
112
|
|
|
@@ -174,7 +173,6 @@ export const TableCellEditor = ({
|
|
|
174
173
|
}
|
|
175
174
|
|
|
176
175
|
const cell = parseCellIndex(editing.index);
|
|
177
|
-
|
|
178
176
|
if (value !== undefined) {
|
|
179
177
|
// Pre-commit validation check.
|
|
180
178
|
const result = await model.validateCellData(cell, value);
|
|
@@ -205,71 +203,26 @@ export const TableCellEditor = ({
|
|
|
205
203
|
[model, editing, onFocus, fieldProjection, setEditing, onSave],
|
|
206
204
|
);
|
|
207
205
|
|
|
208
|
-
const
|
|
206
|
+
const extensions = useMemo(() => {
|
|
209
207
|
if (!fieldProjection) {
|
|
210
208
|
return [];
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
const
|
|
211
|
+
const extensions = [
|
|
214
212
|
editorKeys({
|
|
215
213
|
onClose: handleClose,
|
|
216
214
|
...(editing?.initialContent && { onNav: handleClose }),
|
|
217
215
|
}),
|
|
218
216
|
];
|
|
219
|
-
|
|
220
|
-
const format = fieldProjection.props.format;
|
|
221
|
-
|
|
222
|
-
if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
|
|
223
|
-
// TODO(ZaymonFC): Reconcile this with the TagPicker component?
|
|
224
|
-
// Add markdown extensions needed by tag picker.
|
|
225
|
-
extension.push(createMarkdownExtensions({ themeMode }));
|
|
226
|
-
|
|
227
|
-
const options = fieldProjection.props.options || [];
|
|
228
|
-
|
|
229
|
-
const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
|
|
230
|
-
|
|
231
|
-
extension.push(
|
|
232
|
-
tagPicker({
|
|
233
|
-
mode,
|
|
234
|
-
keymap: false,
|
|
235
|
-
onSearch: (text, selectedIds) => {
|
|
236
|
-
return options
|
|
237
|
-
.filter(
|
|
238
|
-
(option) =>
|
|
239
|
-
selectedIds.indexOf(option.id) === -1 &&
|
|
240
|
-
(text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase())),
|
|
241
|
-
)
|
|
242
|
-
.map((option) => ({
|
|
243
|
-
id: option.id,
|
|
244
|
-
label: option.title,
|
|
245
|
-
hue: option.color as any,
|
|
246
|
-
}));
|
|
247
|
-
},
|
|
248
|
-
onUpdate: (ids) => {
|
|
249
|
-
if (model && editing) {
|
|
250
|
-
if (ids.length === 0) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (mode === 'single-select') {
|
|
254
|
-
void handleEnter(ids[0]);
|
|
255
|
-
} else {
|
|
256
|
-
void handleEnter(ids);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
}),
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Add validation extension to handle content changes
|
|
217
|
+
// Add validation extension to handle content changes.
|
|
265
218
|
if (model && editing) {
|
|
266
|
-
|
|
219
|
+
extensions.push(
|
|
267
220
|
EditorView.updateListener.of(
|
|
268
221
|
debounce((update) => {
|
|
269
222
|
const content = update.state.doc.toString();
|
|
270
223
|
const cell = parseCellIndex(editing.index);
|
|
271
224
|
|
|
272
|
-
// Perform validation on content change
|
|
225
|
+
// Perform validation on content change.
|
|
273
226
|
void model.validateCellData(cell, content).then((result) => {
|
|
274
227
|
if (result.valid) {
|
|
275
228
|
setValidationError(null);
|
|
@@ -283,60 +236,12 @@ export const TableCellEditor = ({
|
|
|
283
236
|
);
|
|
284
237
|
}
|
|
285
238
|
|
|
286
|
-
return
|
|
287
|
-
}, [model, modals, editing, fieldProjection, handleClose
|
|
239
|
+
return extensions;
|
|
240
|
+
}, [model, modals, editing, fieldProjection, handleClose]);
|
|
288
241
|
|
|
289
242
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
290
243
|
if (model && editing) {
|
|
291
244
|
const cell = parseCellIndex(editing.index);
|
|
292
|
-
const { col } = cell;
|
|
293
|
-
const field = model.projection.fields[col];
|
|
294
|
-
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
295
|
-
|
|
296
|
-
if (
|
|
297
|
-
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
298
|
-
fieldProjection?.props.format === FormatEnum.MultiSelect
|
|
299
|
-
) {
|
|
300
|
-
const value = model.getCellData(cell);
|
|
301
|
-
|
|
302
|
-
if (value !== undefined) {
|
|
303
|
-
const options = fieldProjection.props.options || [];
|
|
304
|
-
|
|
305
|
-
if (fieldProjection.props.format === FormatEnum.MultiSelect) {
|
|
306
|
-
const tagItems = value
|
|
307
|
-
.split(',')
|
|
308
|
-
.map((id: string) => {
|
|
309
|
-
const option = options.find((o) => o.id === id);
|
|
310
|
-
if (option) {
|
|
311
|
-
return {
|
|
312
|
-
id,
|
|
313
|
-
label: option.title,
|
|
314
|
-
hue: option.color as any,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
return undefined;
|
|
318
|
-
})
|
|
319
|
-
.filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
320
|
-
|
|
321
|
-
return createLinks(tagItems);
|
|
322
|
-
} else {
|
|
323
|
-
const option = options.find((o) => o.id === value);
|
|
324
|
-
|
|
325
|
-
if (option) {
|
|
326
|
-
const tagItem = {
|
|
327
|
-
id: value,
|
|
328
|
-
label: option.title,
|
|
329
|
-
hue: option.color as any,
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
return createLinks([tagItem]);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return '';
|
|
338
|
-
}
|
|
339
|
-
|
|
340
245
|
const value = model.getCellData(cell);
|
|
341
246
|
return value !== undefined ? String(value) : '';
|
|
342
247
|
}
|
|
@@ -345,7 +250,7 @@ export const TableCellEditor = ({
|
|
|
345
250
|
return (
|
|
346
251
|
<>
|
|
347
252
|
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
348
|
-
<GridCellEditor
|
|
253
|
+
<GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
349
254
|
</>
|
|
350
255
|
);
|
|
351
256
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
6
|
import React, { useMemo } from 'react';
|
|
7
7
|
|
|
8
8
|
import { live } from '@dxos/live-object';
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
createMenuAction,
|
|
19
19
|
useMenuActions,
|
|
20
20
|
} from '@dxos/react-ui-menu';
|
|
21
|
-
import {
|
|
21
|
+
import { atomFromSignal } from '@dxos/react-ui-menu';
|
|
22
22
|
|
|
23
23
|
import { translationKey } from '../../translations';
|
|
24
24
|
|
|
@@ -30,12 +30,14 @@ export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
|
|
|
30
30
|
|
|
31
31
|
type TableToolbarState = Partial<{ viewDirty: boolean }>;
|
|
32
32
|
|
|
33
|
+
// TODO(burdon): Radix style layout.
|
|
34
|
+
|
|
33
35
|
export type TableToolbarProps = ThemedClassName<
|
|
34
36
|
TableToolbarState & {
|
|
35
37
|
onAdd: () => void;
|
|
36
38
|
onSave: () => void;
|
|
37
39
|
attendableId?: string;
|
|
38
|
-
customActions?:
|
|
40
|
+
customActions?: Atom.Atom<ActionGraphProps>;
|
|
39
41
|
}
|
|
40
42
|
>;
|
|
41
43
|
|
|
@@ -46,33 +48,41 @@ const createTableToolbarActions = ({
|
|
|
46
48
|
customActions,
|
|
47
49
|
}: {
|
|
48
50
|
state: TableToolbarState;
|
|
49
|
-
onAdd
|
|
50
|
-
onSave
|
|
51
|
-
customActions?:
|
|
51
|
+
onAdd?: () => void;
|
|
52
|
+
onSave?: () => void;
|
|
53
|
+
customActions?: Atom.Atom<ActionGraphProps>;
|
|
52
54
|
}) =>
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
Atom.make((get) => {
|
|
56
|
+
const nodes: ActionGraphNodes = [];
|
|
57
|
+
if (onAdd) {
|
|
58
|
+
const add = createMenuAction<TableToolbarActionProperties>('add-row', onAdd, {
|
|
59
|
+
type: 'add-row' as const,
|
|
60
|
+
icon: 'ph--plus--regular',
|
|
61
|
+
label: ['add row', { ns: translationKey }],
|
|
62
|
+
testId: 'table.toolbar.add-row',
|
|
63
|
+
});
|
|
64
|
+
nodes.push(add);
|
|
65
|
+
}
|
|
66
|
+
if (onSave) {
|
|
67
|
+
const save = createMenuAction<TableToolbarActionProperties>('save-view', onSave, {
|
|
68
|
+
type: 'save-view' as const,
|
|
69
|
+
icon: 'ph--floppy-disk--regular',
|
|
70
|
+
label: ['save view label', { ns: translationKey }],
|
|
71
|
+
testId: 'table.toolbar.save-view',
|
|
72
|
+
iconOnly: false,
|
|
73
|
+
hidden: get(atomFromSignal(() => !state.viewDirty)),
|
|
74
|
+
});
|
|
75
|
+
nodes.push(save);
|
|
76
|
+
}
|
|
68
77
|
const gap = createGapSeparator();
|
|
69
|
-
|
|
78
|
+
nodes.push(...gap.nodes);
|
|
70
79
|
const edges: ActionGraphEdges = nodes.map(({ id: target }) => ({ source: 'root', target }));
|
|
71
80
|
if (customActions) {
|
|
72
81
|
const custom = get(customActions);
|
|
73
82
|
nodes.push(...custom.nodes);
|
|
74
83
|
edges.push(...custom.edges);
|
|
75
84
|
}
|
|
85
|
+
|
|
76
86
|
return {
|
|
77
87
|
nodes,
|
|
78
88
|
edges,
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo
|
|
8
|
+
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
|
|
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
|
@@ -6,17 +6,17 @@ import { effect } from '@preact/signals-core';
|
|
|
6
6
|
import orderBy from 'lodash.orderby';
|
|
7
7
|
import { useEffect, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
import { type JsonSchemaType } from '@dxos/echo/internal';
|
|
10
11
|
import { type Live } from '@dxos/live-object';
|
|
11
|
-
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
12
12
|
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
13
|
-
import { type
|
|
13
|
+
import { type ProjectionModel, type View } from '@dxos/schema';
|
|
14
14
|
import { isNonNullable } from '@dxos/util';
|
|
15
15
|
|
|
16
16
|
import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
|
|
17
17
|
|
|
18
18
|
export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
19
|
-
view?:
|
|
19
|
+
view?: View.View;
|
|
20
20
|
schema?: JsonSchemaType;
|
|
21
21
|
projection?: ProjectionModel;
|
|
22
22
|
rows?: Live<T>[];
|
|
@@ -39,7 +39,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
39
39
|
onRowAction,
|
|
40
40
|
...props
|
|
41
41
|
}: UseTableModelParams<T>): TableModel<T> | undefined => {
|
|
42
|
-
const selected = useSelected(view &&
|
|
42
|
+
const selected = useSelected(view && Obj.getDXN(view).toString(), 'multi');
|
|
43
43
|
const initialSelection = useMemo(() => selected, [view]);
|
|
44
44
|
|
|
45
45
|
const [model, setModel] = useState<TableModel<T>>();
|
|
@@ -5,21 +5,12 @@
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
6
|
import { type MutableRefObject, type RefObject } from 'react';
|
|
7
7
|
|
|
8
|
-
import { type Live } from '@dxos/live-object';
|
|
9
|
-
import { log } from '@dxos/log';
|
|
10
|
-
|
|
11
8
|
export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
|
|
12
9
|
|
|
13
10
|
export type ModalState =
|
|
14
11
|
| { type: 'row'; rowIndex: number }
|
|
15
12
|
| { type: 'column'; fieldId: string }
|
|
16
13
|
| { type: 'refPanel'; targetId: string; typename: string }
|
|
17
|
-
| {
|
|
18
|
-
type: 'createRefPanel';
|
|
19
|
-
typename: string;
|
|
20
|
-
initialValues?: Record<string, string>;
|
|
21
|
-
onCreate?: (obj: Live<any>) => void;
|
|
22
|
-
}
|
|
23
14
|
| { type: 'columnSettings'; mode: ColumnSettingsMode }
|
|
24
15
|
| { type: 'closed' };
|
|
25
16
|
|
|
@@ -68,25 +59,6 @@ export class ModalController {
|
|
|
68
59
|
}
|
|
69
60
|
};
|
|
70
61
|
|
|
71
|
-
public openCreateRef = (
|
|
72
|
-
typename: string,
|
|
73
|
-
anchorCell: Element | null,
|
|
74
|
-
initialValues?: Record<string, string>,
|
|
75
|
-
onCreate?: (obj: Live<any>) => void,
|
|
76
|
-
) => {
|
|
77
|
-
if (anchorCell) {
|
|
78
|
-
this._triggerRef.current = anchorCell as HTMLElement;
|
|
79
|
-
this._state.value = {
|
|
80
|
-
type: 'createRefPanel',
|
|
81
|
-
typename,
|
|
82
|
-
initialValues,
|
|
83
|
-
onCreate,
|
|
84
|
-
};
|
|
85
|
-
} else {
|
|
86
|
-
log.warn('anchor cell not found while creating new ref');
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
62
|
public close = () => {
|
|
91
63
|
this._state.value = { type: 'closed' };
|
|
92
64
|
};
|
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { computed } from '@preact/signals-core';
|
|
6
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { Filter, Query } from '@dxos/echo';
|
|
10
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
11
|
+
import { updateCounter } from '@dxos/echo/testing';
|
|
12
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
11
13
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
12
14
|
import { live } from '@dxos/live-object';
|
|
13
|
-
import {
|
|
14
|
-
import { createView } from '@dxos/schema';
|
|
15
|
+
import { View } from '@dxos/schema';
|
|
15
16
|
|
|
16
17
|
import { Table } from '../types';
|
|
17
18
|
|
|
@@ -24,7 +25,7 @@ registerSignalsRuntime();
|
|
|
24
25
|
|
|
25
26
|
describe('TableModel', () => {
|
|
26
27
|
let updateCount = 0;
|
|
27
|
-
let model:
|
|
28
|
+
let model: any;
|
|
28
29
|
|
|
29
30
|
beforeEach(async () => {
|
|
30
31
|
updateCount = 0;
|
|
@@ -32,6 +33,11 @@ describe('TableModel', () => {
|
|
|
32
33
|
onCellUpdate: () => updateCount++,
|
|
33
34
|
});
|
|
34
35
|
await model.open();
|
|
36
|
+
model.setRows([
|
|
37
|
+
{ id: '1', title: 'Test', completed: false },
|
|
38
|
+
{ id: '2', title: 'Test 2', completed: true },
|
|
39
|
+
{ id: '3', title: 'Test 3', completed: false },
|
|
40
|
+
]);
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
afterEach(async () => {
|
|
@@ -107,6 +113,10 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
107
113
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
108
114
|
const schema = createEchoSchema(Test);
|
|
109
115
|
const table = Table.make();
|
|
110
|
-
const view =
|
|
116
|
+
const view = View.make({
|
|
117
|
+
query: Query.select(Filter.type(schema)),
|
|
118
|
+
jsonSchema: schema.jsonSchema,
|
|
119
|
+
presentation: table,
|
|
120
|
+
});
|
|
111
121
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
112
122
|
};
|
package/src/model/table-model.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-c
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
8
|
import { Obj, Ref } from '@dxos/echo';
|
|
9
9
|
import {
|
|
10
|
-
type FieldSortType,
|
|
11
10
|
FormatEnum,
|
|
12
11
|
type JsonProp,
|
|
13
12
|
type JsonSchemaType,
|
|
@@ -15,11 +14,10 @@ import {
|
|
|
15
14
|
getValue,
|
|
16
15
|
setValue,
|
|
17
16
|
toEffectSchema,
|
|
18
|
-
} from '@dxos/echo
|
|
17
|
+
} from '@dxos/echo/internal';
|
|
19
18
|
import { invariant } from '@dxos/invariant';
|
|
20
19
|
import { ObjectId } from '@dxos/keys';
|
|
21
20
|
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
22
|
-
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
23
21
|
import { type Label } from '@dxos/react-ui';
|
|
24
22
|
import { formatForEditing, parseValue } from '@dxos/react-ui-form';
|
|
25
23
|
import {
|
|
@@ -28,7 +26,14 @@ import {
|
|
|
28
26
|
type DxGridPlaneRange,
|
|
29
27
|
type DxGridPosition,
|
|
30
28
|
} from '@dxos/react-ui-grid';
|
|
31
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
type FieldSortType,
|
|
31
|
+
ProjectionModel,
|
|
32
|
+
type PropertyType,
|
|
33
|
+
type ValidationError,
|
|
34
|
+
type View,
|
|
35
|
+
validateSchema,
|
|
36
|
+
} from '@dxos/schema';
|
|
32
37
|
|
|
33
38
|
import { Table } from '../types';
|
|
34
39
|
import { touch } from '../util';
|
|
@@ -72,7 +77,7 @@ const defaultFeatures: TableFeatures = {
|
|
|
72
77
|
export type InsertRowResult = 'draft' | 'final';
|
|
73
78
|
|
|
74
79
|
export type TableModelProps<T extends TableRow = TableRow> = {
|
|
75
|
-
view:
|
|
80
|
+
view: View.View;
|
|
76
81
|
schema: JsonSchemaType;
|
|
77
82
|
projection?: ProjectionModel;
|
|
78
83
|
features?: Partial<TableFeatures>;
|
|
@@ -90,7 +95,7 @@ export type TableModelProps<T extends TableRow = TableRow> = {
|
|
|
90
95
|
};
|
|
91
96
|
|
|
92
97
|
export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
93
|
-
private readonly _view:
|
|
98
|
+
private readonly _view: View.View;
|
|
94
99
|
private readonly _projection: ProjectionModel;
|
|
95
100
|
private _table?: Table.Table;
|
|
96
101
|
|
|
@@ -135,6 +140,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
135
140
|
super();
|
|
136
141
|
this._view = view;
|
|
137
142
|
this._projection = projection ?? new ProjectionModel(schema, view.projection);
|
|
143
|
+
this._projection.normalizeView();
|
|
138
144
|
|
|
139
145
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
140
146
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -169,10 +175,10 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
169
175
|
}
|
|
170
176
|
|
|
171
177
|
public get id(): string {
|
|
172
|
-
return
|
|
178
|
+
return Obj.getDXN(this._view).toString();
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
public get view():
|
|
181
|
+
public get view(): View.View {
|
|
176
182
|
return this._view;
|
|
177
183
|
}
|
|
178
184
|
|
|
@@ -473,7 +479,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
473
479
|
}
|
|
474
480
|
|
|
475
481
|
default: {
|
|
476
|
-
return formatForEditing({
|
|
482
|
+
return formatForEditing({
|
|
483
|
+
type: props.type,
|
|
484
|
+
format: props.format,
|
|
485
|
+
value,
|
|
486
|
+
});
|
|
477
487
|
}
|
|
478
488
|
}
|
|
479
489
|
};
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Filter, Query } from '@dxos/echo';
|
|
9
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
10
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
9
11
|
import { live } from '@dxos/live-object';
|
|
10
|
-
import {
|
|
11
|
-
import { createView } from '@dxos/schema';
|
|
12
|
+
import { View } from '@dxos/schema';
|
|
12
13
|
|
|
13
14
|
import { Table } from '../types';
|
|
14
15
|
|
|
@@ -26,8 +27,8 @@ describe('TablePresentation', () => {
|
|
|
26
27
|
updateCount = 0;
|
|
27
28
|
({ data } = live({
|
|
28
29
|
data: [
|
|
29
|
-
{
|
|
30
|
-
{
|
|
30
|
+
{ title: 'A', count: 1 },
|
|
31
|
+
{ title: 'B', count: 2 },
|
|
31
32
|
],
|
|
32
33
|
}));
|
|
33
34
|
|
|
@@ -44,8 +45,8 @@ describe('TablePresentation', () => {
|
|
|
44
45
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
45
46
|
|
|
46
47
|
// Trigger a row update.
|
|
47
|
-
data[0].
|
|
48
|
-
expect(updateCount).toBe(
|
|
48
|
+
data[0].title = 'New Value';
|
|
49
|
+
expect(updateCount).toBe(2);
|
|
49
50
|
|
|
50
51
|
// Verify the new value through getCells.
|
|
51
52
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
|
|
@@ -56,15 +57,14 @@ describe('TablePresentation', () => {
|
|
|
56
57
|
// Set up visible range to include our test data.
|
|
57
58
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
58
59
|
|
|
59
|
-
data.push({
|
|
60
|
-
expect(updateCount).toBe(
|
|
60
|
+
data.push({ title: 'C', count: 3 });
|
|
61
|
+
expect(updateCount).toBe(2);
|
|
61
62
|
|
|
62
63
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
63
64
|
|
|
64
65
|
// Verify the new row's data.
|
|
65
66
|
expect(cells['0,2']?.value).toBe('C');
|
|
66
67
|
expect(cells['1,2']?.value).toBe('3');
|
|
67
|
-
expect(cells['2,2']?.value).toBe('true');
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
it('should handle combined operations reactively', () => {
|
|
@@ -72,32 +72,34 @@ describe('TablePresentation', () => {
|
|
|
72
72
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
73
73
|
|
|
74
74
|
// Multiple operations.
|
|
75
|
-
data[0].
|
|
76
|
-
data.push({
|
|
75
|
+
data[0].title = 'Updated A';
|
|
76
|
+
data.push({ title: 'C', count: 3, completed: true });
|
|
77
77
|
data.splice(1, 1);
|
|
78
78
|
|
|
79
79
|
// We expect one update per operation affecting visible rows.
|
|
80
|
-
expect(updateCount).toBe(
|
|
80
|
+
expect(updateCount).toBe(4);
|
|
81
81
|
|
|
82
82
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
83
83
|
|
|
84
84
|
// Verify final state.
|
|
85
85
|
expect(cells['0,0']?.value).toBe('Updated A');
|
|
86
86
|
expect(cells['0,1']?.value).toBe('C');
|
|
87
|
-
expect(cells['1,1']?.value).toBe('3');
|
|
88
|
-
expect(cells['2,1']?.value).toBe('true');
|
|
89
87
|
});
|
|
90
88
|
});
|
|
91
89
|
});
|
|
92
90
|
|
|
93
91
|
class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
94
92
|
title: Schema.String,
|
|
95
|
-
|
|
93
|
+
count: Schema.Number,
|
|
96
94
|
}) {}
|
|
97
95
|
|
|
98
96
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
99
97
|
const schema = createEchoSchema(Test);
|
|
100
98
|
const table = Table.make();
|
|
101
|
-
const view =
|
|
99
|
+
const view = View.make({
|
|
100
|
+
query: Query.select(Filter.type(schema)),
|
|
101
|
+
jsonSchema: schema.jsonSchema,
|
|
102
|
+
presentation: table,
|
|
103
|
+
});
|
|
102
104
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
103
105
|
};
|