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