@dxos/react-ui-table 0.8.4-main.f5c0578 → 0.8.4-main.fcfe5033a5
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-ZAOSIG3L.mjs → chunk-F7Y374KL.mjs} +739 -802
- package/dist/lib/browser/chunk-F7Y374KL.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1208 -1158
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +34 -43
- 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-ZOFFI3JJ.mjs → chunk-CCJ4LOVM.mjs} +739 -801
- package/dist/lib/node-esm/chunk-CCJ4LOVM.mjs.map +7 -0
- 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/index.mjs +1208 -1158
- 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 +34 -43
- 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 +35 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +106 -5
- 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 +21 -19
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +108 -5
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +17 -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/FormCellEditor.d.ts +13 -9
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +7 -14
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +38 -5
- 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/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +23 -16
- 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 +6 -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 -8
- 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 +110 -29
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +4 -2
- 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 -34
- 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 -12
- 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 -1
- 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 +1 -1
- package/dist/types/src/util/schema.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.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +58 -57
- package/src/components/Table/ColumnActionsMenu.tsx +15 -11
- package/src/components/Table/ColumnSettings.tsx +8 -9
- package/src/components/Table/DynamicTable.stories.tsx +36 -34
- package/src/components/Table/DynamicTable.tsx +33 -36
- package/src/components/Table/Relations.stories.tsx +180 -59
- package/src/components/Table/RowActionsMenu.tsx +24 -15
- package/src/components/Table/Table.stories.tsx +152 -129
- package/src/components/Table/Table.tsx +46 -391
- package/src/components/Table/TableContent.tsx +421 -0
- package/src/components/Table/TableToolbar.tsx +111 -0
- package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -5
- package/src/components/TableCellEditor/FormCellEditor.tsx +134 -40
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +35 -72
- package/src/components/TableCellEditor/TableCellEditor.tsx +80 -224
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/index.ts +0 -1
- package/src/components/testing.ts +44 -18
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +14 -14
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +36 -34
- package/src/model/modal-controller.ts +23 -40
- package/src/model/selection-model.ts +75 -44
- package/src/model/table-model.test.ts +91 -56
- package/src/model/table-model.ts +447 -172
- package/src/model/table-presentation.test.ts +68 -58
- package/src/model/table-presentation.ts +97 -73
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +92 -46
- package/src/testing/useTestTableModel.ts +50 -40
- 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 +45 -32
- package/src/util/index.ts +2 -1
- package/src/util/query-ast.ts +22 -0
- package/src/util/schema.ts +4 -4
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +3 -3
- package/src/util/table-controls.ts +2 -2
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ZOFFI3JJ.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/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/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 -53
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -8
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -12
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/components/TableToolbar/TableToolbar.tsx +0 -102
- package/src/components/TableToolbar/index.ts +0 -5
- package/src/model/table-sorting.ts +0 -216
- package/src/types/schema.ts +0 -16
- package/src/util/table.ts +0 -53
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
-
import {
|
|
7
|
-
import React, { useCallback,
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
7
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { debounce } from '@dxos/async';
|
|
10
|
+
import { type Type } from '@dxos/echo';
|
|
11
|
+
import { Format, TypeEnum } from '@dxos/echo/internal';
|
|
10
12
|
import { invariant } from '@dxos/invariant';
|
|
11
13
|
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
12
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
13
|
-
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
14
14
|
import {
|
|
15
15
|
type EditorBlurHandler,
|
|
16
16
|
type EditorKeyEvent,
|
|
@@ -18,51 +18,41 @@ import {
|
|
|
18
18
|
GridCellEditor,
|
|
19
19
|
type GridCellEditorProps,
|
|
20
20
|
type GridScopedProps,
|
|
21
|
-
cellQuery,
|
|
22
21
|
editorKeys,
|
|
23
22
|
parseCellIndex,
|
|
24
23
|
useGridContext,
|
|
25
24
|
} from '@dxos/react-ui-grid';
|
|
26
|
-
import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
|
|
27
25
|
import { type FieldProjection } from '@dxos/schema';
|
|
28
26
|
|
|
29
27
|
import { type ModalController, type TableModel } from '../../model';
|
|
30
|
-
|
|
31
28
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
32
|
-
import {
|
|
33
|
-
import { FormCellEditor } from './FormCellEditor';
|
|
34
|
-
|
|
35
|
-
const newValue = Symbol.for('newValue');
|
|
29
|
+
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
36
30
|
|
|
37
31
|
/**
|
|
38
32
|
* Option to create new object/value.
|
|
39
33
|
*/
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
34
|
+
export type QueryResult = Pick<Completion, 'label'> & {
|
|
35
|
+
data: any;
|
|
36
|
+
};
|
|
45
37
|
|
|
46
|
-
export type TableCellEditorProps = {
|
|
38
|
+
export type TableCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
|
|
39
|
+
schema?: T;
|
|
47
40
|
model?: TableModel;
|
|
48
41
|
modals?: ModalController;
|
|
49
|
-
schema?: Schema.AnyNoContext;
|
|
50
|
-
onEnter?: (cell: DxGridPosition) => void;
|
|
51
42
|
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
43
|
+
onCreate?: OnCreateHandler;
|
|
52
44
|
onSave?: () => void;
|
|
53
|
-
onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
|
|
54
45
|
};
|
|
55
46
|
|
|
56
|
-
export const TableValueEditor = ({
|
|
47
|
+
export const TableValueEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
48
|
+
__gridScope,
|
|
49
|
+
schema,
|
|
57
50
|
model,
|
|
58
51
|
modals,
|
|
59
|
-
schema,
|
|
60
|
-
onEnter,
|
|
61
52
|
onFocus,
|
|
62
53
|
onSave,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}: GridScopedProps<TableCellEditorProps>) => {
|
|
54
|
+
onCreate,
|
|
55
|
+
}: GridScopedProps<TableCellEditorProps<T>>) => {
|
|
66
56
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
67
57
|
|
|
68
58
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
@@ -71,7 +61,7 @@ export const TableValueEditor = ({
|
|
|
71
61
|
}
|
|
72
62
|
|
|
73
63
|
const { col } = parseCellIndex(editing.index);
|
|
74
|
-
const field = model.projection.
|
|
64
|
+
const field = model.projection.getFields()[col];
|
|
75
65
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
76
66
|
invariant(fieldProjection);
|
|
77
67
|
return fieldProjection;
|
|
@@ -79,48 +69,36 @@ export const TableValueEditor = ({
|
|
|
79
69
|
|
|
80
70
|
if (
|
|
81
71
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
82
|
-
fieldProjection?.props.format ===
|
|
83
|
-
|
|
72
|
+
fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
|
|
73
|
+
fieldProjection?.props.format === Format.TypeFormat.Ref
|
|
74
|
+
// TODO(thure): Support `Format.TypeFormat.MultiSelect`
|
|
84
75
|
) {
|
|
85
76
|
return (
|
|
86
|
-
<FormCellEditor
|
|
87
|
-
fieldProjection={fieldProjection}
|
|
88
|
-
model={model}
|
|
89
|
-
schema={schema}
|
|
77
|
+
<FormCellEditor<T>
|
|
90
78
|
__gridScope={__gridScope}
|
|
79
|
+
schema={schema}
|
|
80
|
+
model={model}
|
|
81
|
+
fieldProjection={fieldProjection}
|
|
82
|
+
modals={modals}
|
|
91
83
|
onSave={onSave}
|
|
84
|
+
onCreate={onCreate}
|
|
92
85
|
/>
|
|
93
86
|
);
|
|
94
87
|
}
|
|
95
88
|
|
|
96
|
-
// For all other types, use the existing cell editor
|
|
97
|
-
return
|
|
98
|
-
<TableCellEditor
|
|
99
|
-
model={model}
|
|
100
|
-
modals={modals}
|
|
101
|
-
onEnter={onEnter}
|
|
102
|
-
onFocus={onFocus}
|
|
103
|
-
onQuery={onQuery}
|
|
104
|
-
onSave={onSave}
|
|
105
|
-
__gridScope={__gridScope}
|
|
106
|
-
/>
|
|
107
|
-
);
|
|
89
|
+
// For all other types, use the existing cell editor.
|
|
90
|
+
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
108
91
|
};
|
|
109
92
|
|
|
110
|
-
const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
111
|
-
|
|
112
93
|
export const TableCellEditor = ({
|
|
94
|
+
__gridScope,
|
|
113
95
|
model,
|
|
114
96
|
modals,
|
|
115
|
-
onEnter,
|
|
116
97
|
onFocus,
|
|
117
|
-
onQuery,
|
|
118
98
|
onSave,
|
|
119
|
-
__gridScope,
|
|
120
99
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
121
|
-
const {
|
|
100
|
+
const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
122
101
|
const suppressNextBlur = useRef(false);
|
|
123
|
-
const { themeMode } = useThemeContext();
|
|
124
102
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
125
103
|
const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
126
104
|
|
|
@@ -130,44 +108,13 @@ export const TableCellEditor = ({
|
|
|
130
108
|
}
|
|
131
109
|
|
|
132
110
|
const { col } = parseCellIndex(editing.index);
|
|
133
|
-
const field = model.projection.
|
|
111
|
+
const field = model.projection.getFields()[col];
|
|
134
112
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
135
113
|
invariant(fieldProjection);
|
|
136
114
|
return fieldProjection;
|
|
137
115
|
}, [model, editing]);
|
|
138
116
|
|
|
139
|
-
|
|
140
|
-
// Check for existing validation errors when editing starts (for draft rows).
|
|
141
|
-
if (!model || !editing || !fieldProjection) {
|
|
142
|
-
setValidationError(null);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const cell = parseCellIndex(editing.index);
|
|
147
|
-
const { row, col } = cell;
|
|
148
|
-
|
|
149
|
-
if (model.isDraftCell(cell)) {
|
|
150
|
-
const field = model.projection.fields[col];
|
|
151
|
-
const hasValidationError = model.hasDraftRowValidationError(row, field.path);
|
|
152
|
-
|
|
153
|
-
if (hasValidationError) {
|
|
154
|
-
const draftRows = model.draftRows.value;
|
|
155
|
-
if (row >= 0 && row < draftRows.length) {
|
|
156
|
-
const draftRow = draftRows[row];
|
|
157
|
-
const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
|
|
158
|
-
if (validationError) {
|
|
159
|
-
setValidationError(validationError.message);
|
|
160
|
-
setValidationVariant('warning');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
setValidationError(null);
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
setValidationError(null);
|
|
168
|
-
}
|
|
169
|
-
}, [model, editing, fieldProjection]);
|
|
170
|
-
|
|
117
|
+
// TOOD(burdon): Attach to event handler?
|
|
171
118
|
const handleEnter = useCallback(
|
|
172
119
|
async (value: any) => {
|
|
173
120
|
if (!model || !editing) {
|
|
@@ -180,7 +127,6 @@ export const TableCellEditor = ({
|
|
|
180
127
|
if (validationResult.valid) {
|
|
181
128
|
setValidationError(null);
|
|
182
129
|
model.setCellData(cell, value);
|
|
183
|
-
onEnter?.(cell);
|
|
184
130
|
onFocus?.();
|
|
185
131
|
setEditing(null);
|
|
186
132
|
onSave?.();
|
|
@@ -189,19 +135,26 @@ export const TableCellEditor = ({
|
|
|
189
135
|
setValidationVariant('error');
|
|
190
136
|
}
|
|
191
137
|
},
|
|
192
|
-
[model, editing,
|
|
138
|
+
[model, editing, onFocus, setEditing],
|
|
193
139
|
);
|
|
194
140
|
|
|
195
141
|
const handleBlur = useCallback<EditorBlurHandler>(
|
|
196
142
|
async (value) => {
|
|
197
|
-
if (!model || !editing) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
143
|
if (suppressNextBlur.current) {
|
|
201
144
|
suppressNextBlur.current = false;
|
|
145
|
+
} else if (model && editing) {
|
|
146
|
+
// Save silently if validation passes
|
|
147
|
+
const cell = parseCellIndex(editing.index);
|
|
148
|
+
if (value !== undefined) {
|
|
149
|
+
const result = await model.validateCellData(cell, value);
|
|
150
|
+
if (result.valid) {
|
|
151
|
+
setValidationError(null);
|
|
152
|
+
model.setCellData(cell, value);
|
|
153
|
+
setEditing(null);
|
|
154
|
+
onSave?.();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
202
157
|
}
|
|
203
|
-
|
|
204
|
-
// Don't save on blur - let handleClose handle validation and saving
|
|
205
158
|
},
|
|
206
159
|
[model, editing],
|
|
207
160
|
);
|
|
@@ -213,17 +166,16 @@ export const TableCellEditor = ({
|
|
|
213
166
|
}
|
|
214
167
|
|
|
215
168
|
const cell = parseCellIndex(editing.index);
|
|
216
|
-
|
|
217
169
|
if (value !== undefined) {
|
|
218
170
|
// Pre-commit validation check.
|
|
219
171
|
const result = await model.validateCellData(cell, value);
|
|
220
172
|
|
|
221
173
|
if (result.valid) {
|
|
174
|
+
suppressNextBlur.current = true;
|
|
222
175
|
setValidationError(null);
|
|
223
176
|
model.setCellData(cell, value);
|
|
224
177
|
setEditing(null);
|
|
225
178
|
onSave?.();
|
|
226
|
-
onEnter?.(cell);
|
|
227
179
|
if (event && onFocus) {
|
|
228
180
|
onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
|
|
229
181
|
}
|
|
@@ -232,6 +184,7 @@ export const TableCellEditor = ({
|
|
|
232
184
|
setValidationVariant('error');
|
|
233
185
|
}
|
|
234
186
|
} else {
|
|
187
|
+
suppressNextBlur.current = true;
|
|
235
188
|
setValidationError(null);
|
|
236
189
|
setEditing(null);
|
|
237
190
|
onSave?.();
|
|
@@ -240,154 +193,48 @@ export const TableCellEditor = ({
|
|
|
240
193
|
}
|
|
241
194
|
}
|
|
242
195
|
},
|
|
243
|
-
[model, editing, onFocus,
|
|
196
|
+
[model, editing, onFocus, fieldProjection, setEditing, onSave],
|
|
244
197
|
);
|
|
245
198
|
|
|
246
|
-
const
|
|
199
|
+
const extensions = useMemo(() => {
|
|
247
200
|
if (!fieldProjection) {
|
|
248
201
|
return [];
|
|
249
202
|
}
|
|
250
203
|
|
|
251
|
-
const
|
|
204
|
+
const extensions = [
|
|
252
205
|
editorKeys({
|
|
253
206
|
onClose: handleClose,
|
|
254
207
|
...(editing?.initialContent && { onNav: handleClose }),
|
|
255
208
|
}),
|
|
256
209
|
];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
extension.push(
|
|
270
|
-
tagPicker({
|
|
271
|
-
mode,
|
|
272
|
-
keymap: false,
|
|
273
|
-
onSearch: (text, selectedIds) => {
|
|
274
|
-
return options
|
|
275
|
-
.filter(
|
|
276
|
-
(option) =>
|
|
277
|
-
selectedIds.indexOf(option.id) === -1 &&
|
|
278
|
-
(text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase())),
|
|
279
|
-
)
|
|
280
|
-
.map((option) => ({
|
|
281
|
-
id: option.id,
|
|
282
|
-
label: option.title,
|
|
283
|
-
hue: option.color as any,
|
|
284
|
-
}));
|
|
285
|
-
},
|
|
286
|
-
onUpdate: (ids) => {
|
|
287
|
-
if (model && editing) {
|
|
288
|
-
if (ids.length === 0) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (mode === 'single-select') {
|
|
292
|
-
void handleEnter(ids[0]);
|
|
210
|
+
// Add validation extension to handle content changes.
|
|
211
|
+
if (model && editing) {
|
|
212
|
+
extensions.push(
|
|
213
|
+
EditorView.updateListener.of(
|
|
214
|
+
debounce((update) => {
|
|
215
|
+
const content = update.state.doc.toString();
|
|
216
|
+
const cell = parseCellIndex(editing.index);
|
|
217
|
+
|
|
218
|
+
// Perform validation on content change.
|
|
219
|
+
void model.validateCellData(cell, content).then((result) => {
|
|
220
|
+
if (result.valid) {
|
|
221
|
+
setValidationError(null);
|
|
293
222
|
} else {
|
|
294
|
-
|
|
223
|
+
setValidationError(result.error);
|
|
224
|
+
setValidationVariant('error');
|
|
295
225
|
}
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
|
|
226
|
+
});
|
|
227
|
+
}, 10),
|
|
228
|
+
),
|
|
299
229
|
);
|
|
300
230
|
}
|
|
301
231
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
case FormatEnum.Ref: {
|
|
305
|
-
extension.push([
|
|
306
|
-
completion({
|
|
307
|
-
onQuery: (text) => onQuery(fieldProjection, text),
|
|
308
|
-
onMatch: (data) => {
|
|
309
|
-
if (model && editing && modals) {
|
|
310
|
-
if (isCreateOption(data)) {
|
|
311
|
-
const { field, props } = fieldProjection;
|
|
312
|
-
if (props.referenceSchema) {
|
|
313
|
-
suppressNextBlur.current = true;
|
|
314
|
-
modals.openCreateRef(
|
|
315
|
-
props.referenceSchema,
|
|
316
|
-
document.querySelector(cellQuery(editing.index, gridId)),
|
|
317
|
-
{
|
|
318
|
-
[field.referencePath!]: data.text,
|
|
319
|
-
},
|
|
320
|
-
(data) => {
|
|
321
|
-
void handleEnter(data);
|
|
322
|
-
},
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
void handleEnter(data);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
}),
|
|
331
|
-
]);
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return extension;
|
|
338
|
-
}, [model, modals, editing, fieldProjection, handleClose, themeMode]);
|
|
232
|
+
return extensions;
|
|
233
|
+
}, [model, modals, editing, fieldProjection, handleClose]);
|
|
339
234
|
|
|
340
235
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
341
236
|
if (model && editing) {
|
|
342
237
|
const cell = parseCellIndex(editing.index);
|
|
343
|
-
const { col } = cell;
|
|
344
|
-
const field = model.projection.fields[col];
|
|
345
|
-
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
349
|
-
fieldProjection?.props.format === FormatEnum.MultiSelect
|
|
350
|
-
) {
|
|
351
|
-
const value = model.getCellData(cell);
|
|
352
|
-
|
|
353
|
-
if (value !== undefined) {
|
|
354
|
-
const options = fieldProjection.props.options || [];
|
|
355
|
-
|
|
356
|
-
if (fieldProjection.props.format === FormatEnum.MultiSelect) {
|
|
357
|
-
const tagItems = value
|
|
358
|
-
.split(',')
|
|
359
|
-
.map((id: string) => {
|
|
360
|
-
const option = options.find((o) => o.id === id);
|
|
361
|
-
if (option) {
|
|
362
|
-
return {
|
|
363
|
-
id,
|
|
364
|
-
label: option.title,
|
|
365
|
-
hue: option.color as any,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
return undefined;
|
|
369
|
-
})
|
|
370
|
-
.filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
371
|
-
|
|
372
|
-
return createLinks(tagItems);
|
|
373
|
-
} else {
|
|
374
|
-
const option = options.find((o) => o.id === value);
|
|
375
|
-
|
|
376
|
-
if (option) {
|
|
377
|
-
const tagItem = {
|
|
378
|
-
id: value,
|
|
379
|
-
label: option.title,
|
|
380
|
-
hue: option.color as any,
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
return createLinks([tagItem]);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return '';
|
|
389
|
-
}
|
|
390
|
-
|
|
391
238
|
const value = model.getCellData(cell);
|
|
392
239
|
return value !== undefined ? String(value) : '';
|
|
393
240
|
}
|
|
@@ -396,7 +243,16 @@ export const TableCellEditor = ({
|
|
|
396
243
|
return (
|
|
397
244
|
<>
|
|
398
245
|
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
399
|
-
<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
|
+
/>
|
|
400
256
|
</>
|
|
401
257
|
);
|
|
402
258
|
};
|
package/src/components/index.ts
CHANGED
|
@@ -2,33 +2,59 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
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
|
};
|
package/src/hooks/index.ts
CHANGED
package/src/hooks/useAddRow.ts
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Schema } from 'effect';
|
|
6
5
|
import { useCallback } from 'react';
|
|
7
6
|
|
|
8
|
-
import { type
|
|
9
|
-
import { Obj } from '@dxos/echo';
|
|
7
|
+
import { type Database, Obj, type Type } from '@dxos/echo';
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
import { type InsertRowResult } from '../model';
|
|
10
|
+
|
|
11
|
+
export type UseAddRowProps = {
|
|
12
|
+
db?: Database.Database;
|
|
13
|
+
schema?: Type.AnyObj;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -18,19 +18,19 @@ export type UseAddRowParams = {
|
|
|
18
18
|
* Returns a callback that attempts to create an object and returns boolean success/failure.
|
|
19
19
|
* Can accept optional data to create objects with specific content (used for committing draft rows).
|
|
20
20
|
*/
|
|
21
|
-
export const useAddRow = ({
|
|
21
|
+
export const useAddRow = ({ db, schema }: UseAddRowProps) => {
|
|
22
22
|
return useCallback(
|
|
23
|
-
(data?: any) => {
|
|
24
|
-
if (
|
|
23
|
+
(data?: any): InsertRowResult => {
|
|
24
|
+
if (db && schema) {
|
|
25
25
|
try {
|
|
26
|
-
|
|
27
|
-
return
|
|
26
|
+
db.add(Obj.make(schema, data ?? {}));
|
|
27
|
+
return 'final';
|
|
28
28
|
} catch (error) {
|
|
29
|
-
return
|
|
29
|
+
return 'draft';
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
return
|
|
32
|
+
return 'final';
|
|
33
33
|
},
|
|
34
|
-
[
|
|
34
|
+
[db, schema],
|
|
35
35
|
);
|
|
36
36
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Registry } from '@effect-atom/atom-react';
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { JsonSchema, Type } from '@dxos/echo';
|
|
9
|
+
import { useAsyncEffect } from '@dxos/react-ui';
|
|
10
|
+
import { ProjectionModel, createEchoChangeCallback } from '@dxos/schema';
|
|
11
|
+
|
|
12
|
+
import { type Table } from '../types';
|
|
13
|
+
|
|
14
|
+
export const useProjectionModel = <S extends Type.AnyEntity>(
|
|
15
|
+
schema: S | undefined,
|
|
16
|
+
table: Table.Table | undefined,
|
|
17
|
+
registry: Registry.Registry,
|
|
18
|
+
) => {
|
|
19
|
+
const [projection, setProjection] = useState<ProjectionModel | undefined>();
|
|
20
|
+
|
|
21
|
+
useAsyncEffect(async () => {
|
|
22
|
+
if (schema && table) {
|
|
23
|
+
const view = await table.view.load();
|
|
24
|
+
// For mutable schemas (EchoSchema), use the live jsonSchema reference for reactivity.
|
|
25
|
+
// For immutable schemas, create a snapshot.
|
|
26
|
+
const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
|
|
27
|
+
|
|
28
|
+
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
29
|
+
// Pass schema only when mutable to allow schema mutations.
|
|
30
|
+
const change = createEchoChangeCallback(view, Type.isMutable(schema) ? schema : undefined);
|
|
31
|
+
|
|
32
|
+
const projection = new ProjectionModel({ registry, view, baseSchema: jsonSchema, change });
|
|
33
|
+
projection.normalizeView();
|
|
34
|
+
setProjection(projection);
|
|
35
|
+
}
|
|
36
|
+
}, [schema, table, registry]);
|
|
37
|
+
|
|
38
|
+
return projection;
|
|
39
|
+
};
|