@dxos/react-ui-table 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0
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-BF54SSNL.mjs +70 -0
- package/dist/lib/browser/chunk-BF54SSNL.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GS2UNGJK.mjs → chunk-NTWHM2PX.mjs} +656 -680
- package/dist/lib/browser/chunk-NTWHM2PX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1166 -1011
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +32 -44
- 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-SIRDIPHZ.mjs +71 -0
- package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-TRR3C5NX.mjs → chunk-Z3TLFG6Z.mjs} +656 -679
- package/dist/lib/node-esm/chunk-Z3TLFG6Z.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1166 -1011
- 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 +32 -44
- 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 -2
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +21 -37
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +82 -80
- 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 +28 -21
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +90 -26
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +21 -0
- package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts +25 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +10 -12
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +24 -26
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +22 -15
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +5 -6
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +9 -9
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +5 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +17 -8
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +104 -24
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +3 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +2 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +18 -15
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +21 -23
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +26 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- 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/util/dynamic-table.d.ts +14 -11
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
- package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts +7 -0
- package/dist/types/src/util/query-ast.d.ts.map +1 -0
- package/dist/types/src/util/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +55 -56
- package/src/components/Table/ColumnActionsMenu.tsx +15 -11
- package/src/components/Table/ColumnSettings.tsx +8 -9
- package/src/components/Table/DynamicTable.stories.tsx +27 -28
- package/src/components/Table/DynamicTable.tsx +32 -32
- package/src/components/Table/Relations.stories.tsx +61 -58
- package/src/components/Table/RowActionsMenu.tsx +3 -2
- package/src/components/Table/Table.stories.tsx +118 -102
- package/src/components/Table/Table.tsx +43 -407
- package/src/components/Table/TableContent.tsx +421 -0
- package/src/components/{TableToolbar → Table}/TableToolbar.tsx +43 -44
- package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -5
- package/src/components/TableCellEditor/FormCellEditor.tsx +118 -135
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +20 -17
- package/src/components/TableCellEditor/TableCellEditor.tsx +37 -33
- package/src/components/index.ts +0 -1
- package/src/components/testing.ts +43 -17
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +8 -10
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +36 -34
- package/src/model/modal-controller.ts +23 -12
- package/src/model/selection-model.ts +75 -44
- package/src/model/table-model.test.ts +82 -55
- package/src/model/table-model.ts +430 -167
- package/src/model/table-presentation.test.ts +59 -50
- package/src/model/table-presentation.ts +43 -38
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +52 -8
- package/src/testing/useTestTableModel.ts +48 -41
- package/src/translations.ts +20 -22
- package/src/types/Table.ts +79 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +40 -26
- package/src/util/index.ts +2 -0
- package/src/util/query-ast.ts +22 -0
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +1 -1
- package/src/util/table-controls.ts +2 -2
- package/dist/lib/browser/chunk-2PFAPQL4.mjs +0 -66
- package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +0 -7
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs +0 -67
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +0 -7
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
- package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
- package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
- package/dist/types/src/model/table-sorting.d.ts +0 -52
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/components/TableToolbar/index.ts +0 -5
- package/src/model/table-sorting.ts +0 -225
- package/src/types/schema.ts +0 -73
|
@@ -2,123 +2,48 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import * as Schema from 'effect/Schema';
|
|
5
|
+
import type * as Schema from 'effect/Schema';
|
|
6
6
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Entity, type Type } from '@dxos/echo';
|
|
9
|
+
import { Ref, getValue } from '@dxos/echo/internal';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import {
|
|
12
|
-
import { type
|
|
13
|
-
import { getSpace } from '@dxos/react-client/echo';
|
|
14
|
-
import { Popover } from '@dxos/react-ui';
|
|
15
|
-
import { Form, type FormProps } from '@dxos/react-ui-form';
|
|
11
|
+
import { type Label, Popover } from '@dxos/react-ui';
|
|
12
|
+
import { Form, type FormRootProps, type RefFieldProps } from '@dxos/react-ui-form';
|
|
16
13
|
import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
17
14
|
import { type FieldProjection } from '@dxos/schema';
|
|
18
15
|
import { getDeep, isTruthy, setDeep } from '@dxos/util';
|
|
19
16
|
|
|
20
|
-
import { type ModalController, type TableModel } from '../../model';
|
|
17
|
+
import { type ModalController, type TableModel, type TableRow } from '../../model';
|
|
21
18
|
import { translationKey } from '../../translations';
|
|
22
19
|
import { narrowSchema } from '../../util';
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
const createOptionLabel: Label = ['create-new-object.label', { ns: translationKey }];
|
|
25
22
|
|
|
26
|
-
export type
|
|
27
|
-
|
|
23
|
+
export type OnCreateHandler = (schema: Type.AnyEntity, values: any) => Parameters<typeof Ref.make>[0];
|
|
24
|
+
|
|
25
|
+
export type FormCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
|
|
26
|
+
__gridScope: any;
|
|
27
|
+
schema?: T;
|
|
28
28
|
model?: TableModel;
|
|
29
|
-
|
|
29
|
+
fieldProjection: FieldProjection;
|
|
30
|
+
modals?: ModalController; // TODO(burdon): Not used. Remove?
|
|
30
31
|
onSave?: () => void;
|
|
31
32
|
onCreate?: OnCreateHandler;
|
|
32
|
-
|
|
33
|
-
modals?: ModalController;
|
|
34
|
-
__gridScope: any;
|
|
35
|
-
} & Omit<FormProps<any>, 'values' | 'schema' | 'onCreate'>;
|
|
36
|
-
|
|
37
|
-
const createOptionLabel = ['create new object label', { ns: translationKey }] as [string, { ns: string }];
|
|
33
|
+
} & Omit<FormRootProps<any>, 'values' | 'schema' | 'onCreate'>;
|
|
38
34
|
|
|
39
|
-
export const FormCellEditor = ({
|
|
40
|
-
|
|
41
|
-
model,
|
|
35
|
+
export const FormCellEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
36
|
+
__gridScope,
|
|
42
37
|
schema,
|
|
38
|
+
model,
|
|
39
|
+
fieldProjection,
|
|
43
40
|
onSave,
|
|
44
|
-
client,
|
|
45
|
-
modals,
|
|
46
41
|
onCreate,
|
|
47
|
-
__gridScope,
|
|
48
42
|
...formProps
|
|
49
|
-
}: FormCellEditorProps) => {
|
|
50
|
-
const { editing: contextEditing, setEditing, id: gridId } = useGridContext('ArrayEditor', __gridScope);
|
|
51
|
-
const [editing, setLocalEditing] = useState(false);
|
|
43
|
+
}: FormCellEditorProps<T>) => {
|
|
52
44
|
const anchorRef = useRef<HTMLButtonElement | null>(null);
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
(typeAnnotation: Pick<TypeAnnotation, 'typename'>) => {
|
|
56
|
-
const space = getSpace(model!.view);
|
|
57
|
-
invariant(space);
|
|
58
|
-
|
|
59
|
-
let schema;
|
|
60
|
-
if (client) {
|
|
61
|
-
schema = client.graph.schemaRegistry.getSchema(typeAnnotation.typename);
|
|
62
|
-
}
|
|
63
|
-
if (!schema) {
|
|
64
|
-
schema = space.db.schemaRegistry.getSchema(typeAnnotation.typename);
|
|
65
|
-
}
|
|
66
|
-
return { space, schema };
|
|
67
|
-
},
|
|
68
|
-
[client, model],
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
const handleQueryRefOptions = useCallback(
|
|
72
|
-
async (typeAnnotation: TypeAnnotation) => {
|
|
73
|
-
const { schema, space } = getSchema(typeAnnotation);
|
|
74
|
-
if (model && schema && space) {
|
|
75
|
-
const { objects } = await space.db.query(Filter.type(schema)).run();
|
|
76
|
-
return objects
|
|
77
|
-
.map((obj) => {
|
|
78
|
-
return {
|
|
79
|
-
dxn: Obj.getDXN(obj),
|
|
80
|
-
label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
81
|
-
};
|
|
82
|
-
})
|
|
83
|
-
.filter(isTruthy);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return [];
|
|
87
|
-
},
|
|
88
|
-
[client, model],
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const originalRow = useMemo(() => {
|
|
92
|
-
if (model && contextEditing) {
|
|
93
|
-
const cell = parseCellIndex(contextEditing.index);
|
|
94
|
-
|
|
95
|
-
// Check if this is a draft cell and get the appropriate row data
|
|
96
|
-
if (model.isDraftCell(cell)) {
|
|
97
|
-
const draftRow = model.draftRows.value[cell.row];
|
|
98
|
-
invariant(draftRow);
|
|
99
|
-
return draftRow.data;
|
|
100
|
-
} else {
|
|
101
|
-
const row = model.getRowAt(cell.row);
|
|
102
|
-
invariant(row);
|
|
103
|
-
return row;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return undefined;
|
|
108
|
-
}, [model, contextEditing]);
|
|
109
|
-
|
|
110
|
-
const handleSave = useCallback(
|
|
111
|
-
(values: any) => {
|
|
112
|
-
const path = fieldProjection.field.path;
|
|
113
|
-
const value = getDeep(values, [path]);
|
|
114
|
-
setDeep(originalRow, [path], value);
|
|
115
|
-
contextEditing?.cellElement?.focus();
|
|
116
|
-
setEditing(null);
|
|
117
|
-
setLocalEditing(false);
|
|
118
|
-
onSave?.();
|
|
119
|
-
},
|
|
120
|
-
[fieldProjection.field.path, onSave, contextEditing, originalRow],
|
|
121
|
-
);
|
|
45
|
+
const { id: _gridId, editing: contextEditing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
46
|
+
const [editing, setLocalEditing] = useState(false);
|
|
122
47
|
|
|
123
48
|
useEffect(() => {
|
|
124
49
|
if (contextEditing && contextEditing.cellElement) {
|
|
@@ -132,16 +57,40 @@ export const FormCellEditor = ({
|
|
|
132
57
|
}
|
|
133
58
|
}, [contextEditing]);
|
|
134
59
|
|
|
135
|
-
const narrowedSchema = useMemo(() => {
|
|
60
|
+
const narrowedSchema = useMemo<Schema.Schema.AnyNoContext | undefined>(() => {
|
|
136
61
|
if (!schema) {
|
|
137
62
|
return undefined;
|
|
138
63
|
}
|
|
64
|
+
|
|
139
65
|
return narrowSchema(schema, [fieldProjection.field.path]);
|
|
140
66
|
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
141
67
|
|
|
142
|
-
|
|
143
|
-
|
|
68
|
+
const originalRow = useMemo<TableRow | undefined>(() => {
|
|
69
|
+
if (model && contextEditing) {
|
|
70
|
+
// Check if this is a draft cell and get the appropriate row data
|
|
71
|
+
const cell = parseCellIndex(contextEditing.index);
|
|
72
|
+
if (model.isDraftCell(cell)) {
|
|
73
|
+
const draftRow = model.getDraftRows()[cell.row];
|
|
74
|
+
invariant(draftRow);
|
|
75
|
+
return draftRow.data;
|
|
76
|
+
} else {
|
|
77
|
+
const row = model.getRowAt(cell.row);
|
|
78
|
+
invariant(row);
|
|
79
|
+
return row;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
144
82
|
|
|
83
|
+
return undefined;
|
|
84
|
+
}, [model, contextEditing]);
|
|
85
|
+
|
|
86
|
+
// NOTE: Important to get a mutable deep clone to eject from the echo object.
|
|
87
|
+
// TODO(wittjosiah): Consider using something like Obj.clone for this use case.
|
|
88
|
+
const initialFormValues = useMemo(() => (originalRow ? JSON.parse(JSON.stringify(originalRow)) : {}), [originalRow]);
|
|
89
|
+
const [formValues, setFormValues] = useState<any>(initialFormValues);
|
|
90
|
+
|
|
91
|
+
const handleValuesChanged = useCallback<NonNullable<FormRootProps<any>['onValuesChanged']>>((values) => {
|
|
92
|
+
setFormValues(values);
|
|
93
|
+
}, []);
|
|
145
94
|
const handleOpenChange = useCallback((nextOpen: boolean) => {
|
|
146
95
|
if (nextOpen === false) {
|
|
147
96
|
setEditing(null);
|
|
@@ -150,27 +99,41 @@ export const FormCellEditor = ({
|
|
|
150
99
|
setLocalEditing(nextOpen);
|
|
151
100
|
}, []);
|
|
152
101
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
102
|
+
const handleSave = useCallback<NonNullable<FormRootProps<any>['onSave']>>(
|
|
103
|
+
(values) => {
|
|
104
|
+
if (!originalRow) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const path = fieldProjection.field.path;
|
|
108
|
+
const value = getDeep(values, [path]);
|
|
109
|
+
// Use model's changeRow for consistent mutation handling.
|
|
110
|
+
if (model) {
|
|
111
|
+
model.changeRow(originalRow, (mutableRow) => {
|
|
112
|
+
setDeep(mutableRow, [path], value);
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
setDeep(originalRow, [path], value);
|
|
116
|
+
}
|
|
117
|
+
contextEditing?.cellElement?.focus();
|
|
118
|
+
setEditing(null);
|
|
119
|
+
setLocalEditing(false);
|
|
120
|
+
onSave?.();
|
|
121
|
+
},
|
|
122
|
+
[fieldProjection.field.path, onSave, contextEditing, originalRow, model],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const handleCreate = useCallback<NonNullable<FormRootProps<any>['onCreate']>>(
|
|
126
|
+
(schema, values) => {
|
|
127
|
+
const objectWithId = onCreate?.(schema, values);
|
|
128
|
+
if (objectWithId && originalRow) {
|
|
129
|
+
const ref = Ref.make(objectWithId);
|
|
130
|
+
const path = fieldProjection.field.path;
|
|
131
|
+
// Use model's changeRow for consistent mutation handling.
|
|
132
|
+
if (model) {
|
|
133
|
+
model.changeRow(originalRow, (mutableRow) => {
|
|
134
|
+
setDeep(mutableRow, [path], ref);
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
174
137
|
setDeep(originalRow, [path], ref);
|
|
175
138
|
}
|
|
176
139
|
}
|
|
@@ -179,7 +142,20 @@ export const FormCellEditor = ({
|
|
|
179
142
|
setLocalEditing(false);
|
|
180
143
|
onSave?.();
|
|
181
144
|
},
|
|
182
|
-
[fieldProjection.field.path, onSave, contextEditing, originalRow,
|
|
145
|
+
[fieldProjection.field.path, onSave, contextEditing, originalRow, onCreate, model],
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const getOptions = useCallback<NonNullable<RefFieldProps['getOptions']>>(
|
|
149
|
+
(results) =>
|
|
150
|
+
results
|
|
151
|
+
.map((obj) => {
|
|
152
|
+
return {
|
|
153
|
+
id: Entity.getDXN(obj).toString(),
|
|
154
|
+
label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
155
|
+
};
|
|
156
|
+
})
|
|
157
|
+
.filter(isTruthy),
|
|
158
|
+
[fieldProjection],
|
|
183
159
|
);
|
|
184
160
|
|
|
185
161
|
if (!editing) {
|
|
@@ -190,24 +166,31 @@ export const FormCellEditor = ({
|
|
|
190
166
|
<Popover.Root open={editing} onOpenChange={handleOpenChange}>
|
|
191
167
|
<Popover.VirtualTrigger virtualRef={anchorRef} />
|
|
192
168
|
<Popover.Portal>
|
|
193
|
-
<Popover.Content tabIndex={-1} classNames='
|
|
169
|
+
<Popover.Content tabIndex={-1} classNames='dx-card-popover-width dx-density-fine'>
|
|
194
170
|
<Popover.Arrow />
|
|
195
171
|
<Popover.Viewport>
|
|
196
|
-
<Form
|
|
172
|
+
<Form.Root
|
|
173
|
+
{...formProps}
|
|
197
174
|
autoFocus
|
|
175
|
+
schema={narrowedSchema}
|
|
198
176
|
values={formValues}
|
|
199
|
-
|
|
177
|
+
onValuesChanged={handleValuesChanged}
|
|
178
|
+
projection={model?.projection}
|
|
179
|
+
createInitialValuePath={fieldProjection.field.referencePath}
|
|
180
|
+
createOptionIcon='ph--plus--regular'
|
|
181
|
+
createOptionLabel={createOptionLabel}
|
|
182
|
+
db={model?.db}
|
|
183
|
+
getOptions={getOptions}
|
|
184
|
+
onCreate={handleCreate}
|
|
200
185
|
onSave={handleSave}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
})}
|
|
210
|
-
/>
|
|
186
|
+
>
|
|
187
|
+
<Form.Viewport>
|
|
188
|
+
<Form.Content>
|
|
189
|
+
<Form.FieldSet />
|
|
190
|
+
<Form.Actions />
|
|
191
|
+
</Form.Content>
|
|
192
|
+
</Form.Viewport>
|
|
193
|
+
</Form.Root>
|
|
211
194
|
</Popover.Viewport>
|
|
212
195
|
</Popover.Content>
|
|
213
196
|
</Popover.Portal>
|
|
@@ -6,32 +6,33 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import {
|
|
9
|
+
import { View } from '@dxos/echo';
|
|
10
|
+
import { random } from '@dxos/random';
|
|
10
11
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
12
12
|
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
13
|
-
import {
|
|
13
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
14
|
+
import { ViewModel } from '@dxos/schema';
|
|
14
15
|
import { Task } from '@dxos/types';
|
|
15
16
|
|
|
16
17
|
import { useTestTableModel } from '../../testing';
|
|
17
18
|
import { translations } from '../../translations';
|
|
18
19
|
import { Table } from '../../types';
|
|
19
|
-
|
|
20
20
|
import { TableCellEditor } from './TableCellEditor';
|
|
21
21
|
|
|
22
|
-
type
|
|
22
|
+
type DefaultStoryProps = {
|
|
23
23
|
editing: GridEditing;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// TODO(burdon): Broken layout.
|
|
27
|
+
const DefaultStory = ({ editing }: DefaultStoryProps) => {
|
|
28
|
+
const { model, table } = useTestTableModel();
|
|
28
29
|
|
|
29
|
-
if (!model || !
|
|
30
|
+
if (!model || !table) {
|
|
30
31
|
return <div />;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
return (
|
|
34
|
-
<div className='
|
|
35
|
+
<div className='border border-separator' style={{ height: defaultRowSize }}>
|
|
35
36
|
<Grid.Root id='test' editing={editing}>
|
|
36
37
|
<TableCellEditor model={model} schema={Task.Task} />
|
|
37
38
|
</Grid.Root>
|
|
@@ -44,20 +45,22 @@ const meta = {
|
|
|
44
45
|
component: DefaultStory,
|
|
45
46
|
render: DefaultStory,
|
|
46
47
|
decorators: [
|
|
47
|
-
withTheme,
|
|
48
|
+
withTheme(),
|
|
49
|
+
withLayout({ layout: 'column' }),
|
|
48
50
|
withClientProvider({
|
|
49
51
|
types: [View.View, Task.Task, Table.Table],
|
|
50
52
|
createIdentity: true,
|
|
51
53
|
createSpace: true,
|
|
52
|
-
onCreateSpace: async ({
|
|
53
|
-
const { view } = await
|
|
54
|
-
|
|
54
|
+
onCreateSpace: async ({ space }) => {
|
|
55
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: Task.Task.typename });
|
|
56
|
+
const table = Table.make({ view, jsonSchema });
|
|
57
|
+
space.db.add(table);
|
|
55
58
|
Array.from({ length: 10 }).forEach(() => {
|
|
56
59
|
space.db.add(
|
|
57
60
|
Obj.make(Task.Task, {
|
|
58
|
-
title:
|
|
59
|
-
status:
|
|
60
|
-
description:
|
|
61
|
+
title: random.person.fullName(),
|
|
62
|
+
status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
63
|
+
description: random.lorem.sentence(),
|
|
61
64
|
}),
|
|
62
65
|
);
|
|
63
66
|
});
|
|
@@ -65,8 +68,8 @@ const meta = {
|
|
|
65
68
|
}),
|
|
66
69
|
],
|
|
67
70
|
parameters: {
|
|
71
|
+
layout: 'fullscreen',
|
|
68
72
|
translations,
|
|
69
|
-
layout: 'centered',
|
|
70
73
|
},
|
|
71
74
|
} satisfies Meta<typeof DefaultStory>;
|
|
72
75
|
|
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
6
|
import { EditorView } from '@codemirror/view';
|
|
7
|
-
import type * as Schema from 'effect/Schema';
|
|
8
7
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
9
8
|
|
|
10
9
|
import { debounce } from '@dxos/async';
|
|
11
|
-
import type
|
|
12
|
-
import {
|
|
10
|
+
import { type Type } from '@dxos/echo';
|
|
11
|
+
import { Format, TypeEnum } from '@dxos/echo/internal';
|
|
13
12
|
import { invariant } from '@dxos/invariant';
|
|
14
13
|
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
15
14
|
import {
|
|
@@ -26,38 +25,40 @@ import {
|
|
|
26
25
|
import { type FieldProjection } from '@dxos/schema';
|
|
27
26
|
|
|
28
27
|
import { type ModalController, type TableModel } from '../../model';
|
|
29
|
-
|
|
30
28
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
29
|
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
32
30
|
|
|
31
|
+
const documentSlots = {
|
|
32
|
+
scroll: {
|
|
33
|
+
className: '!py-(--dx-grid-cell-editor-padding-block)',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
33
37
|
/**
|
|
34
38
|
* Option to create new object/value.
|
|
35
39
|
*/
|
|
36
|
-
export type QueryResult = Pick<Completion, 'label'> & {
|
|
40
|
+
export type QueryResult = Pick<Completion, 'label'> & {
|
|
41
|
+
data: any;
|
|
42
|
+
};
|
|
37
43
|
|
|
38
|
-
export type TableCellEditorProps = {
|
|
44
|
+
export type TableCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
|
|
45
|
+
schema?: T;
|
|
39
46
|
model?: TableModel;
|
|
40
47
|
modals?: ModalController;
|
|
41
|
-
schema?: Schema.Schema.AnyNoContext;
|
|
42
48
|
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
43
49
|
onCreate?: OnCreateHandler;
|
|
44
50
|
onSave?: () => void;
|
|
45
|
-
client?: Client;
|
|
46
51
|
};
|
|
47
52
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
export const TableValueEditor = ({
|
|
53
|
+
export const TableValueEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
54
|
+
__gridScope,
|
|
55
|
+
schema,
|
|
52
56
|
model,
|
|
53
57
|
modals,
|
|
54
|
-
schema,
|
|
55
58
|
onFocus,
|
|
56
59
|
onSave,
|
|
57
60
|
onCreate,
|
|
58
|
-
|
|
59
|
-
__gridScope,
|
|
60
|
-
}: GridScopedProps<TableCellEditorProps>) => {
|
|
61
|
+
}: GridScopedProps<TableCellEditorProps<T>>) => {
|
|
61
62
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
62
63
|
|
|
63
64
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
@@ -66,7 +67,7 @@ export const TableValueEditor = ({
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
const { col } = parseCellIndex(editing.index);
|
|
69
|
-
const field = model.projection.
|
|
70
|
+
const field = model.projection.getFields()[col];
|
|
70
71
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
71
72
|
invariant(fieldProjection);
|
|
72
73
|
return fieldProjection;
|
|
@@ -74,36 +75,33 @@ export const TableValueEditor = ({
|
|
|
74
75
|
|
|
75
76
|
if (
|
|
76
77
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
77
|
-
fieldProjection?.props.format ===
|
|
78
|
-
fieldProjection?.props.format ===
|
|
79
|
-
// TODO(thure): Support `
|
|
78
|
+
fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
|
|
79
|
+
fieldProjection?.props.format === Format.TypeFormat.Ref
|
|
80
|
+
// TODO(thure): Support `Format.TypeFormat.MultiSelect`
|
|
80
81
|
) {
|
|
81
82
|
return (
|
|
82
|
-
<FormCellEditor
|
|
83
|
-
fieldProjection={fieldProjection}
|
|
84
|
-
model={model}
|
|
85
|
-
schema={schema}
|
|
83
|
+
<FormCellEditor<T>
|
|
86
84
|
__gridScope={__gridScope}
|
|
85
|
+
schema={schema}
|
|
86
|
+
model={model}
|
|
87
|
+
fieldProjection={fieldProjection}
|
|
88
|
+
modals={modals}
|
|
87
89
|
onSave={onSave}
|
|
88
90
|
onCreate={onCreate}
|
|
89
|
-
client={client}
|
|
90
|
-
modals={modals}
|
|
91
91
|
/>
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
// For all other types, use the existing cell editor
|
|
95
|
+
// For all other types, use the existing cell editor.
|
|
96
96
|
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
100
|
-
|
|
101
99
|
export const TableCellEditor = ({
|
|
100
|
+
__gridScope,
|
|
102
101
|
model,
|
|
103
102
|
modals,
|
|
104
103
|
onFocus,
|
|
105
104
|
onSave,
|
|
106
|
-
__gridScope,
|
|
107
105
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
108
106
|
const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
109
107
|
const suppressNextBlur = useRef(false);
|
|
@@ -116,12 +114,13 @@ export const TableCellEditor = ({
|
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
const { col } = parseCellIndex(editing.index);
|
|
119
|
-
const field = model.projection.
|
|
117
|
+
const field = model.projection.getFields()[col];
|
|
120
118
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
121
119
|
invariant(fieldProjection);
|
|
122
120
|
return fieldProjection;
|
|
123
121
|
}, [model, editing]);
|
|
124
122
|
|
|
123
|
+
// TOOD(burdon): Attach to event handler?
|
|
125
124
|
const handleEnter = useCallback(
|
|
126
125
|
async (value: any) => {
|
|
127
126
|
if (!model || !editing) {
|
|
@@ -138,7 +137,7 @@ export const TableCellEditor = ({
|
|
|
138
137
|
setEditing(null);
|
|
139
138
|
onSave?.();
|
|
140
139
|
} else {
|
|
141
|
-
setValidationError(
|
|
140
|
+
setValidationError(validationResult.error);
|
|
142
141
|
setValidationVariant('error');
|
|
143
142
|
}
|
|
144
143
|
},
|
|
@@ -250,7 +249,12 @@ export const TableCellEditor = ({
|
|
|
250
249
|
return (
|
|
251
250
|
<>
|
|
252
251
|
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
253
|
-
<GridCellEditor
|
|
252
|
+
<GridCellEditor
|
|
253
|
+
extensions={extensions}
|
|
254
|
+
getCellContent={getCellContent}
|
|
255
|
+
onBlur={handleBlur}
|
|
256
|
+
slots={documentSlots}
|
|
257
|
+
/>
|
|
254
258
|
</>
|
|
255
259
|
);
|
|
256
260
|
};
|
package/src/components/index.ts
CHANGED
|
@@ -2,33 +2,59 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
5
6
|
import * as Schema from 'effect/Schema';
|
|
6
7
|
import { useEffect } from 'react';
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { Format, Type } from '@dxos/echo';
|
|
10
|
+
import { TypeEnum } from '@dxos/echo/internal';
|
|
11
|
+
import { setValue } from '@dxos/effect';
|
|
12
|
+
import { random } from '@dxos/random';
|
|
11
13
|
import { type ProjectionModel } from '@dxos/schema';
|
|
12
14
|
|
|
13
|
-
export const TestSchema =
|
|
14
|
-
id: ObjectId,
|
|
15
|
+
export const TestSchema = Schema.Struct({
|
|
15
16
|
name: Schema.optional(Schema.String),
|
|
16
17
|
age: Schema.optional(Schema.Number),
|
|
17
18
|
active: Schema.optional(Schema.Boolean),
|
|
18
19
|
netWorth: Schema.optional(Schema.Number),
|
|
19
|
-
})
|
|
20
|
+
}).pipe(
|
|
21
|
+
Type.object({
|
|
22
|
+
typename: 'com.example.type.test',
|
|
23
|
+
version: '0.1.0',
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
20
26
|
|
|
21
|
-
export
|
|
22
|
-
|
|
27
|
+
export type TestItem = {
|
|
28
|
+
name: string;
|
|
29
|
+
age: number;
|
|
30
|
+
active: boolean;
|
|
31
|
+
netWorth: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates an Atom containing test items data.
|
|
36
|
+
*/
|
|
37
|
+
export const createItemsAtom = (n: number) => {
|
|
38
|
+
return Atom.make<{ data: TestItem[] }>({
|
|
23
39
|
data: Array.from({ length: n }, () => ({
|
|
24
|
-
name:
|
|
25
|
-
age:
|
|
26
|
-
active:
|
|
27
|
-
netWorth:
|
|
40
|
+
name: random.person.fullName(),
|
|
41
|
+
age: random.number.int({ min: 20, max: 70 }),
|
|
42
|
+
active: random.datatype.boolean(),
|
|
43
|
+
netWorth: random.number.int(),
|
|
28
44
|
})),
|
|
29
|
-
});
|
|
45
|
+
}).pipe(Atom.keepAlive);
|
|
46
|
+
};
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Creates plain test items (non-reactive).
|
|
50
|
+
*/
|
|
51
|
+
export const createItems = (n: number): TestItem[] => {
|
|
52
|
+
return Array.from({ length: n }, () => ({
|
|
53
|
+
name: random.person.fullName(),
|
|
54
|
+
age: random.number.int({ min: 20, max: 70 }),
|
|
55
|
+
active: random.datatype.boolean(),
|
|
56
|
+
netWorth: random.number.int(),
|
|
57
|
+
}));
|
|
32
58
|
};
|
|
33
59
|
|
|
34
60
|
export type SimulatorProps = {
|
|
@@ -59,7 +85,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
|
|
|
59
85
|
|
|
60
86
|
const i = setInterval(() => {
|
|
61
87
|
const rowIdx = Math.floor(Math.random() * items.length);
|
|
62
|
-
const fields = projection.
|
|
88
|
+
const fields = projection.getFields() ?? [];
|
|
63
89
|
const columnIdx = Math.floor(Math.random() * fields.length);
|
|
64
90
|
const field = fields[columnIdx];
|
|
65
91
|
const item = items[rowIdx];
|
|
@@ -88,7 +114,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
|
|
|
88
114
|
|
|
89
115
|
if (format) {
|
|
90
116
|
switch (format) {
|
|
91
|
-
case
|
|
117
|
+
case Format.TypeFormat.Currency: {
|
|
92
118
|
item[path] = Math.floor(Math.random() * 1000);
|
|
93
119
|
break;
|
|
94
120
|
}
|
|
@@ -98,5 +124,5 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
|
|
|
98
124
|
}, updateInterval);
|
|
99
125
|
|
|
100
126
|
return () => clearInterval(i);
|
|
101
|
-
}, [items, projection.
|
|
127
|
+
}, [items, projection.getFields(), updateInterval]);
|
|
102
128
|
};
|