@dxos/react-ui-table 0.8.3 → 0.8.4-main.1068cf700f
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-GTGYBRJA.mjs +1748 -0
- package/dist/lib/browser/chunk-GTGYBRJA.mjs.map +7 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs +76 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1156 -2508
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +114 -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-DPNSZHU5.mjs +1750 -0
- package/dist/lib/node-esm/chunk-DPNSZHU5.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs +77 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1156 -2508
- 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 +115 -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 +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 +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 +13 -9
- 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 +2 -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 +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 +4 -4
- package/dist/types/src/components/TableToolbar/TableToolbar.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 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +13 -0
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
- 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 +1 -2
- 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 +17 -7
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +149 -32
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +13 -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 +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 +25 -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/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 +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/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 +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 +69 -56
- package/src/components/Table/ColumnActionsMenu.tsx +11 -7
- package/src/components/Table/ColumnSettings.tsx +16 -12
- package/src/components/Table/DynamicTable.stories.tsx +29 -25
- package/src/components/Table/DynamicTable.tsx +34 -22
- package/src/components/Table/Relations.stories.tsx +237 -0
- package/src/components/Table/RowActionsMenu.tsx +26 -17
- package/src/components/Table/Table.stories.tsx +171 -194
- package/src/components/Table/Table.tsx +364 -286
- package/src/components/TableCellEditor/CellValidationMessage.tsx +8 -3
- package/src/components/TableCellEditor/FormCellEditor.tsx +146 -49
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +36 -76
- package/src/components/TableCellEditor/TableCellEditor.tsx +106 -198
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +49 -33
- package/src/components/testing.ts +41 -32
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +41 -35
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +24 -41
- package/src/model/selection-model.ts +75 -41
- package/src/model/table-model.test.ts +92 -56
- package/src/model/table-model.ts +628 -162
- package/src/model/table-presentation.test.ts +70 -59
- package/src/model/table-presentation.ts +278 -149
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +180 -41
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +133 -0
- package/src/translations.ts +7 -5
- package/src/types/Table.ts +90 -0
- package/src/types/index.ts +2 -2
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +50 -36
- package/src/util/index.ts +2 -1
- package/src/util/query-ast.ts +19 -0
- package/src/util/schema.ts +4 -4
- package/src/util/sort.ts +90 -0
- 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/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/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 -124
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/model/table-sorting.ts +0 -216
- package/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
- package/src/util/table.ts +0 -127
|
@@ -3,64 +3,63 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
-
import {
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
7
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 { tagPickerExtension, 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: '!plb-[--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.Entity.Any = Type.Entity.Any> = {
|
|
46
|
+
schema?: T;
|
|
47
47
|
model?: TableModel;
|
|
48
48
|
modals?: ModalController;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
|
|
49
|
+
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
50
|
+
onCreate?: OnCreateHandler;
|
|
51
|
+
onSave?: () => void;
|
|
53
52
|
};
|
|
54
53
|
|
|
55
|
-
export const TableValueEditor = ({
|
|
54
|
+
export const TableValueEditor = <T extends Type.Entity.Any = Type.Entity.Any>({
|
|
55
|
+
__gridScope,
|
|
56
|
+
schema,
|
|
56
57
|
model,
|
|
57
58
|
modals,
|
|
58
|
-
schema,
|
|
59
|
-
onEnter,
|
|
60
59
|
onFocus,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}: GridScopedProps<TableCellEditorProps
|
|
60
|
+
onSave,
|
|
61
|
+
onCreate,
|
|
62
|
+
}: GridScopedProps<TableCellEditorProps<T>>) => {
|
|
64
63
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
65
64
|
|
|
66
65
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
@@ -69,41 +68,46 @@ export const TableValueEditor = ({
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
const { col } = parseCellIndex(editing.index);
|
|
72
|
-
const field = model.projection.
|
|
71
|
+
const field = model.projection.getFields()[col];
|
|
73
72
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
74
73
|
invariant(fieldProjection);
|
|
75
74
|
return fieldProjection;
|
|
76
75
|
}, [model, editing]);
|
|
77
76
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
77
|
+
if (
|
|
78
|
+
fieldProjection?.props.type === TypeEnum.Array ||
|
|
79
|
+
fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
|
|
80
|
+
fieldProjection?.props.format === Format.TypeFormat.Ref
|
|
81
|
+
// TODO(thure): Support `Format.TypeFormat.MultiSelect`
|
|
82
|
+
) {
|
|
83
|
+
return (
|
|
84
|
+
<FormCellEditor<T>
|
|
85
|
+
__gridScope={__gridScope}
|
|
86
|
+
schema={schema}
|
|
87
|
+
model={model}
|
|
88
|
+
fieldProjection={fieldProjection}
|
|
89
|
+
modals={modals}
|
|
90
|
+
onSave={onSave}
|
|
91
|
+
onCreate={onCreate}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
80
94
|
}
|
|
81
95
|
|
|
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
|
-
);
|
|
96
|
+
// For all other types, use the existing cell editor.
|
|
97
|
+
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
93
98
|
};
|
|
94
99
|
|
|
95
100
|
export const TableCellEditor = ({
|
|
101
|
+
__gridScope,
|
|
96
102
|
model,
|
|
97
103
|
modals,
|
|
98
|
-
onEnter,
|
|
99
104
|
onFocus,
|
|
100
|
-
|
|
101
|
-
__gridScope,
|
|
105
|
+
onSave,
|
|
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,12 +115,13 @@ export const TableCellEditor = ({
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
const { col } = parseCellIndex(editing.index);
|
|
114
|
-
const field = model.projection.
|
|
118
|
+
const field = model.projection.getFields()[col];
|
|
115
119
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
116
120
|
invariant(fieldProjection);
|
|
117
121
|
return fieldProjection;
|
|
118
122
|
}, [model, editing]);
|
|
119
123
|
|
|
124
|
+
// TOOD(burdon): Attach to event handler?
|
|
120
125
|
const handleEnter = useCallback(
|
|
121
126
|
async (value: any) => {
|
|
122
127
|
if (!model || !editing) {
|
|
@@ -124,34 +129,39 @@ export const TableCellEditor = ({
|
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
const cell = parseCellIndex(editing.index);
|
|
132
|
+
const validationResult = await model.validateCellData(cell, value);
|
|
127
133
|
|
|
128
|
-
|
|
129
|
-
const result = await model.validateCellData(cell, value);
|
|
130
|
-
|
|
131
|
-
if (result.valid) {
|
|
134
|
+
if (validationResult.valid) {
|
|
132
135
|
setValidationError(null);
|
|
133
136
|
model.setCellData(cell, value);
|
|
134
|
-
onEnter?.(cell);
|
|
135
137
|
onFocus?.();
|
|
136
138
|
setEditing(null);
|
|
139
|
+
onSave?.();
|
|
137
140
|
} else {
|
|
138
|
-
setValidationError(
|
|
139
|
-
|
|
141
|
+
setValidationError(validationResult.error);
|
|
142
|
+
setValidationVariant('error');
|
|
140
143
|
}
|
|
141
144
|
},
|
|
142
|
-
[model, editing,
|
|
145
|
+
[model, editing, onFocus, setEditing],
|
|
143
146
|
);
|
|
144
147
|
|
|
145
148
|
const handleBlur = useCallback<EditorBlurHandler>(
|
|
146
149
|
async (value) => {
|
|
147
|
-
if (!model || !editing) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
150
|
if (suppressNextBlur.current) {
|
|
151
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
|
+
}
|
|
152
164
|
}
|
|
153
|
-
|
|
154
|
-
// Don't save on blur - let handleClose handle validation and saving
|
|
155
165
|
},
|
|
156
166
|
[model, editing],
|
|
157
167
|
);
|
|
@@ -163,177 +173,75 @@ export const TableCellEditor = ({
|
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
const cell = parseCellIndex(editing.index);
|
|
166
|
-
|
|
167
176
|
if (value !== undefined) {
|
|
168
177
|
// Pre-commit validation check.
|
|
169
178
|
const result = await model.validateCellData(cell, value);
|
|
170
179
|
|
|
171
180
|
if (result.valid) {
|
|
181
|
+
suppressNextBlur.current = true;
|
|
172
182
|
setValidationError(null);
|
|
173
183
|
model.setCellData(cell, value);
|
|
174
184
|
setEditing(null);
|
|
175
|
-
|
|
185
|
+
onSave?.();
|
|
176
186
|
if (event && onFocus) {
|
|
177
|
-
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
187
|
+
onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
|
|
178
188
|
}
|
|
179
189
|
} else {
|
|
180
190
|
setValidationError(result.error);
|
|
191
|
+
setValidationVariant('error');
|
|
181
192
|
}
|
|
182
193
|
} else {
|
|
194
|
+
suppressNextBlur.current = true;
|
|
183
195
|
setValidationError(null);
|
|
184
196
|
setEditing(null);
|
|
197
|
+
onSave?.();
|
|
185
198
|
if (event && onFocus) {
|
|
186
199
|
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
187
200
|
}
|
|
188
201
|
}
|
|
189
202
|
},
|
|
190
|
-
[model, editing, onFocus,
|
|
203
|
+
[model, editing, onFocus, fieldProjection, setEditing, onSave],
|
|
191
204
|
);
|
|
192
205
|
|
|
193
|
-
const
|
|
206
|
+
const extensions = useMemo(() => {
|
|
194
207
|
if (!fieldProjection) {
|
|
195
208
|
return [];
|
|
196
209
|
}
|
|
197
210
|
|
|
198
|
-
const
|
|
211
|
+
const extensions = [
|
|
199
212
|
editorKeys({
|
|
200
213
|
onClose: handleClose,
|
|
201
214
|
...(editing?.initialContent && { onNav: handleClose }),
|
|
202
215
|
}),
|
|
203
216
|
];
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
extension.push(
|
|
217
|
-
tagPickerExtension({
|
|
218
|
-
mode,
|
|
219
|
-
inGrid: true,
|
|
220
|
-
onSearch: (text, selectedIds) => {
|
|
221
|
-
return options
|
|
222
|
-
.filter(
|
|
223
|
-
(option) =>
|
|
224
|
-
selectedIds.indexOf(option.id) === -1 &&
|
|
225
|
-
(text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase())),
|
|
226
|
-
)
|
|
227
|
-
.map((option) => ({
|
|
228
|
-
id: option.id,
|
|
229
|
-
label: option.title,
|
|
230
|
-
hue: option.color as any,
|
|
231
|
-
}));
|
|
232
|
-
},
|
|
233
|
-
onUpdate: (ids) => {
|
|
234
|
-
if (model && editing) {
|
|
235
|
-
if (ids.length === 0) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (mode === 'single-select') {
|
|
239
|
-
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);
|
|
240
229
|
} else {
|
|
241
|
-
|
|
230
|
+
setValidationError(result.error);
|
|
231
|
+
setValidationVariant('error');
|
|
242
232
|
}
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
|
|
233
|
+
});
|
|
234
|
+
}, 10),
|
|
235
|
+
),
|
|
246
236
|
);
|
|
247
237
|
}
|
|
248
238
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
case FormatEnum.Ref: {
|
|
252
|
-
extension.push([
|
|
253
|
-
completion({
|
|
254
|
-
onQuery: (text) => onQuery(fieldProjection, text),
|
|
255
|
-
onMatch: (data) => {
|
|
256
|
-
if (model && editing && modals) {
|
|
257
|
-
if (isCreateOption(data)) {
|
|
258
|
-
const { field, props } = fieldProjection;
|
|
259
|
-
if (props.referenceSchema) {
|
|
260
|
-
suppressNextBlur.current = true;
|
|
261
|
-
modals.openCreateRef(
|
|
262
|
-
props.referenceSchema,
|
|
263
|
-
document.querySelector(cellQuery(editing.index, gridId)),
|
|
264
|
-
{
|
|
265
|
-
[field.referencePath!]: data.text,
|
|
266
|
-
},
|
|
267
|
-
(data) => {
|
|
268
|
-
void handleEnter(data);
|
|
269
|
-
},
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
} else {
|
|
273
|
-
void handleEnter(data);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
}),
|
|
278
|
-
]);
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return extension;
|
|
285
|
-
}, [model, modals, editing, fieldProjection, handleClose, themeMode]);
|
|
239
|
+
return extensions;
|
|
240
|
+
}, [model, modals, editing, fieldProjection, handleClose]);
|
|
286
241
|
|
|
287
242
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
288
243
|
if (model && editing) {
|
|
289
244
|
const cell = parseCellIndex(editing.index);
|
|
290
|
-
const { col } = cell;
|
|
291
|
-
const field = model.projection.view.fields[col];
|
|
292
|
-
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
296
|
-
fieldProjection?.props.format === FormatEnum.MultiSelect
|
|
297
|
-
) {
|
|
298
|
-
const value = model.getCellData(cell);
|
|
299
|
-
|
|
300
|
-
if (value !== undefined) {
|
|
301
|
-
const options = fieldProjection.props.options || [];
|
|
302
|
-
|
|
303
|
-
if (fieldProjection.props.format === FormatEnum.MultiSelect && Array.isArray(value)) {
|
|
304
|
-
const tagItems = value
|
|
305
|
-
.map((id) => {
|
|
306
|
-
const option = options.find((o) => o.id === id);
|
|
307
|
-
if (option) {
|
|
308
|
-
return {
|
|
309
|
-
id,
|
|
310
|
-
label: option.title,
|
|
311
|
-
hue: option.color as any,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
return undefined;
|
|
315
|
-
})
|
|
316
|
-
.filter((item): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
317
|
-
|
|
318
|
-
return createLinks(tagItems);
|
|
319
|
-
} else {
|
|
320
|
-
const option = options.find((o) => o.id === value);
|
|
321
|
-
|
|
322
|
-
if (option) {
|
|
323
|
-
const tagItem = {
|
|
324
|
-
id: value,
|
|
325
|
-
label: option.title,
|
|
326
|
-
hue: option.color as any,
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
return createLinks([tagItem]);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return '';
|
|
335
|
-
}
|
|
336
|
-
|
|
337
245
|
const value = model.getCellData(cell);
|
|
338
246
|
return value !== undefined ? String(value) : '';
|
|
339
247
|
}
|
|
@@ -341,8 +249,8 @@ export const TableCellEditor = ({
|
|
|
341
249
|
|
|
342
250
|
return (
|
|
343
251
|
<>
|
|
344
|
-
<CellValidationMessage validationError={
|
|
345
|
-
<GridCellEditor
|
|
252
|
+
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
253
|
+
<GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
346
254
|
</>
|
|
347
255
|
);
|
|
348
256
|
};
|
|
@@ -2,23 +2,21 @@
|
|
|
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,
|
|
14
|
-
createGapSeparator,
|
|
15
|
-
createMenuAction,
|
|
16
13
|
type MenuAction,
|
|
17
14
|
MenuProvider,
|
|
18
15
|
ToolbarMenu,
|
|
16
|
+
createGapSeparator,
|
|
17
|
+
createMenuAction,
|
|
19
18
|
useMenuActions,
|
|
20
19
|
} from '@dxos/react-ui-menu';
|
|
21
|
-
import { rxFromSignal } from '@dxos/react-ui-menu';
|
|
22
20
|
|
|
23
21
|
import { translationKey } from '../../translations';
|
|
24
22
|
|
|
@@ -30,49 +28,60 @@ export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
|
|
|
30
28
|
|
|
31
29
|
type TableToolbarState = Partial<{ viewDirty: boolean }>;
|
|
32
30
|
|
|
31
|
+
// TODO(burdon): Radix style layout.
|
|
32
|
+
|
|
33
33
|
export type TableToolbarProps = ThemedClassName<
|
|
34
34
|
TableToolbarState & {
|
|
35
|
-
onAdd
|
|
36
|
-
onSave
|
|
35
|
+
onAdd?: () => void;
|
|
36
|
+
onSave?: () => void;
|
|
37
37
|
attendableId?: string;
|
|
38
|
-
customActions?:
|
|
38
|
+
customActions?: Atom.Atom<ActionGraphProps>;
|
|
39
39
|
}
|
|
40
40
|
>;
|
|
41
41
|
|
|
42
42
|
const createTableToolbarActions = ({
|
|
43
|
-
|
|
43
|
+
stateAtom,
|
|
44
44
|
onAdd,
|
|
45
45
|
onSave,
|
|
46
46
|
customActions,
|
|
47
47
|
}: {
|
|
48
|
-
|
|
49
|
-
onAdd
|
|
50
|
-
onSave
|
|
51
|
-
customActions?:
|
|
48
|
+
stateAtom: Atom.Atom<TableToolbarState>;
|
|
49
|
+
onAdd?: () => void;
|
|
50
|
+
onSave?: () => void;
|
|
51
|
+
customActions?: Atom.Atom<ActionGraphProps>;
|
|
52
52
|
}) =>
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
Atom.make((get) => {
|
|
54
|
+
const nodes: ActionGraphNodes = [];
|
|
55
|
+
if (onAdd) {
|
|
56
|
+
const add = createMenuAction<TableToolbarActionProperties>('add-row', onAdd, {
|
|
57
|
+
type: 'add-row' as const,
|
|
58
|
+
icon: 'ph--plus--regular',
|
|
59
|
+
label: ['add row label', { ns: translationKey }],
|
|
60
|
+
testId: 'table.toolbar.add-row',
|
|
61
|
+
});
|
|
62
|
+
nodes.push(add);
|
|
63
|
+
}
|
|
64
|
+
if (onSave) {
|
|
65
|
+
const state = get(stateAtom);
|
|
66
|
+
const save = createMenuAction<TableToolbarActionProperties>('save-view', onSave, {
|
|
67
|
+
type: 'save-view' as const,
|
|
68
|
+
icon: 'ph--floppy-disk--regular',
|
|
69
|
+
label: ['save view label', { ns: translationKey }],
|
|
70
|
+
testId: 'table.toolbar.save-view',
|
|
71
|
+
iconOnly: false,
|
|
72
|
+
hidden: !state.viewDirty,
|
|
73
|
+
});
|
|
74
|
+
nodes.push(save);
|
|
75
|
+
}
|
|
68
76
|
const gap = createGapSeparator();
|
|
69
|
-
|
|
77
|
+
nodes.push(...gap.nodes);
|
|
70
78
|
const edges: ActionGraphEdges = nodes.map(({ id: target }) => ({ source: 'root', target }));
|
|
71
79
|
if (customActions) {
|
|
72
80
|
const custom = get(customActions);
|
|
73
81
|
nodes.push(...custom.nodes);
|
|
74
82
|
edges.push(...custom.edges);
|
|
75
83
|
}
|
|
84
|
+
|
|
76
85
|
return {
|
|
77
86
|
nodes,
|
|
78
87
|
edges,
|
|
@@ -87,10 +96,17 @@ export const TableToolbar = ({
|
|
|
87
96
|
onSave,
|
|
88
97
|
customActions,
|
|
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
|
|