@dxos/react-ui-table 0.8.3 → 0.8.4-main.1f223c7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-63UUF4IG.mjs +66 -0
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +7 -0
- package/dist/lib/browser/chunk-BBXDTWCX.mjs +1822 -0
- package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +814 -2152
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +124 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +3 -5
- package/dist/lib/node-esm/chunk-K734DJB7.mjs +67 -0
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs +1823 -0
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +814 -2152
- 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 +125 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +3 -5
- 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 +4 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +50 -2
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +44 -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 +3 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +42 -3
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +8 -3
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +9 -15
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.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 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +14 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +8 -7
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +2 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +59 -21
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +10 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +3 -4
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/useTestTableModel.d.ts +22 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +5 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- 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 +28 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- 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/table-buttons.d.ts +15 -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 +57 -44
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +15 -11
- package/src/components/Table/CreateRefPanel.tsx +30 -11
- package/src/components/Table/DynamicTable.stories.tsx +8 -5
- package/src/components/Table/DynamicTable.tsx +17 -10
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +56 -47
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +126 -159
- package/src/components/Table/Table.tsx +167 -123
- package/src/components/TableCellEditor/CellValidationMessage.tsx +7 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +158 -48
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -47
- package/src/components/TableCellEditor/TableCellEditor.tsx +106 -111
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +7 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +38 -0
- package/src/hooks/useTableModel.ts +21 -17
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +6 -3
- package/src/model/table-model.test.ts +12 -9
- package/src/model/table-model.ts +288 -88
- package/src/model/table-presentation.test.ts +12 -8
- package/src/model/table-presentation.ts +263 -138
- package/src/model/table-sorting.ts +22 -13
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +137 -44
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +124 -0
- package/src/translations.ts +7 -5
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +73 -0
- package/src/util/dynamic-table.ts +24 -21
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +1 -1
- package/src/util/table-buttons.ts +28 -4
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
- package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
- package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
- package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -2739
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/types/index.cjs +0 -31
- package/dist/lib/node/types/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
- package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
- 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/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -14
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
- package/src/util/table.ts +0 -127
|
@@ -3,62 +3,59 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
6
7
|
import { type Schema } from 'effect/Schema';
|
|
7
8
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
8
9
|
|
|
10
|
+
import { debounce } from '@dxos/async';
|
|
11
|
+
import type { Client } from '@dxos/client';
|
|
9
12
|
import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
|
|
10
13
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { type
|
|
12
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
14
|
+
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
13
15
|
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
14
16
|
import {
|
|
15
|
-
|
|
16
|
-
editorKeys,
|
|
17
|
-
parseCellIndex,
|
|
18
|
-
useGridContext,
|
|
19
|
-
type DxGridPlanePosition,
|
|
17
|
+
type EditorBlurHandler,
|
|
20
18
|
type EditorKeyEvent,
|
|
21
19
|
type EditorKeyOrBlurHandler,
|
|
22
|
-
type EditorBlurHandler,
|
|
23
20
|
GridCellEditor,
|
|
24
21
|
type GridCellEditorProps,
|
|
25
22
|
type GridScopedProps,
|
|
23
|
+
editorKeys,
|
|
24
|
+
parseCellIndex,
|
|
25
|
+
useGridContext,
|
|
26
26
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import {
|
|
27
|
+
import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
|
|
28
28
|
import { type FieldProjection } from '@dxos/schema';
|
|
29
29
|
|
|
30
|
+
import { type ModalController, type TableModel } from '../../model';
|
|
31
|
+
|
|
30
32
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
33
|
import { FormCellEditor } from './FormCellEditor';
|
|
32
|
-
import { completion } from './extension';
|
|
33
|
-
import { type TableModel, type ModalController } from '../../model';
|
|
34
|
-
|
|
35
|
-
const newValue = Symbol.for('newValue');
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
36
|
* Option to create new object/value.
|
|
39
37
|
*/
|
|
40
|
-
export const createOption = (text: string) => ({ [newValue]: true, text });
|
|
41
|
-
|
|
42
|
-
const isCreateOption = (data: any) => typeof data === 'object' && data[newValue];
|
|
43
|
-
|
|
44
38
|
export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
45
39
|
|
|
46
40
|
export type TableCellEditorProps = {
|
|
47
41
|
model?: TableModel;
|
|
48
42
|
modals?: ModalController;
|
|
49
43
|
schema?: Schema.AnyNoContext;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
45
|
+
onSave?: () => void;
|
|
46
|
+
client?: Client;
|
|
53
47
|
};
|
|
54
48
|
|
|
49
|
+
const adaptValidationMessage = (message: string | null) =>
|
|
50
|
+
message ? (message.endsWith('is missing') ? 'Can’t be blank' : message) : null;
|
|
51
|
+
|
|
55
52
|
export const TableValueEditor = ({
|
|
56
53
|
model,
|
|
57
54
|
modals,
|
|
58
55
|
schema,
|
|
59
|
-
onEnter,
|
|
60
56
|
onFocus,
|
|
61
|
-
|
|
57
|
+
onSave,
|
|
58
|
+
client,
|
|
62
59
|
__gridScope,
|
|
63
60
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
64
61
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
@@ -69,41 +66,48 @@ export const TableValueEditor = ({
|
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
const { col } = parseCellIndex(editing.index);
|
|
72
|
-
const field = model.projection.
|
|
69
|
+
const field = model.projection.fields[col];
|
|
73
70
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
74
71
|
invariant(fieldProjection);
|
|
75
72
|
return fieldProjection;
|
|
76
73
|
}, [model, editing]);
|
|
77
74
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
75
|
+
if (
|
|
76
|
+
fieldProjection?.props.type === TypeEnum.Array ||
|
|
77
|
+
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
78
|
+
fieldProjection?.props.format === FormatEnum.Ref
|
|
79
|
+
// TODO(thure): Support `FormatEnum.MultiSelect`
|
|
80
|
+
) {
|
|
81
|
+
return (
|
|
82
|
+
<FormCellEditor
|
|
83
|
+
fieldProjection={fieldProjection}
|
|
84
|
+
model={model}
|
|
85
|
+
schema={schema}
|
|
86
|
+
__gridScope={__gridScope}
|
|
87
|
+
onSave={onSave}
|
|
88
|
+
client={client}
|
|
89
|
+
modals={modals}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
// For all other types, use the existing cell editor
|
|
83
|
-
return
|
|
84
|
-
<TableCellEditor
|
|
85
|
-
model={model}
|
|
86
|
-
modals={modals}
|
|
87
|
-
onEnter={onEnter}
|
|
88
|
-
onFocus={onFocus}
|
|
89
|
-
onQuery={onQuery}
|
|
90
|
-
__gridScope={__gridScope}
|
|
91
|
-
/>
|
|
92
|
-
);
|
|
95
|
+
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
93
96
|
};
|
|
94
97
|
|
|
98
|
+
const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
99
|
+
|
|
95
100
|
export const TableCellEditor = ({
|
|
96
101
|
model,
|
|
97
102
|
modals,
|
|
98
|
-
onEnter,
|
|
99
103
|
onFocus,
|
|
100
|
-
|
|
104
|
+
onSave,
|
|
101
105
|
__gridScope,
|
|
102
106
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
103
|
-
const {
|
|
107
|
+
const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
104
108
|
const suppressNextBlur = useRef(false);
|
|
105
|
-
const
|
|
106
|
-
const [
|
|
109
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
110
|
+
const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
107
111
|
|
|
108
112
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
109
113
|
if (!model || !editing) {
|
|
@@ -111,7 +115,7 @@ export const TableCellEditor = ({
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
const { col } = parseCellIndex(editing.index);
|
|
114
|
-
const field = model.projection.
|
|
118
|
+
const field = model.projection.fields[col];
|
|
115
119
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
116
120
|
invariant(fieldProjection);
|
|
117
121
|
return fieldProjection;
|
|
@@ -124,34 +128,39 @@ export const TableCellEditor = ({
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
const cell = parseCellIndex(editing.index);
|
|
131
|
+
const validationResult = await model.validateCellData(cell, value);
|
|
127
132
|
|
|
128
|
-
|
|
129
|
-
const result = await model.validateCellData(cell, value);
|
|
130
|
-
|
|
131
|
-
if (result.valid) {
|
|
133
|
+
if (validationResult.valid) {
|
|
132
134
|
setValidationError(null);
|
|
133
135
|
model.setCellData(cell, value);
|
|
134
|
-
onEnter?.(cell);
|
|
135
136
|
onFocus?.();
|
|
136
137
|
setEditing(null);
|
|
138
|
+
onSave?.();
|
|
137
139
|
} else {
|
|
138
|
-
setValidationError(
|
|
139
|
-
|
|
140
|
+
setValidationError(adaptValidationMessage(validationResult.error));
|
|
141
|
+
setValidationVariant('error');
|
|
140
142
|
}
|
|
141
143
|
},
|
|
142
|
-
[model, editing,
|
|
144
|
+
[model, editing, onFocus, setEditing],
|
|
143
145
|
);
|
|
144
146
|
|
|
145
147
|
const handleBlur = useCallback<EditorBlurHandler>(
|
|
146
148
|
async (value) => {
|
|
147
|
-
if (!model || !editing) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
149
|
if (suppressNextBlur.current) {
|
|
151
150
|
suppressNextBlur.current = false;
|
|
151
|
+
} else if (model && editing) {
|
|
152
|
+
// Save silently if validation passes
|
|
153
|
+
const cell = parseCellIndex(editing.index);
|
|
154
|
+
if (value !== undefined) {
|
|
155
|
+
const result = await model.validateCellData(cell, value);
|
|
156
|
+
if (result.valid) {
|
|
157
|
+
setValidationError(null);
|
|
158
|
+
model.setCellData(cell, value);
|
|
159
|
+
setEditing(null);
|
|
160
|
+
onSave?.();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
152
163
|
}
|
|
153
|
-
|
|
154
|
-
// Don't save on blur - let handleClose handle validation and saving
|
|
155
164
|
},
|
|
156
165
|
[model, editing],
|
|
157
166
|
);
|
|
@@ -163,39 +172,42 @@ export const TableCellEditor = ({
|
|
|
163
172
|
}
|
|
164
173
|
|
|
165
174
|
const cell = parseCellIndex(editing.index);
|
|
166
|
-
|
|
167
175
|
if (value !== undefined) {
|
|
168
176
|
// Pre-commit validation check.
|
|
169
177
|
const result = await model.validateCellData(cell, value);
|
|
170
178
|
|
|
171
179
|
if (result.valid) {
|
|
180
|
+
suppressNextBlur.current = true;
|
|
172
181
|
setValidationError(null);
|
|
173
182
|
model.setCellData(cell, value);
|
|
174
183
|
setEditing(null);
|
|
175
|
-
|
|
184
|
+
onSave?.();
|
|
176
185
|
if (event && onFocus) {
|
|
177
|
-
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
186
|
+
onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
|
|
178
187
|
}
|
|
179
188
|
} else {
|
|
180
189
|
setValidationError(result.error);
|
|
190
|
+
setValidationVariant('error');
|
|
181
191
|
}
|
|
182
192
|
} else {
|
|
193
|
+
suppressNextBlur.current = true;
|
|
183
194
|
setValidationError(null);
|
|
184
195
|
setEditing(null);
|
|
196
|
+
onSave?.();
|
|
185
197
|
if (event && onFocus) {
|
|
186
198
|
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
187
199
|
}
|
|
188
200
|
}
|
|
189
201
|
},
|
|
190
|
-
[model, editing, onFocus,
|
|
202
|
+
[model, editing, onFocus, fieldProjection, setEditing, onSave],
|
|
191
203
|
);
|
|
192
204
|
|
|
193
|
-
const
|
|
205
|
+
const extensions = useMemo(() => {
|
|
194
206
|
if (!fieldProjection) {
|
|
195
207
|
return [];
|
|
196
208
|
}
|
|
197
209
|
|
|
198
|
-
const
|
|
210
|
+
const extensions = [
|
|
199
211
|
editorKeys({
|
|
200
212
|
onClose: handleClose,
|
|
201
213
|
...(editing?.initialContent && { onNav: handleClose }),
|
|
@@ -203,20 +215,17 @@ export const TableCellEditor = ({
|
|
|
203
215
|
];
|
|
204
216
|
|
|
205
217
|
const format = fieldProjection.props.format;
|
|
206
|
-
|
|
207
218
|
if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
|
|
208
219
|
// TODO(ZaymonFC): Reconcile this with the TagPicker component?
|
|
209
|
-
// Add markdown extensions needed by tag picker.
|
|
210
|
-
extension.push(createMarkdownExtensions({ themeMode }));
|
|
211
|
-
|
|
212
220
|
const options = fieldProjection.props.options || [];
|
|
213
|
-
|
|
214
221
|
const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
|
|
215
222
|
|
|
216
|
-
|
|
217
|
-
|
|
223
|
+
// Add markdown extensions needed by tag picker.
|
|
224
|
+
extensions.push(createMarkdownExtensions());
|
|
225
|
+
extensions.push(
|
|
226
|
+
tagPicker({
|
|
218
227
|
mode,
|
|
219
|
-
|
|
228
|
+
keymap: false,
|
|
220
229
|
onSearch: (text, selectedIds) => {
|
|
221
230
|
return options
|
|
222
231
|
.filter(
|
|
@@ -246,49 +255,36 @@ export const TableCellEditor = ({
|
|
|
246
255
|
);
|
|
247
256
|
}
|
|
248
257
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
},
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
} else {
|
|
273
|
-
void handleEnter(data);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
}),
|
|
278
|
-
]);
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
258
|
+
// Add validation extension to handle content changes.
|
|
259
|
+
if (model && editing) {
|
|
260
|
+
extensions.push(
|
|
261
|
+
EditorView.updateListener.of(
|
|
262
|
+
debounce((update) => {
|
|
263
|
+
const content = update.state.doc.toString();
|
|
264
|
+
const cell = parseCellIndex(editing.index);
|
|
265
|
+
|
|
266
|
+
// Perform validation on content change.
|
|
267
|
+
void model.validateCellData(cell, content).then((result) => {
|
|
268
|
+
if (result.valid) {
|
|
269
|
+
setValidationError(null);
|
|
270
|
+
} else {
|
|
271
|
+
setValidationError(result.error);
|
|
272
|
+
setValidationVariant('error');
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}, 10),
|
|
276
|
+
),
|
|
277
|
+
);
|
|
282
278
|
}
|
|
283
279
|
|
|
284
|
-
return
|
|
285
|
-
}, [model, modals, editing, fieldProjection, handleClose
|
|
280
|
+
return extensions;
|
|
281
|
+
}, [model, modals, editing, fieldProjection, handleClose]);
|
|
286
282
|
|
|
287
283
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
288
284
|
if (model && editing) {
|
|
289
285
|
const cell = parseCellIndex(editing.index);
|
|
290
286
|
const { col } = cell;
|
|
291
|
-
const field = model.projection.
|
|
287
|
+
const field = model.projection.fields[col];
|
|
292
288
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
293
289
|
|
|
294
290
|
if (
|
|
@@ -296,13 +292,12 @@ export const TableCellEditor = ({
|
|
|
296
292
|
fieldProjection?.props.format === FormatEnum.MultiSelect
|
|
297
293
|
) {
|
|
298
294
|
const value = model.getCellData(cell);
|
|
299
|
-
|
|
300
295
|
if (value !== undefined) {
|
|
301
296
|
const options = fieldProjection.props.options || [];
|
|
302
|
-
|
|
303
|
-
if (fieldProjection.props.format === FormatEnum.MultiSelect && Array.isArray(value)) {
|
|
297
|
+
if (fieldProjection.props.format === FormatEnum.MultiSelect) {
|
|
304
298
|
const tagItems = value
|
|
305
|
-
.
|
|
299
|
+
.split(',')
|
|
300
|
+
.map((id: string) => {
|
|
306
301
|
const option = options.find((o) => o.id === id);
|
|
307
302
|
if (option) {
|
|
308
303
|
return {
|
|
@@ -313,7 +308,7 @@ export const TableCellEditor = ({
|
|
|
313
308
|
}
|
|
314
309
|
return undefined;
|
|
315
310
|
})
|
|
316
|
-
.filter((item): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
311
|
+
.filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
317
312
|
|
|
318
313
|
return createLinks(tagItems);
|
|
319
314
|
} else {
|
|
@@ -341,8 +336,8 @@ export const TableCellEditor = ({
|
|
|
341
336
|
|
|
342
337
|
return (
|
|
343
338
|
<>
|
|
344
|
-
<CellValidationMessage validationError={
|
|
345
|
-
<GridCellEditor
|
|
339
|
+
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
340
|
+
<GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
346
341
|
</>
|
|
347
342
|
);
|
|
348
343
|
};
|
|
@@ -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';
|
|
@@ -5,13 +5,10 @@
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo-schema';
|
|
9
|
+
import { live } from '@dxos/live-object';
|
|
10
10
|
import { faker } from '@dxos/random';
|
|
11
|
-
import {
|
|
12
|
-
import { createView, type ViewProjection } from '@dxos/schema';
|
|
13
|
-
|
|
14
|
-
import { TableType } from '../types';
|
|
11
|
+
import { type ProjectionModel } from '@dxos/schema';
|
|
15
12
|
|
|
16
13
|
export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
17
14
|
id: ObjectId,
|
|
@@ -21,18 +18,6 @@ export const TestSchema = TypedObject({ typename: 'example.com/type/Test', versi
|
|
|
21
18
|
netWorth: Schema.optional(Schema.Number),
|
|
22
19
|
});
|
|
23
20
|
|
|
24
|
-
export const createTable = (schema = TestSchema) => {
|
|
25
|
-
return Obj.make(TableType, {
|
|
26
|
-
view: Ref.make(
|
|
27
|
-
createView({
|
|
28
|
-
name: 'Test',
|
|
29
|
-
typename: schema.typename,
|
|
30
|
-
jsonSchema: toJsonSchema(schema),
|
|
31
|
-
}),
|
|
32
|
-
),
|
|
33
|
-
});
|
|
34
|
-
};
|
|
35
|
-
|
|
36
21
|
export const createItems = (n: number) => {
|
|
37
22
|
const { data } = live({
|
|
38
23
|
data: Array.from({ length: n }, () => ({
|
|
@@ -47,13 +32,13 @@ export const createItems = (n: number) => {
|
|
|
47
32
|
};
|
|
48
33
|
|
|
49
34
|
export type SimulatorProps = {
|
|
50
|
-
|
|
35
|
+
projection: ProjectionModel;
|
|
51
36
|
items: any[];
|
|
52
37
|
insertInterval?: number;
|
|
53
38
|
updateInterval?: number;
|
|
54
39
|
};
|
|
55
40
|
|
|
56
|
-
export const useSimulator = ({ items,
|
|
41
|
+
export const useSimulator = ({ items, projection, insertInterval, updateInterval }: SimulatorProps) => {
|
|
57
42
|
useEffect(() => {
|
|
58
43
|
if (!insertInterval) {
|
|
59
44
|
return;
|
|
@@ -74,10 +59,8 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
|
|
|
74
59
|
|
|
75
60
|
const i = setInterval(() => {
|
|
76
61
|
const rowIdx = Math.floor(Math.random() * items.length);
|
|
77
|
-
const fields =
|
|
62
|
+
const fields = projection.fields ?? [];
|
|
78
63
|
const columnIdx = Math.floor(Math.random() * fields.length);
|
|
79
|
-
// TODO(ZaymonFC): ... This is borked.
|
|
80
|
-
const projection: ViewProjection = (table as any)._projection;
|
|
81
64
|
const field = fields[columnIdx];
|
|
82
65
|
const item = items[rowIdx];
|
|
83
66
|
|
|
@@ -115,5 +98,5 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
|
|
|
115
98
|
}, updateInterval);
|
|
116
99
|
|
|
117
100
|
return () => clearInterval(i);
|
|
118
|
-
}, [items,
|
|
101
|
+
}, [items, projection.fields, updateInterval]);
|
|
119
102
|
};
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Schema } from 'effect';
|
|
6
|
+
import { useCallback } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type Space } from '@dxos/client/echo';
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
|
|
11
|
+
import { type InsertRowResult } from '../model';
|
|
12
|
+
|
|
13
|
+
export type UseAddRowParams = {
|
|
14
|
+
space?: Space;
|
|
15
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hook that provides a safe row creation function that handles schema validation failures.
|
|
20
|
+
* Returns a callback that attempts to create an object and returns boolean success/failure.
|
|
21
|
+
* Can accept optional data to create objects with specific content (used for committing draft rows).
|
|
22
|
+
*/
|
|
23
|
+
export const useAddRow = ({ space, schema }: UseAddRowParams) => {
|
|
24
|
+
return useCallback(
|
|
25
|
+
(data?: any): InsertRowResult => {
|
|
26
|
+
if (space && schema) {
|
|
27
|
+
try {
|
|
28
|
+
space.db.add(Obj.make(schema, data ?? {}));
|
|
29
|
+
return 'final';
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return 'draft';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return 'final';
|
|
35
|
+
},
|
|
36
|
+
[space, schema],
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -4,19 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
import { effect } from '@preact/signals-core';
|
|
6
6
|
import orderBy from 'lodash.orderby';
|
|
7
|
-
import { useEffect, useState } from 'react';
|
|
7
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { type JsonSchemaType } from '@dxos/echo-schema';
|
|
10
|
+
import { type Live } from '@dxos/live-object';
|
|
11
|
+
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
12
|
+
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
13
|
+
import { type DataType, type ProjectionModel } from '@dxos/schema';
|
|
12
14
|
import { isNonNullable } from '@dxos/util';
|
|
13
15
|
|
|
14
|
-
import {
|
|
15
|
-
import { type TableType } from '../types';
|
|
16
|
+
import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
|
|
16
17
|
|
|
17
18
|
export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
view?: DataType.View;
|
|
20
|
+
schema?: JsonSchemaType;
|
|
21
|
+
projection?: ProjectionModel;
|
|
20
22
|
rows?: Live<T>[];
|
|
21
23
|
rowActions?: TableRowAction[];
|
|
22
24
|
onSelectionChanged?: (selection: string[]) => void;
|
|
@@ -27,7 +29,8 @@ export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
|
27
29
|
>;
|
|
28
30
|
|
|
29
31
|
export const useTableModel = <T extends TableRow = TableRow>({
|
|
30
|
-
|
|
32
|
+
view,
|
|
33
|
+
schema,
|
|
31
34
|
projection,
|
|
32
35
|
rows,
|
|
33
36
|
rowActions,
|
|
@@ -36,21 +39,24 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
36
39
|
onRowAction,
|
|
37
40
|
...props
|
|
38
41
|
}: UseTableModelParams<T>): TableModel<T> | undefined => {
|
|
42
|
+
const selected = useSelected(view && fullyQualifiedId(view), 'multi');
|
|
43
|
+
const initialSelection = useMemo(() => selected, [view]);
|
|
44
|
+
|
|
39
45
|
const [model, setModel] = useState<TableModel<T>>();
|
|
40
46
|
useEffect(() => {
|
|
41
|
-
if (!
|
|
47
|
+
if (!view || !schema) {
|
|
42
48
|
return;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
let model: TableModel<T> | undefined;
|
|
46
52
|
const t = setTimeout(async () => {
|
|
47
53
|
model = new TableModel<T>({
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
view: table.view?.target,
|
|
54
|
+
view,
|
|
55
|
+
schema,
|
|
51
56
|
projection,
|
|
52
57
|
features,
|
|
53
58
|
rowActions,
|
|
59
|
+
initialSelection,
|
|
54
60
|
onRowAction,
|
|
55
61
|
...props,
|
|
56
62
|
});
|
|
@@ -62,7 +68,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
62
68
|
clearTimeout(t);
|
|
63
69
|
void model?.close();
|
|
64
70
|
};
|
|
65
|
-
}, [
|
|
71
|
+
}, [view, schema, projection, features, rowActions, initialSelection]); // TODO(burdon): Trigger if callbacks change?
|
|
66
72
|
|
|
67
73
|
// Update data.
|
|
68
74
|
useEffect(() => {
|
|
@@ -77,9 +83,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
77
83
|
}
|
|
78
84
|
}, [model, rows]);
|
|
79
85
|
|
|
80
|
-
const { multiSelect, clear } = useSelectionActions(
|
|
81
|
-
[table?.id, table?.view?.target?.query.typename].filter(isNonNullable),
|
|
82
|
-
);
|
|
86
|
+
const { multiSelect, clear } = useSelectionActions([model?.id].filter(isNonNullable));
|
|
83
87
|
|
|
84
88
|
useEffect(() => {
|
|
85
89
|
if (!model) {
|
package/src/index.ts
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import translations from './translations';
|
|
5
|
+
import { translations } from './translations';
|
|
6
6
|
|
|
7
7
|
export * from './components';
|
|
8
8
|
export * from './hooks';
|
|
9
9
|
export * from './model';
|
|
10
|
-
export * from './types';
|
|
11
10
|
export * from './util';
|
|
12
11
|
|
|
13
12
|
export { translations };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
|
-
import { type
|
|
6
|
+
import { type MutableRefObject, type RefObject } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type Live } from '@dxos/live-object';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { computed, effect, signal
|
|
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 {
|
|
15
|
-
private readonly _selection
|
|
16
|
+
private readonly _selection: Signal<Set<string>>;
|
|
16
17
|
|
|
17
18
|
private readonly _validSelectedIds = computed<Set<string>>(() => {
|
|
18
19
|
const rows = this._rows.value;
|
|
@@ -38,9 +39,11 @@ export class SelectionModel<T extends TableRow> extends Resource {
|
|
|
38
39
|
constructor(
|
|
39
40
|
private readonly _rows: ReadonlySignal<T[]>,
|
|
40
41
|
private readonly _selectionMode: SelectionMode,
|
|
42
|
+
private readonly _initialSelection: string[],
|
|
41
43
|
private readonly _onSelectionChanged?: () => void,
|
|
42
44
|
) {
|
|
43
45
|
super();
|
|
46
|
+
this._selection = signal(new Set(this._initialSelection));
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
protected override async _open(): Promise<void> {
|