@dxos/react-ui-table 0.8.4-main.c1de068 → 0.8.4-main.c85a9c8dae
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-DSR2MRZB.mjs +80 -0
- package/dist/lib/browser/chunk-DSR2MRZB.mjs.map +7 -0
- package/dist/lib/browser/chunk-PTHMYAAM.mjs +1751 -0
- package/dist/lib/browser/chunk-PTHMYAAM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1190 -1139
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +31 -38
- 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-GI72MQSG.mjs +1753 -0
- package/dist/lib/node-esm/chunk-GI72MQSG.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HTZJSULI.mjs +81 -0
- package/dist/lib/node-esm/chunk-HTZJSULI.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1190 -1139
- 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 +31 -38
- 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 +2 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -3
- 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 +37 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +111 -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 +17 -13
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +108 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +13 -9
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +11 -18
- 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 +5 -5
- package/dist/types/src/components/TableToolbar/TableToolbar.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 +10 -10
- 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 +112 -30
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +5 -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 +4 -4
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +31 -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/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +61 -56
- package/src/components/Table/ColumnActionsMenu.tsx +11 -7
- package/src/components/Table/ColumnSettings.tsx +9 -10
- package/src/components/Table/DynamicTable.stories.tsx +28 -24
- package/src/components/Table/DynamicTable.tsx +33 -27
- package/src/components/Table/Relations.stories.tsx +240 -0
- package/src/components/Table/RowActionsMenu.tsx +26 -17
- package/src/components/Table/Table.stories.tsx +146 -116
- package/src/components/Table/Table.tsx +353 -297
- package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -6
- package/src/components/TableCellEditor/FormCellEditor.tsx +134 -40
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -66
- package/src/components/TableCellEditor/TableCellEditor.tsx +88 -238
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +54 -38
- package/src/components/testing.ts +39 -13
- 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 +24 -41
- package/src/model/selection-model.ts +75 -43
- package/src/model/table-model.test.ts +92 -55
- package/src/model/table-model.ts +453 -175
- package/src/model/table-presentation.test.ts +70 -58
- package/src/model/table-presentation.ts +111 -64
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +171 -33
- package/src/testing/useTestTableModel.ts +46 -35
- package/src/translations.ts +3 -3
- package/src/types/Table.ts +95 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +46 -33
- 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 +4 -3
- package/src/util/table-controls.ts +6 -3
- package/dist/lib/browser/chunk-5XLSHLOG.mjs +0 -1751
- 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 +0 -1752
- 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/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 -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/model/table-sorting.ts +0 -216
- package/src/types/schema.ts +0 -16
- package/src/util/table.ts +0 -49
|
@@ -3,66 +3,63 @@
|
|
|
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
|
-
import { type
|
|
12
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
13
|
-
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
13
|
+
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
editorKeys,
|
|
17
|
-
parseCellIndex,
|
|
18
|
-
useGridContext,
|
|
19
|
-
type DxGridPlanePosition,
|
|
15
|
+
type EditorBlurHandler,
|
|
20
16
|
type EditorKeyEvent,
|
|
21
17
|
type EditorKeyOrBlurHandler,
|
|
22
|
-
type EditorBlurHandler,
|
|
23
18
|
GridCellEditor,
|
|
24
19
|
type GridCellEditorProps,
|
|
25
20
|
type GridScopedProps,
|
|
21
|
+
editorKeys,
|
|
22
|
+
parseCellIndex,
|
|
23
|
+
useGridContext,
|
|
26
24
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import { tagPicker, createLinks } from '@dxos/react-ui-tag-picker';
|
|
28
25
|
import { type FieldProjection } from '@dxos/schema';
|
|
29
26
|
|
|
27
|
+
import { type ModalController, type TableModel } from '../../model';
|
|
28
|
+
|
|
30
29
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
|
-
import { FormCellEditor } from './FormCellEditor';
|
|
32
|
-
import { completion } from './extension';
|
|
33
|
-
import { type TableModel, type ModalController } from '../../model';
|
|
30
|
+
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
34
31
|
|
|
35
|
-
const
|
|
32
|
+
const editorSlots = {
|
|
33
|
+
scroll: {
|
|
34
|
+
className: '!py-(--dx-grid-cell-editor-padding-block)',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Option to create new object/value.
|
|
39
40
|
*/
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
41
|
+
export type QueryResult = Pick<Completion, 'label'> & {
|
|
42
|
+
data: any;
|
|
43
|
+
};
|
|
45
44
|
|
|
46
|
-
export type TableCellEditorProps = {
|
|
45
|
+
export type TableCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
|
|
46
|
+
schema?: T;
|
|
47
47
|
model?: TableModel;
|
|
48
48
|
modals?: ModalController;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
onFocus?: DxGrid['refocus'];
|
|
49
|
+
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
50
|
+
onCreate?: OnCreateHandler;
|
|
52
51
|
onSave?: () => void;
|
|
53
|
-
onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
|
|
54
52
|
};
|
|
55
53
|
|
|
56
|
-
export const TableValueEditor = ({
|
|
54
|
+
export const TableValueEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
55
|
+
__gridScope,
|
|
56
|
+
schema,
|
|
57
57
|
model,
|
|
58
58
|
modals,
|
|
59
|
-
schema,
|
|
60
|
-
onEnter,
|
|
61
59
|
onFocus,
|
|
62
60
|
onSave,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}: GridScopedProps<TableCellEditorProps>) => {
|
|
61
|
+
onCreate,
|
|
62
|
+
}: GridScopedProps<TableCellEditorProps<T>>) => {
|
|
66
63
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
67
64
|
|
|
68
65
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
@@ -71,7 +68,7 @@ export const TableValueEditor = ({
|
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
const { col } = parseCellIndex(editing.index);
|
|
74
|
-
const field = model.projection.
|
|
71
|
+
const field = model.projection.getFields()[col];
|
|
75
72
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
76
73
|
invariant(fieldProjection);
|
|
77
74
|
return fieldProjection;
|
|
@@ -79,50 +76,38 @@ export const TableValueEditor = ({
|
|
|
79
76
|
|
|
80
77
|
if (
|
|
81
78
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
82
|
-
fieldProjection?.props.format ===
|
|
83
|
-
|
|
79
|
+
fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
|
|
80
|
+
fieldProjection?.props.format === Format.TypeFormat.Ref
|
|
81
|
+
// TODO(thure): Support `Format.TypeFormat.MultiSelect`
|
|
84
82
|
) {
|
|
85
83
|
return (
|
|
86
|
-
<FormCellEditor
|
|
87
|
-
fieldProjection={fieldProjection}
|
|
88
|
-
model={model}
|
|
89
|
-
schema={schema}
|
|
84
|
+
<FormCellEditor<T>
|
|
90
85
|
__gridScope={__gridScope}
|
|
86
|
+
schema={schema}
|
|
87
|
+
model={model}
|
|
88
|
+
fieldProjection={fieldProjection}
|
|
89
|
+
modals={modals}
|
|
91
90
|
onSave={onSave}
|
|
91
|
+
onCreate={onCreate}
|
|
92
92
|
/>
|
|
93
93
|
);
|
|
94
94
|
}
|
|
95
95
|
|
|
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
|
-
);
|
|
96
|
+
// For all other types, use the existing cell editor.
|
|
97
|
+
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
108
98
|
};
|
|
109
99
|
|
|
110
|
-
const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
111
|
-
|
|
112
100
|
export const TableCellEditor = ({
|
|
101
|
+
__gridScope,
|
|
113
102
|
model,
|
|
114
103
|
modals,
|
|
115
|
-
onEnter,
|
|
116
104
|
onFocus,
|
|
117
|
-
onQuery,
|
|
118
105
|
onSave,
|
|
119
|
-
__gridScope,
|
|
120
106
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
121
|
-
const {
|
|
107
|
+
const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
122
108
|
const suppressNextBlur = useRef(false);
|
|
123
|
-
const
|
|
124
|
-
const [
|
|
125
|
-
const [_validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
109
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
110
|
+
const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
126
111
|
|
|
127
112
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
128
113
|
if (!model || !editing) {
|
|
@@ -130,44 +115,13 @@ export const TableCellEditor = ({
|
|
|
130
115
|
}
|
|
131
116
|
|
|
132
117
|
const { col } = parseCellIndex(editing.index);
|
|
133
|
-
const field = model.projection.
|
|
118
|
+
const field = model.projection.getFields()[col];
|
|
134
119
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
135
120
|
invariant(fieldProjection);
|
|
136
121
|
return fieldProjection;
|
|
137
122
|
}, [model, editing]);
|
|
138
123
|
|
|
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
|
-
|
|
124
|
+
// TOOD(burdon): Attach to event handler?
|
|
171
125
|
const handleEnter = useCallback(
|
|
172
126
|
async (value: any) => {
|
|
173
127
|
if (!model || !editing) {
|
|
@@ -180,7 +134,6 @@ 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?.();
|
|
@@ -189,19 +142,26 @@ export const TableCellEditor = ({
|
|
|
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
|
};
|
|
@@ -2,23 +2,20 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import React, { useMemo } from 'react';
|
|
5
|
+
import { Atom, RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import React, { useContext, useEffect, useMemo } from 'react';
|
|
7
7
|
|
|
8
|
-
import { live } from '@dxos/live-object';
|
|
9
8
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
10
9
|
import {
|
|
11
10
|
type ActionGraphEdges,
|
|
12
11
|
type ActionGraphNodes,
|
|
13
12
|
type ActionGraphProps,
|
|
13
|
+
Menu,
|
|
14
|
+
type MenuAction,
|
|
14
15
|
createGapSeparator,
|
|
15
16
|
createMenuAction,
|
|
16
|
-
type MenuAction,
|
|
17
|
-
MenuProvider,
|
|
18
|
-
ToolbarMenu,
|
|
19
17
|
useMenuActions,
|
|
20
18
|
} from '@dxos/react-ui-menu';
|
|
21
|
-
import { rxFromSignal } from '@dxos/react-ui-menu';
|
|
22
19
|
|
|
23
20
|
import { translationKey } from '../../translations';
|
|
24
21
|
|
|
@@ -30,49 +27,60 @@ export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
|
|
|
30
27
|
|
|
31
28
|
type TableToolbarState = Partial<{ viewDirty: boolean }>;
|
|
32
29
|
|
|
30
|
+
// TODO(burdon): Radix style layout.
|
|
31
|
+
|
|
33
32
|
export type TableToolbarProps = ThemedClassName<
|
|
34
33
|
TableToolbarState & {
|
|
35
|
-
onAdd
|
|
36
|
-
onSave
|
|
34
|
+
onAdd?: () => void;
|
|
35
|
+
onSave?: () => void;
|
|
37
36
|
attendableId?: string;
|
|
38
|
-
customActions?:
|
|
37
|
+
customActions?: Atom.Atom<ActionGraphProps>;
|
|
39
38
|
}
|
|
40
39
|
>;
|
|
41
40
|
|
|
42
41
|
const createTableToolbarActions = ({
|
|
43
|
-
|
|
42
|
+
stateAtom,
|
|
44
43
|
onAdd,
|
|
45
44
|
onSave,
|
|
46
45
|
customActions,
|
|
47
46
|
}: {
|
|
48
|
-
|
|
49
|
-
onAdd
|
|
50
|
-
onSave
|
|
51
|
-
customActions?:
|
|
47
|
+
stateAtom: Atom.Atom<TableToolbarState>;
|
|
48
|
+
onAdd?: () => void;
|
|
49
|
+
onSave?: () => void;
|
|
50
|
+
customActions?: Atom.Atom<ActionGraphProps>;
|
|
52
51
|
}) =>
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
Atom.make((get) => {
|
|
53
|
+
const nodes: ActionGraphNodes = [];
|
|
54
|
+
if (onAdd) {
|
|
55
|
+
const add = createMenuAction<TableToolbarActionProperties>('add-row', onAdd, {
|
|
56
|
+
type: 'add-row' as const,
|
|
57
|
+
icon: 'ph--plus--regular',
|
|
58
|
+
label: ['add row label', { ns: translationKey }],
|
|
59
|
+
testId: 'table.toolbar.add-row',
|
|
60
|
+
});
|
|
61
|
+
nodes.push(add);
|
|
62
|
+
}
|
|
63
|
+
if (onSave) {
|
|
64
|
+
const state = get(stateAtom);
|
|
65
|
+
const save = createMenuAction<TableToolbarActionProperties>('save-view', onSave, {
|
|
66
|
+
type: 'save-view' as const,
|
|
67
|
+
icon: 'ph--floppy-disk--regular',
|
|
68
|
+
label: ['save view label', { ns: translationKey }],
|
|
69
|
+
testId: 'table.toolbar.save-view',
|
|
70
|
+
iconOnly: false,
|
|
71
|
+
hidden: !state.viewDirty,
|
|
72
|
+
});
|
|
73
|
+
nodes.push(save);
|
|
74
|
+
}
|
|
68
75
|
const gap = createGapSeparator();
|
|
69
|
-
|
|
70
|
-
const edges: ActionGraphEdges = nodes.map(({ id: target }) => ({ source: 'root', target }));
|
|
76
|
+
nodes.push(...gap.nodes);
|
|
77
|
+
const edges: ActionGraphEdges = nodes.map(({ id: target }) => ({ source: 'root', target, relation: 'child' }));
|
|
71
78
|
if (customActions) {
|
|
72
79
|
const custom = get(customActions);
|
|
73
80
|
nodes.push(...custom.nodes);
|
|
74
81
|
edges.push(...custom.edges);
|
|
75
82
|
}
|
|
83
|
+
|
|
76
84
|
return {
|
|
77
85
|
nodes,
|
|
78
86
|
edges,
|
|
@@ -86,17 +94,25 @@ export const TableToolbar = ({
|
|
|
86
94
|
onAdd,
|
|
87
95
|
onSave,
|
|
88
96
|
customActions,
|
|
97
|
+
...props
|
|
89
98
|
}: TableToolbarProps) => {
|
|
90
|
-
const
|
|
99
|
+
const registry = useContext(RegistryContext);
|
|
100
|
+
const stateAtom = useMemo(() => Atom.make<TableToolbarState>({ viewDirty }), []);
|
|
101
|
+
|
|
102
|
+
// Update state.viewDirty when the prop changes.
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
registry.set(stateAtom, { viewDirty });
|
|
105
|
+
}, [registry, stateAtom, viewDirty]);
|
|
106
|
+
|
|
91
107
|
const actionsCreator = useMemo(
|
|
92
|
-
() => createTableToolbarActions({
|
|
93
|
-
[
|
|
108
|
+
() => createTableToolbarActions({ stateAtom, onAdd, onSave, customActions }),
|
|
109
|
+
[stateAtom, onAdd, onSave, customActions],
|
|
94
110
|
);
|
|
95
111
|
const menu = useMenuActions(actionsCreator);
|
|
96
112
|
|
|
97
113
|
return (
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
</
|
|
114
|
+
<Menu.Root {...menu} attendableId={attendableId}>
|
|
115
|
+
<Menu.Toolbar {...props} classNames={classNames} />
|
|
116
|
+
</Menu.Root>
|
|
101
117
|
);
|
|
102
118
|
};
|