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