@dxos/react-ui-table 0.8.4-main.c1de068 → 0.8.4-main.c4373fc
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-4K7JPALT.mjs +66 -0
- package/dist/lib/browser/chunk-4K7JPALT.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5XLSHLOG.mjs → chunk-OZTXLZGM.mjs} +544 -491
- package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +590 -675
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +13 -8
- 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-MK6OVUF6.mjs +67 -0
- package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-Y66YLSTC.mjs → chunk-S56Z4V4X.mjs} +544 -491
- package/dist/lib/node-esm/chunk-S56Z4V4X.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +590 -675
- 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 +13 -8
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +51 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +108 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +8 -4
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +42 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -6
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -17
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -3
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +3 -2
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +2 -2
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +0 -7
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +9 -7
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +2 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +2 -3
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +3 -22
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +25 -5
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +4 -5
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +55 -50
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +1 -1
- package/src/components/Table/DynamicTable.stories.tsx +15 -11
- package/src/components/Table/DynamicTable.tsx +8 -10
- package/src/components/Table/Relations.stories.tsx +248 -0
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +66 -54
- package/src/components/Table/Table.tsx +147 -117
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -64
- package/src/components/TableCellEditor/TableCellEditor.tsx +74 -224
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +2 -2
- package/src/hooks/useAddRow.ts +7 -5
- package/src/hooks/useTableModel.ts +2 -2
- package/src/model/modal-controller.ts +1 -29
- package/src/model/selection-model.ts +3 -2
- package/src/model/table-model.test.ts +19 -10
- package/src/model/table-model.ts +37 -25
- package/src/model/table-presentation.test.ts +23 -21
- package/src/model/table-presentation.ts +74 -33
- package/src/model/table-sorting.ts +16 -7
- package/src/playwright/smoke.spec.ts +129 -37
- package/src/testing/useTestTableModel.ts +11 -7
- package/src/translations.ts +2 -2
- package/src/types/index.ts +1 -1
- package/src/types/schema.ts +63 -6
- package/src/util/dynamic-table.ts +13 -13
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +4 -4
- package/src/util/table-buttons.ts +4 -3
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +0 -7
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
- package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -11
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/Table/relations.stories.tsx +0 -116
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/util/table.ts +0 -49
|
@@ -3,64 +3,59 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
7
|
+
import type * as Schema from 'effect/Schema';
|
|
8
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
+
import { debounce } from '@dxos/async';
|
|
11
|
+
import type { Client } from '@dxos/client';
|
|
12
|
+
import { FormatEnum, TypeEnum } from '@dxos/echo/internal';
|
|
10
13
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { type
|
|
12
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
13
|
-
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
14
|
+
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
14
15
|
import {
|
|
15
|
-
|
|
16
|
-
editorKeys,
|
|
17
|
-
parseCellIndex,
|
|
18
|
-
useGridContext,
|
|
19
|
-
type DxGridPlanePosition,
|
|
16
|
+
type EditorBlurHandler,
|
|
20
17
|
type EditorKeyEvent,
|
|
21
18
|
type EditorKeyOrBlurHandler,
|
|
22
|
-
type EditorBlurHandler,
|
|
23
19
|
GridCellEditor,
|
|
24
20
|
type GridCellEditorProps,
|
|
25
21
|
type GridScopedProps,
|
|
22
|
+
editorKeys,
|
|
23
|
+
parseCellIndex,
|
|
24
|
+
useGridContext,
|
|
26
25
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import { tagPicker, createLinks } from '@dxos/react-ui-tag-picker';
|
|
28
26
|
import { type FieldProjection } from '@dxos/schema';
|
|
29
27
|
|
|
30
|
-
import {
|
|
31
|
-
import { FormCellEditor } from './FormCellEditor';
|
|
32
|
-
import { completion } from './extension';
|
|
33
|
-
import { type TableModel, type ModalController } from '../../model';
|
|
28
|
+
import { type ModalController, type TableModel } from '../../model';
|
|
34
29
|
|
|
35
|
-
|
|
30
|
+
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
|
+
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
34
|
* Option to create new object/value.
|
|
39
35
|
*/
|
|
40
|
-
export const createOption = (text: string) => ({ [newValue]: true, text });
|
|
41
|
-
|
|
42
|
-
const isCreateOption = (data: any) => typeof data === 'object' && data[newValue];
|
|
43
|
-
|
|
44
36
|
export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
45
37
|
|
|
46
38
|
export type TableCellEditorProps = {
|
|
47
39
|
model?: TableModel;
|
|
48
40
|
modals?: ModalController;
|
|
49
|
-
schema?: Schema.AnyNoContext;
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
42
|
+
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
43
|
+
onCreate?: OnCreateHandler;
|
|
52
44
|
onSave?: () => void;
|
|
53
|
-
|
|
45
|
+
client?: Client;
|
|
54
46
|
};
|
|
55
47
|
|
|
48
|
+
const adaptValidationMessage = (message: string | null) =>
|
|
49
|
+
message ? (message.endsWith('is missing') ? 'Can’t be blank' : message) : null;
|
|
50
|
+
|
|
56
51
|
export const TableValueEditor = ({
|
|
57
52
|
model,
|
|
58
53
|
modals,
|
|
59
54
|
schema,
|
|
60
|
-
onEnter,
|
|
61
55
|
onFocus,
|
|
62
56
|
onSave,
|
|
63
|
-
|
|
57
|
+
onCreate,
|
|
58
|
+
client,
|
|
64
59
|
__gridScope,
|
|
65
60
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
66
61
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
@@ -79,7 +74,8 @@ export const TableValueEditor = ({
|
|
|
79
74
|
|
|
80
75
|
if (
|
|
81
76
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
82
|
-
fieldProjection?.props.format === FormatEnum.SingleSelect
|
|
77
|
+
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
78
|
+
fieldProjection?.props.format === FormatEnum.Ref
|
|
83
79
|
// TODO(thure): Support `FormatEnum.MultiSelect`
|
|
84
80
|
) {
|
|
85
81
|
return (
|
|
@@ -89,40 +85,30 @@ export const TableValueEditor = ({
|
|
|
89
85
|
schema={schema}
|
|
90
86
|
__gridScope={__gridScope}
|
|
91
87
|
onSave={onSave}
|
|
88
|
+
onCreate={onCreate}
|
|
89
|
+
client={client}
|
|
90
|
+
modals={modals}
|
|
92
91
|
/>
|
|
93
92
|
);
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
// For all other types, use the existing cell editor
|
|
97
|
-
return
|
|
98
|
-
<TableCellEditor
|
|
99
|
-
model={model}
|
|
100
|
-
modals={modals}
|
|
101
|
-
onEnter={onEnter}
|
|
102
|
-
onFocus={onFocus}
|
|
103
|
-
onQuery={onQuery}
|
|
104
|
-
onSave={onSave}
|
|
105
|
-
__gridScope={__gridScope}
|
|
106
|
-
/>
|
|
107
|
-
);
|
|
96
|
+
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
108
97
|
};
|
|
109
98
|
|
|
110
|
-
const editorSlots = {
|
|
99
|
+
const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
111
100
|
|
|
112
101
|
export const TableCellEditor = ({
|
|
113
102
|
model,
|
|
114
103
|
modals,
|
|
115
|
-
onEnter,
|
|
116
104
|
onFocus,
|
|
117
|
-
onQuery,
|
|
118
105
|
onSave,
|
|
119
106
|
__gridScope,
|
|
120
107
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
121
|
-
const {
|
|
108
|
+
const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
122
109
|
const suppressNextBlur = useRef(false);
|
|
123
|
-
const
|
|
124
|
-
const [
|
|
125
|
-
const [_validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
110
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
111
|
+
const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
126
112
|
|
|
127
113
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
128
114
|
if (!model || !editing) {
|
|
@@ -136,38 +122,6 @@ export const TableCellEditor = ({
|
|
|
136
122
|
return fieldProjection;
|
|
137
123
|
}, [model, editing]);
|
|
138
124
|
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
// Check for existing validation errors when editing starts (for draft rows).
|
|
141
|
-
if (!model || !editing || !fieldProjection) {
|
|
142
|
-
setValidationError(null);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const cell = parseCellIndex(editing.index);
|
|
147
|
-
const { row, col } = cell;
|
|
148
|
-
|
|
149
|
-
if (model.isDraftCell(cell)) {
|
|
150
|
-
const field = model.projection.fields[col];
|
|
151
|
-
const hasValidationError = model.hasDraftRowValidationError(row, field.path);
|
|
152
|
-
|
|
153
|
-
if (hasValidationError) {
|
|
154
|
-
const draftRows = model.draftRows.value;
|
|
155
|
-
if (row >= 0 && row < draftRows.length) {
|
|
156
|
-
const draftRow = draftRows[row];
|
|
157
|
-
const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
|
|
158
|
-
if (validationError) {
|
|
159
|
-
setValidationError(validationError.message);
|
|
160
|
-
setValidationVariant('warning');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
setValidationError(null);
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
setValidationError(null);
|
|
168
|
-
}
|
|
169
|
-
}, [model, editing, fieldProjection]);
|
|
170
|
-
|
|
171
125
|
const handleEnter = useCallback(
|
|
172
126
|
async (value: any) => {
|
|
173
127
|
if (!model || !editing) {
|
|
@@ -180,28 +134,34 @@ export const TableCellEditor = ({
|
|
|
180
134
|
if (validationResult.valid) {
|
|
181
135
|
setValidationError(null);
|
|
182
136
|
model.setCellData(cell, value);
|
|
183
|
-
onEnter?.(cell);
|
|
184
137
|
onFocus?.();
|
|
185
138
|
setEditing(null);
|
|
186
139
|
onSave?.();
|
|
187
140
|
} else {
|
|
188
|
-
setValidationError(validationResult.error);
|
|
141
|
+
setValidationError(adaptValidationMessage(validationResult.error));
|
|
189
142
|
setValidationVariant('error');
|
|
190
143
|
}
|
|
191
144
|
},
|
|
192
|
-
[model, editing,
|
|
145
|
+
[model, editing, onFocus, setEditing],
|
|
193
146
|
);
|
|
194
147
|
|
|
195
148
|
const handleBlur = useCallback<EditorBlurHandler>(
|
|
196
149
|
async (value) => {
|
|
197
|
-
if (!model || !editing) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
150
|
if (suppressNextBlur.current) {
|
|
201
151
|
suppressNextBlur.current = false;
|
|
152
|
+
} else if (model && editing) {
|
|
153
|
+
// Save silently if validation passes
|
|
154
|
+
const cell = parseCellIndex(editing.index);
|
|
155
|
+
if (value !== undefined) {
|
|
156
|
+
const result = await model.validateCellData(cell, value);
|
|
157
|
+
if (result.valid) {
|
|
158
|
+
setValidationError(null);
|
|
159
|
+
model.setCellData(cell, value);
|
|
160
|
+
setEditing(null);
|
|
161
|
+
onSave?.();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
202
164
|
}
|
|
203
|
-
|
|
204
|
-
// Don't save on blur - let handleClose handle validation and saving
|
|
205
165
|
},
|
|
206
166
|
[model, editing],
|
|
207
167
|
);
|
|
@@ -213,25 +173,25 @@ export const TableCellEditor = ({
|
|
|
213
173
|
}
|
|
214
174
|
|
|
215
175
|
const cell = parseCellIndex(editing.index);
|
|
216
|
-
|
|
217
176
|
if (value !== undefined) {
|
|
218
177
|
// Pre-commit validation check.
|
|
219
178
|
const result = await model.validateCellData(cell, value);
|
|
220
179
|
|
|
221
180
|
if (result.valid) {
|
|
181
|
+
suppressNextBlur.current = true;
|
|
222
182
|
setValidationError(null);
|
|
223
183
|
model.setCellData(cell, value);
|
|
224
184
|
setEditing(null);
|
|
225
185
|
onSave?.();
|
|
226
|
-
onEnter?.(cell);
|
|
227
186
|
if (event && onFocus) {
|
|
228
|
-
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
187
|
+
onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
|
|
229
188
|
}
|
|
230
189
|
} else {
|
|
231
190
|
setValidationError(result.error);
|
|
232
191
|
setValidationVariant('error');
|
|
233
192
|
}
|
|
234
193
|
} else {
|
|
194
|
+
suppressNextBlur.current = true;
|
|
235
195
|
setValidationError(null);
|
|
236
196
|
setEditing(null);
|
|
237
197
|
onSave?.();
|
|
@@ -240,154 +200,48 @@ export const TableCellEditor = ({
|
|
|
240
200
|
}
|
|
241
201
|
}
|
|
242
202
|
},
|
|
243
|
-
[model, editing, onFocus,
|
|
203
|
+
[model, editing, onFocus, fieldProjection, setEditing, onSave],
|
|
244
204
|
);
|
|
245
205
|
|
|
246
|
-
const
|
|
206
|
+
const extensions = useMemo(() => {
|
|
247
207
|
if (!fieldProjection) {
|
|
248
208
|
return [];
|
|
249
209
|
}
|
|
250
210
|
|
|
251
|
-
const
|
|
211
|
+
const extensions = [
|
|
252
212
|
editorKeys({
|
|
253
213
|
onClose: handleClose,
|
|
254
214
|
...(editing?.initialContent && { onNav: handleClose }),
|
|
255
215
|
}),
|
|
256
216
|
];
|
|
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]);
|
|
217
|
+
// Add validation extension to handle content changes.
|
|
218
|
+
if (model && editing) {
|
|
219
|
+
extensions.push(
|
|
220
|
+
EditorView.updateListener.of(
|
|
221
|
+
debounce((update) => {
|
|
222
|
+
const content = update.state.doc.toString();
|
|
223
|
+
const cell = parseCellIndex(editing.index);
|
|
224
|
+
|
|
225
|
+
// Perform validation on content change.
|
|
226
|
+
void model.validateCellData(cell, content).then((result) => {
|
|
227
|
+
if (result.valid) {
|
|
228
|
+
setValidationError(null);
|
|
293
229
|
} else {
|
|
294
|
-
|
|
230
|
+
setValidationError(result.error);
|
|
231
|
+
setValidationVariant('error');
|
|
295
232
|
}
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
|
|
233
|
+
});
|
|
234
|
+
}, 10),
|
|
235
|
+
),
|
|
299
236
|
);
|
|
300
237
|
}
|
|
301
238
|
|
|
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]);
|
|
239
|
+
return extensions;
|
|
240
|
+
}, [model, modals, editing, fieldProjection, handleClose]);
|
|
339
241
|
|
|
340
242
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
341
243
|
if (model && editing) {
|
|
342
244
|
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
245
|
const value = model.getCellData(cell);
|
|
392
246
|
return value !== undefined ? String(value) : '';
|
|
393
247
|
}
|
|
@@ -395,12 +249,8 @@ export const TableCellEditor = ({
|
|
|
395
249
|
|
|
396
250
|
return (
|
|
397
251
|
<>
|
|
398
|
-
<CellValidationMessage
|
|
399
|
-
|
|
400
|
-
variant={_validationVariant}
|
|
401
|
-
__gridScope={__gridScope}
|
|
402
|
-
/>
|
|
403
|
-
<GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
252
|
+
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
253
|
+
<GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
404
254
|
</>
|
|
405
255
|
);
|
|
406
256
|
};
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
type ActionGraphEdges,
|
|
12
12
|
type ActionGraphNodes,
|
|
13
13
|
type ActionGraphProps,
|
|
14
|
-
createGapSeparator,
|
|
15
|
-
createMenuAction,
|
|
16
14
|
type MenuAction,
|
|
17
15
|
MenuProvider,
|
|
18
16
|
ToolbarMenu,
|
|
17
|
+
createGapSeparator,
|
|
18
|
+
createMenuAction,
|
|
19
19
|
useMenuActions,
|
|
20
20
|
} from '@dxos/react-ui-menu';
|
|
21
21
|
import { rxFromSignal } from '@dxos/react-ui-menu';
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import { FormatEnum, ObjectId,
|
|
8
|
+
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
|
|
9
9
|
import { live } from '@dxos/live-object';
|
|
10
10
|
import { faker } from '@dxos/random';
|
|
11
11
|
import { type ProjectionModel } from '@dxos/schema';
|
package/src/hooks/useAddRow.ts
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import type * as Schema from 'effect/Schema';
|
|
6
6
|
import { useCallback } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type Space } from '@dxos/client/echo';
|
|
9
9
|
import { Obj } from '@dxos/echo';
|
|
10
10
|
|
|
11
|
+
import { type InsertRowResult } from '../model';
|
|
12
|
+
|
|
11
13
|
export type UseAddRowParams = {
|
|
12
14
|
space?: Space;
|
|
13
15
|
schema?: Schema.Schema.AnyNoContext;
|
|
@@ -20,16 +22,16 @@ export type UseAddRowParams = {
|
|
|
20
22
|
*/
|
|
21
23
|
export const useAddRow = ({ space, schema }: UseAddRowParams) => {
|
|
22
24
|
return useCallback(
|
|
23
|
-
(data?: any) => {
|
|
25
|
+
(data?: any): InsertRowResult => {
|
|
24
26
|
if (space && schema) {
|
|
25
27
|
try {
|
|
26
28
|
space.db.add(Obj.make(schema, data ?? {}));
|
|
27
|
-
return
|
|
29
|
+
return 'final';
|
|
28
30
|
} catch (error) {
|
|
29
|
-
return
|
|
31
|
+
return 'draft';
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
return
|
|
34
|
+
return 'final';
|
|
33
35
|
},
|
|
34
36
|
[space, schema],
|
|
35
37
|
);
|
|
@@ -6,14 +6,14 @@ import { effect } from '@preact/signals-core';
|
|
|
6
6
|
import orderBy from 'lodash.orderby';
|
|
7
7
|
import { useEffect, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import { type JsonSchemaType } from '@dxos/echo
|
|
9
|
+
import { type JsonSchemaType } from '@dxos/echo/internal';
|
|
10
10
|
import { type Live } from '@dxos/live-object';
|
|
11
11
|
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
12
12
|
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
13
13
|
import { type DataType, type ProjectionModel } from '@dxos/schema';
|
|
14
14
|
import { isNonNullable } from '@dxos/util';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
|
|
17
17
|
|
|
18
18
|
export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
19
19
|
view?: DataType.View;
|
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
|
-
import { type
|
|
7
|
-
|
|
8
|
-
import { type Live } from '@dxos/live-object';
|
|
9
|
-
import { log } from '@dxos/log';
|
|
6
|
+
import { type MutableRefObject, type RefObject } from 'react';
|
|
10
7
|
|
|
11
8
|
export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
|
|
12
9
|
|
|
@@ -14,12 +11,6 @@ export type ModalState =
|
|
|
14
11
|
| { type: 'row'; rowIndex: number }
|
|
15
12
|
| { type: 'column'; fieldId: string }
|
|
16
13
|
| { type: 'refPanel'; targetId: string; typename: string }
|
|
17
|
-
| {
|
|
18
|
-
type: 'createRefPanel';
|
|
19
|
-
typename: string;
|
|
20
|
-
initialValues?: Record<string, string>;
|
|
21
|
-
onCreate?: (obj: Live<any>) => void;
|
|
22
|
-
}
|
|
23
14
|
| { type: 'columnSettings'; mode: ColumnSettingsMode }
|
|
24
15
|
| { type: 'closed' };
|
|
25
16
|
|
|
@@ -68,25 +59,6 @@ export class ModalController {
|
|
|
68
59
|
}
|
|
69
60
|
};
|
|
70
61
|
|
|
71
|
-
public openCreateRef = (
|
|
72
|
-
typename: string,
|
|
73
|
-
anchorCell: Element | null,
|
|
74
|
-
initialValues?: Record<string, string>,
|
|
75
|
-
onCreate?: (obj: Live<any>) => void,
|
|
76
|
-
) => {
|
|
77
|
-
if (anchorCell) {
|
|
78
|
-
this._triggerRef.current = anchorCell as HTMLElement;
|
|
79
|
-
this._state.value = {
|
|
80
|
-
type: 'createRefPanel',
|
|
81
|
-
typename,
|
|
82
|
-
initialValues,
|
|
83
|
-
onCreate,
|
|
84
|
-
};
|
|
85
|
-
} else {
|
|
86
|
-
log.warn('anchor cell not found while creating new ref');
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
62
|
public close = () => {
|
|
91
63
|
this._state.value = { type: 'closed' };
|
|
92
64
|
};
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type ReadonlySignal, type Signal, computed, effect, signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
8
|
|
|
9
|
-
import { type TableRow } from './table-model';
|
|
10
9
|
import { touch } from '../util';
|
|
11
10
|
|
|
11
|
+
import { type TableRow } from './table-model';
|
|
12
|
+
|
|
12
13
|
export type SelectionMode = 'single' | 'multiple';
|
|
13
14
|
|
|
14
15
|
export class SelectionModel<T extends TableRow> extends Resource {
|
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { computed } from '@preact/signals-core';
|
|
6
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { TypedObject } from '@dxos/echo
|
|
11
|
-
import { updateCounter } from '@dxos/echo
|
|
9
|
+
import { Filter, Query } from '@dxos/echo';
|
|
10
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
11
|
+
import { updateCounter } from '@dxos/echo/testing';
|
|
12
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
12
13
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
13
14
|
import { live } from '@dxos/live-object';
|
|
14
|
-
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
15
15
|
import { createView } from '@dxos/schema';
|
|
16
16
|
|
|
17
|
+
import { Table } from '../types';
|
|
18
|
+
|
|
17
19
|
import { TableModel, type TableModelProps } from './table-model';
|
|
18
|
-
import { TableView } from '../types';
|
|
19
20
|
|
|
20
21
|
// TODO(burdon): Tests are disabled in project.json since they bring in plugin deps.
|
|
21
22
|
// Restore once factored out into react-ui-table.
|
|
@@ -24,7 +25,7 @@ registerSignalsRuntime();
|
|
|
24
25
|
|
|
25
26
|
describe('TableModel', () => {
|
|
26
27
|
let updateCount = 0;
|
|
27
|
-
let model:
|
|
28
|
+
let model: any;
|
|
28
29
|
|
|
29
30
|
beforeEach(async () => {
|
|
30
31
|
updateCount = 0;
|
|
@@ -32,6 +33,11 @@ describe('TableModel', () => {
|
|
|
32
33
|
onCellUpdate: () => updateCount++,
|
|
33
34
|
});
|
|
34
35
|
await model.open();
|
|
36
|
+
model.setRows([
|
|
37
|
+
{ id: '1', title: 'Test', completed: false },
|
|
38
|
+
{ id: '2', title: 'Test 2', completed: true },
|
|
39
|
+
{ id: '3', title: 'Test 3', completed: false },
|
|
40
|
+
]);
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
afterEach(async () => {
|
|
@@ -72,7 +78,6 @@ describe('TableModel', () => {
|
|
|
72
78
|
describe('reactivity', () => {
|
|
73
79
|
it('pure signals should nest', () => {
|
|
74
80
|
const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
|
|
75
|
-
|
|
76
81
|
const computed$ = computed(() => {
|
|
77
82
|
return signal$.arr.map((row) =>
|
|
78
83
|
computed(() => {
|
|
@@ -107,7 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
107
112
|
|
|
108
113
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
109
114
|
const schema = createEchoSchema(Test);
|
|
110
|
-
const table =
|
|
111
|
-
const view = createView({
|
|
115
|
+
const table = Table.make();
|
|
116
|
+
const view = createView({
|
|
117
|
+
query: Query.select(Filter.type(schema)),
|
|
118
|
+
jsonSchema: schema.jsonSchema,
|
|
119
|
+
presentation: table,
|
|
120
|
+
});
|
|
112
121
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
113
122
|
};
|