@dxos/react-ui-table 0.8.4-main.c1de068 → 0.8.4-main.e098934
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-63UUF4IG.mjs +66 -0
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5XLSHLOG.mjs → chunk-BBXDTWCX.mjs} +537 -466
- package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +730 -669
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +9 -6
- 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-K734DJB7.mjs +67 -0
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-Y66YLSTC.mjs → chunk-RNQDU7IQ.mjs} +537 -466
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +730 -669
- 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 +9 -6
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +50 -2
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +44 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +3 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +42 -3
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -3
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -15
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +2 -1
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +9 -7
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +2 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +2 -3
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +2 -21
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +24 -4
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +2 -2
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts.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 +47 -42
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +1 -1
- package/src/components/Table/CreateRefPanel.tsx +25 -8
- package/src/components/Table/DynamicTable.stories.tsx +7 -4
- package/src/components/Table/DynamicTable.tsx +4 -3
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +40 -23
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +39 -28
- package/src/components/Table/Table.tsx +138 -112
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +143 -42
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +12 -24
- package/src/components/TableCellEditor/TableCellEditor.tsx +74 -137
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +1 -1
- package/src/hooks/useAddRow.ts +6 -4
- package/src/hooks/useTableModel.ts +1 -1
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +3 -2
- package/src/model/table-model.test.ts +9 -5
- package/src/model/table-model.ts +35 -24
- package/src/model/table-presentation.test.ts +10 -5
- package/src/model/table-presentation.ts +72 -30
- package/src/model/table-sorting.ts +16 -7
- package/src/playwright/smoke.spec.ts +129 -37
- package/src/testing/useTestTableModel.ts +7 -5
- package/src/translations.ts +2 -2
- package/src/types/index.ts +1 -1
- package/src/types/schema.ts +62 -5
- package/src/util/dynamic-table.ts +10 -9
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +1 -1
- package/src/util/table-buttons.ts +4 -3
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +0 -7
- package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -11
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/util/table.ts +0 -49
|
@@ -13,16 +13,16 @@ import { invariant } from '@dxos/invariant';
|
|
|
13
13
|
import { faker } from '@dxos/random';
|
|
14
14
|
import { Filter, useQuery } from '@dxos/react-client/echo';
|
|
15
15
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
16
|
-
import {
|
|
17
|
-
import { createDefaultSchema,
|
|
16
|
+
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
17
|
+
import { DataType, createDefaultSchema, typenameFromQuery } from '@dxos/schema';
|
|
18
18
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
19
19
|
|
|
20
|
-
import { TableCellEditor, type TableCellEditorProps } from './TableCellEditor';
|
|
21
20
|
import { useTableModel } from '../../hooks';
|
|
22
21
|
import { type TableFeatures } from '../../model';
|
|
23
22
|
import { translations } from '../../translations';
|
|
24
|
-
import {
|
|
25
|
-
|
|
23
|
+
import { Table } from '../../types';
|
|
24
|
+
|
|
25
|
+
import { TableCellEditor } from './TableCellEditor';
|
|
26
26
|
|
|
27
27
|
type StoryProps = {
|
|
28
28
|
editing: GridEditing;
|
|
@@ -39,7 +39,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
39
39
|
if (space && views.length && !view) {
|
|
40
40
|
const view = views[0];
|
|
41
41
|
setView(view);
|
|
42
|
-
setSchema(space.db.schemaRegistry.getSchema(view.query
|
|
42
|
+
setSchema(space.db.schemaRegistry.getSchema(typenameFromQuery(view.query)!));
|
|
43
43
|
}
|
|
44
44
|
}, [space, views, view]);
|
|
45
45
|
|
|
@@ -54,18 +54,6 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
54
54
|
|
|
55
55
|
const model = useTableModel({ view, schema: schema?.jsonSchema, features });
|
|
56
56
|
|
|
57
|
-
const handleQuery: TableCellEditorProps['onQuery'] = async ({ field }) => {
|
|
58
|
-
// TODO(dmaretskyi): If no schema query nothing
|
|
59
|
-
const { objects } = await space.db.query(schema ? Filter.type(schema) : Filter.everything()).run();
|
|
60
|
-
return objects.map((obj) => {
|
|
61
|
-
const label = obj[field.referencePath ?? 'id'];
|
|
62
|
-
return {
|
|
63
|
-
label,
|
|
64
|
-
data: obj,
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
|
|
69
57
|
if (!model || !schema || !view) {
|
|
70
58
|
return <div />;
|
|
71
59
|
}
|
|
@@ -73,25 +61,25 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
73
61
|
return (
|
|
74
62
|
<div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
|
|
75
63
|
<Grid.Root id='test' editing={editing}>
|
|
76
|
-
<TableCellEditor model={model}
|
|
64
|
+
<TableCellEditor model={model} schema={schema} />
|
|
77
65
|
</Grid.Root>
|
|
78
66
|
</div>
|
|
79
67
|
);
|
|
80
68
|
};
|
|
81
69
|
|
|
82
|
-
const meta
|
|
70
|
+
const meta = {
|
|
83
71
|
title: 'ui/react-ui-table/TableCellEditor',
|
|
84
72
|
component: DefaultStory,
|
|
85
73
|
render: DefaultStory,
|
|
86
74
|
parameters: { translations, layout: 'centered' },
|
|
87
75
|
decorators: [
|
|
88
76
|
withClientProvider({
|
|
89
|
-
types: [DataType.View,
|
|
77
|
+
types: [DataType.View, Table.Table],
|
|
90
78
|
createIdentity: true,
|
|
91
79
|
createSpace: true,
|
|
92
80
|
onSpaceCreated: async ({ client, space }) => {
|
|
93
81
|
const schema = createDefaultSchema();
|
|
94
|
-
const { view } = await
|
|
82
|
+
const { view } = await Table.makeView({ client, space, typename: schema.typename });
|
|
95
83
|
space.db.add(view);
|
|
96
84
|
Array.from({ length: 10 }).forEach(() => {
|
|
97
85
|
space.db.add(
|
|
@@ -107,11 +95,11 @@ const meta: Meta<StoryProps> = {
|
|
|
107
95
|
withTheme,
|
|
108
96
|
withLayout(),
|
|
109
97
|
],
|
|
110
|
-
}
|
|
98
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
111
99
|
|
|
112
100
|
export default meta;
|
|
113
101
|
|
|
114
|
-
type Story = StoryObj<
|
|
102
|
+
type Story = StoryObj<typeof meta>;
|
|
115
103
|
|
|
116
104
|
export const Default: Story = {
|
|
117
105
|
args: {
|
|
@@ -3,64 +3,59 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
6
7
|
import { type Schema } from 'effect/Schema';
|
|
7
|
-
import React, { useCallback,
|
|
8
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
8
9
|
|
|
10
|
+
import { debounce } from '@dxos/async';
|
|
11
|
+
import type { Client } from '@dxos/client';
|
|
9
12
|
import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
|
|
10
13
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { type
|
|
12
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
14
|
+
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
13
15
|
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
14
16
|
import {
|
|
15
|
-
|
|
16
|
-
editorKeys,
|
|
17
|
-
parseCellIndex,
|
|
18
|
-
useGridContext,
|
|
19
|
-
type DxGridPlanePosition,
|
|
17
|
+
type EditorBlurHandler,
|
|
20
18
|
type EditorKeyEvent,
|
|
21
19
|
type EditorKeyOrBlurHandler,
|
|
22
|
-
type EditorBlurHandler,
|
|
23
20
|
GridCellEditor,
|
|
24
21
|
type GridCellEditorProps,
|
|
25
22
|
type GridScopedProps,
|
|
23
|
+
editorKeys,
|
|
24
|
+
parseCellIndex,
|
|
25
|
+
useGridContext,
|
|
26
26
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import {
|
|
27
|
+
import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
|
|
28
28
|
import { type FieldProjection } from '@dxos/schema';
|
|
29
29
|
|
|
30
|
+
import { type ModalController, type TableModel } from '../../model';
|
|
31
|
+
|
|
30
32
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
33
|
import { FormCellEditor } from './FormCellEditor';
|
|
32
|
-
import { completion } from './extension';
|
|
33
|
-
import { type TableModel, type ModalController } from '../../model';
|
|
34
|
-
|
|
35
|
-
const newValue = Symbol.for('newValue');
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
36
|
* Option to create new object/value.
|
|
39
37
|
*/
|
|
40
|
-
export const createOption = (text: string) => ({ [newValue]: true, text });
|
|
41
|
-
|
|
42
|
-
const isCreateOption = (data: any) => typeof data === 'object' && data[newValue];
|
|
43
|
-
|
|
44
38
|
export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
45
39
|
|
|
46
40
|
export type TableCellEditorProps = {
|
|
47
41
|
model?: TableModel;
|
|
48
42
|
modals?: ModalController;
|
|
49
43
|
schema?: Schema.AnyNoContext;
|
|
50
|
-
|
|
51
|
-
onFocus?: DxGrid['refocus'];
|
|
44
|
+
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
52
45
|
onSave?: () => void;
|
|
53
|
-
|
|
46
|
+
client?: Client;
|
|
54
47
|
};
|
|
55
48
|
|
|
49
|
+
const adaptValidationMessage = (message: string | null) =>
|
|
50
|
+
message ? (message.endsWith('is missing') ? 'Can’t be blank' : message) : null;
|
|
51
|
+
|
|
56
52
|
export const TableValueEditor = ({
|
|
57
53
|
model,
|
|
58
54
|
modals,
|
|
59
55
|
schema,
|
|
60
|
-
onEnter,
|
|
61
56
|
onFocus,
|
|
62
57
|
onSave,
|
|
63
|
-
|
|
58
|
+
client,
|
|
64
59
|
__gridScope,
|
|
65
60
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
66
61
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
@@ -79,7 +74,8 @@ export const TableValueEditor = ({
|
|
|
79
74
|
|
|
80
75
|
if (
|
|
81
76
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
82
|
-
fieldProjection?.props.format === FormatEnum.SingleSelect
|
|
77
|
+
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
78
|
+
fieldProjection?.props.format === FormatEnum.Ref
|
|
83
79
|
// TODO(thure): Support `FormatEnum.MultiSelect`
|
|
84
80
|
) {
|
|
85
81
|
return (
|
|
@@ -89,40 +85,29 @@ export const TableValueEditor = ({
|
|
|
89
85
|
schema={schema}
|
|
90
86
|
__gridScope={__gridScope}
|
|
91
87
|
onSave={onSave}
|
|
88
|
+
client={client}
|
|
89
|
+
modals={modals}
|
|
92
90
|
/>
|
|
93
91
|
);
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
// 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
|
-
);
|
|
95
|
+
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
108
96
|
};
|
|
109
97
|
|
|
110
|
-
const editorSlots = {
|
|
98
|
+
const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
111
99
|
|
|
112
100
|
export const TableCellEditor = ({
|
|
113
101
|
model,
|
|
114
102
|
modals,
|
|
115
|
-
onEnter,
|
|
116
103
|
onFocus,
|
|
117
|
-
onQuery,
|
|
118
104
|
onSave,
|
|
119
105
|
__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) {
|
|
@@ -136,38 +121,6 @@ export const TableCellEditor = ({
|
|
|
136
121
|
return fieldProjection;
|
|
137
122
|
}, [model, editing]);
|
|
138
123
|
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
// Check for existing validation errors when editing starts (for draft rows).
|
|
141
|
-
if (!model || !editing || !fieldProjection) {
|
|
142
|
-
setValidationError(null);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const cell = parseCellIndex(editing.index);
|
|
147
|
-
const { row, col } = cell;
|
|
148
|
-
|
|
149
|
-
if (model.isDraftCell(cell)) {
|
|
150
|
-
const field = model.projection.fields[col];
|
|
151
|
-
const hasValidationError = model.hasDraftRowValidationError(row, field.path);
|
|
152
|
-
|
|
153
|
-
if (hasValidationError) {
|
|
154
|
-
const draftRows = model.draftRows.value;
|
|
155
|
-
if (row >= 0 && row < draftRows.length) {
|
|
156
|
-
const draftRow = draftRows[row];
|
|
157
|
-
const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
|
|
158
|
-
if (validationError) {
|
|
159
|
-
setValidationError(validationError.message);
|
|
160
|
-
setValidationVariant('warning');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
setValidationError(null);
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
setValidationError(null);
|
|
168
|
-
}
|
|
169
|
-
}, [model, editing, fieldProjection]);
|
|
170
|
-
|
|
171
124
|
const handleEnter = useCallback(
|
|
172
125
|
async (value: any) => {
|
|
173
126
|
if (!model || !editing) {
|
|
@@ -180,28 +133,34 @@ export const TableCellEditor = ({
|
|
|
180
133
|
if (validationResult.valid) {
|
|
181
134
|
setValidationError(null);
|
|
182
135
|
model.setCellData(cell, value);
|
|
183
|
-
onEnter?.(cell);
|
|
184
136
|
onFocus?.();
|
|
185
137
|
setEditing(null);
|
|
186
138
|
onSave?.();
|
|
187
139
|
} else {
|
|
188
|
-
setValidationError(validationResult.error);
|
|
140
|
+
setValidationError(adaptValidationMessage(validationResult.error));
|
|
189
141
|
setValidationVariant('error');
|
|
190
142
|
}
|
|
191
143
|
},
|
|
192
|
-
[model, editing,
|
|
144
|
+
[model, editing, onFocus, setEditing],
|
|
193
145
|
);
|
|
194
146
|
|
|
195
147
|
const handleBlur = useCallback<EditorBlurHandler>(
|
|
196
148
|
async (value) => {
|
|
197
|
-
if (!model || !editing) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
149
|
if (suppressNextBlur.current) {
|
|
201
150
|
suppressNextBlur.current = false;
|
|
151
|
+
} else if (model && editing) {
|
|
152
|
+
// Save silently if validation passes
|
|
153
|
+
const cell = parseCellIndex(editing.index);
|
|
154
|
+
if (value !== undefined) {
|
|
155
|
+
const result = await model.validateCellData(cell, value);
|
|
156
|
+
if (result.valid) {
|
|
157
|
+
setValidationError(null);
|
|
158
|
+
model.setCellData(cell, value);
|
|
159
|
+
setEditing(null);
|
|
160
|
+
onSave?.();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
202
163
|
}
|
|
203
|
-
|
|
204
|
-
// Don't save on blur - let handleClose handle validation and saving
|
|
205
164
|
},
|
|
206
165
|
[model, editing],
|
|
207
166
|
);
|
|
@@ -213,25 +172,25 @@ export const TableCellEditor = ({
|
|
|
213
172
|
}
|
|
214
173
|
|
|
215
174
|
const cell = parseCellIndex(editing.index);
|
|
216
|
-
|
|
217
175
|
if (value !== undefined) {
|
|
218
176
|
// Pre-commit validation check.
|
|
219
177
|
const result = await model.validateCellData(cell, value);
|
|
220
178
|
|
|
221
179
|
if (result.valid) {
|
|
180
|
+
suppressNextBlur.current = true;
|
|
222
181
|
setValidationError(null);
|
|
223
182
|
model.setCellData(cell, value);
|
|
224
183
|
setEditing(null);
|
|
225
184
|
onSave?.();
|
|
226
|
-
onEnter?.(cell);
|
|
227
185
|
if (event && onFocus) {
|
|
228
|
-
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
186
|
+
onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
|
|
229
187
|
}
|
|
230
188
|
} else {
|
|
231
189
|
setValidationError(result.error);
|
|
232
190
|
setValidationVariant('error');
|
|
233
191
|
}
|
|
234
192
|
} else {
|
|
193
|
+
suppressNextBlur.current = true;
|
|
235
194
|
setValidationError(null);
|
|
236
195
|
setEditing(null);
|
|
237
196
|
onSave?.();
|
|
@@ -240,15 +199,15 @@ export const TableCellEditor = ({
|
|
|
240
199
|
}
|
|
241
200
|
}
|
|
242
201
|
},
|
|
243
|
-
[model, editing, onFocus,
|
|
202
|
+
[model, editing, onFocus, fieldProjection, setEditing, onSave],
|
|
244
203
|
);
|
|
245
204
|
|
|
246
|
-
const
|
|
205
|
+
const extensions = useMemo(() => {
|
|
247
206
|
if (!fieldProjection) {
|
|
248
207
|
return [];
|
|
249
208
|
}
|
|
250
209
|
|
|
251
|
-
const
|
|
210
|
+
const extensions = [
|
|
252
211
|
editorKeys({
|
|
253
212
|
onClose: handleClose,
|
|
254
213
|
...(editing?.initialContent && { onNav: handleClose }),
|
|
@@ -256,17 +215,14 @@ export const TableCellEditor = ({
|
|
|
256
215
|
];
|
|
257
216
|
|
|
258
217
|
const format = fieldProjection.props.format;
|
|
259
|
-
|
|
260
218
|
if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
|
|
261
219
|
// TODO(ZaymonFC): Reconcile this with the TagPicker component?
|
|
262
|
-
// Add markdown extensions needed by tag picker.
|
|
263
|
-
extension.push(createMarkdownExtensions({ themeMode }));
|
|
264
|
-
|
|
265
220
|
const options = fieldProjection.props.options || [];
|
|
266
|
-
|
|
267
221
|
const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
|
|
268
222
|
|
|
269
|
-
|
|
223
|
+
// Add markdown extensions needed by tag picker.
|
|
224
|
+
extensions.push(createMarkdownExtensions());
|
|
225
|
+
extensions.push(
|
|
270
226
|
tagPicker({
|
|
271
227
|
mode,
|
|
272
228
|
keymap: false,
|
|
@@ -299,43 +255,30 @@ export const TableCellEditor = ({
|
|
|
299
255
|
);
|
|
300
256
|
}
|
|
301
257
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
},
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
void handleEnter(data);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
}),
|
|
331
|
-
]);
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
258
|
+
// Add validation extension to handle content changes.
|
|
259
|
+
if (model && editing) {
|
|
260
|
+
extensions.push(
|
|
261
|
+
EditorView.updateListener.of(
|
|
262
|
+
debounce((update) => {
|
|
263
|
+
const content = update.state.doc.toString();
|
|
264
|
+
const cell = parseCellIndex(editing.index);
|
|
265
|
+
|
|
266
|
+
// Perform validation on content change.
|
|
267
|
+
void model.validateCellData(cell, content).then((result) => {
|
|
268
|
+
if (result.valid) {
|
|
269
|
+
setValidationError(null);
|
|
270
|
+
} else {
|
|
271
|
+
setValidationError(result.error);
|
|
272
|
+
setValidationVariant('error');
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}, 10),
|
|
276
|
+
),
|
|
277
|
+
);
|
|
335
278
|
}
|
|
336
279
|
|
|
337
|
-
return
|
|
338
|
-
}, [model, modals, editing, fieldProjection, handleClose
|
|
280
|
+
return extensions;
|
|
281
|
+
}, [model, modals, editing, fieldProjection, handleClose]);
|
|
339
282
|
|
|
340
283
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
341
284
|
if (model && editing) {
|
|
@@ -349,10 +292,8 @@ export const TableCellEditor = ({
|
|
|
349
292
|
fieldProjection?.props.format === FormatEnum.MultiSelect
|
|
350
293
|
) {
|
|
351
294
|
const value = model.getCellData(cell);
|
|
352
|
-
|
|
353
295
|
if (value !== undefined) {
|
|
354
296
|
const options = fieldProjection.props.options || [];
|
|
355
|
-
|
|
356
297
|
if (fieldProjection.props.format === FormatEnum.MultiSelect) {
|
|
357
298
|
const tagItems = value
|
|
358
299
|
.split(',')
|
|
@@ -395,12 +336,8 @@ export const TableCellEditor = ({
|
|
|
395
336
|
|
|
396
337
|
return (
|
|
397
338
|
<>
|
|
398
|
-
<CellValidationMessage
|
|
399
|
-
|
|
400
|
-
variant={_validationVariant}
|
|
401
|
-
__gridScope={__gridScope}
|
|
402
|
-
/>
|
|
403
|
-
<GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
339
|
+
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
340
|
+
<GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
404
341
|
</>
|
|
405
342
|
);
|
|
406
343
|
};
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
type ActionGraphEdges,
|
|
12
12
|
type ActionGraphNodes,
|
|
13
13
|
type ActionGraphProps,
|
|
14
|
-
createGapSeparator,
|
|
15
|
-
createMenuAction,
|
|
16
14
|
type MenuAction,
|
|
17
15
|
MenuProvider,
|
|
18
16
|
ToolbarMenu,
|
|
17
|
+
createGapSeparator,
|
|
18
|
+
createMenuAction,
|
|
19
19
|
useMenuActions,
|
|
20
20
|
} from '@dxos/react-ui-menu';
|
|
21
21
|
import { rxFromSignal } from '@dxos/react-ui-menu';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import { FormatEnum, ObjectId,
|
|
8
|
+
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo-schema';
|
|
9
9
|
import { live } from '@dxos/live-object';
|
|
10
10
|
import { faker } from '@dxos/random';
|
|
11
11
|
import { type ProjectionModel } from '@dxos/schema';
|
package/src/hooks/useAddRow.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { useCallback } from 'react';
|
|
|
8
8
|
import { type Space } from '@dxos/client/echo';
|
|
9
9
|
import { Obj } from '@dxos/echo';
|
|
10
10
|
|
|
11
|
+
import { type InsertRowResult } from '../model';
|
|
12
|
+
|
|
11
13
|
export type UseAddRowParams = {
|
|
12
14
|
space?: Space;
|
|
13
15
|
schema?: Schema.Schema.AnyNoContext;
|
|
@@ -20,16 +22,16 @@ export type UseAddRowParams = {
|
|
|
20
22
|
*/
|
|
21
23
|
export const useAddRow = ({ space, schema }: UseAddRowParams) => {
|
|
22
24
|
return useCallback(
|
|
23
|
-
(data?: any) => {
|
|
25
|
+
(data?: any): InsertRowResult => {
|
|
24
26
|
if (space && schema) {
|
|
25
27
|
try {
|
|
26
28
|
space.db.add(Obj.make(schema, data ?? {}));
|
|
27
|
-
return
|
|
29
|
+
return 'final';
|
|
28
30
|
} catch (error) {
|
|
29
|
-
return
|
|
31
|
+
return 'draft';
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
return
|
|
34
|
+
return 'final';
|
|
33
35
|
},
|
|
34
36
|
[space, schema],
|
|
35
37
|
);
|
|
@@ -13,7 +13,7 @@ import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
|
13
13
|
import { type DataType, type ProjectionModel } from '@dxos/schema';
|
|
14
14
|
import { isNonNullable } from '@dxos/util';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
|
|
17
17
|
|
|
18
18
|
export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
19
19
|
view?: DataType.View;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
|
-
import { type
|
|
6
|
+
import { type MutableRefObject, type RefObject } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type Live } from '@dxos/live-object';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type ReadonlySignal, type Signal, computed, effect, signal } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Resource } from '@dxos/context';
|
|
8
8
|
|
|
9
|
-
import { type TableRow } from './table-model';
|
|
10
9
|
import { touch } from '../util';
|
|
11
10
|
|
|
11
|
+
import { type TableRow } from './table-model';
|
|
12
|
+
|
|
12
13
|
export type SelectionMode = 'single' | 'multiple';
|
|
13
14
|
|
|
14
15
|
export class SelectionModel<T extends TableRow> extends Resource {
|
|
@@ -6,7 +6,7 @@ import { computed } from '@preact/signals-core';
|
|
|
6
6
|
import { Schema } from 'effect';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { Filter, Query } from '@dxos/echo';
|
|
10
10
|
import { TypedObject } from '@dxos/echo-schema';
|
|
11
11
|
import { updateCounter } from '@dxos/echo-schema/testing';
|
|
12
12
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
@@ -14,8 +14,9 @@ import { live } from '@dxos/live-object';
|
|
|
14
14
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
15
15
|
import { createView } from '@dxos/schema';
|
|
16
16
|
|
|
17
|
+
import { Table } from '../types';
|
|
18
|
+
|
|
17
19
|
import { TableModel, type TableModelProps } from './table-model';
|
|
18
|
-
import { TableView } from '../types';
|
|
19
20
|
|
|
20
21
|
// TODO(burdon): Tests are disabled in project.json since they bring in plugin deps.
|
|
21
22
|
// Restore once factored out into react-ui-table.
|
|
@@ -72,7 +73,6 @@ describe('TableModel', () => {
|
|
|
72
73
|
describe('reactivity', () => {
|
|
73
74
|
it('pure signals should nest', () => {
|
|
74
75
|
const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
|
|
75
|
-
|
|
76
76
|
const computed$ = computed(() => {
|
|
77
77
|
return signal$.arr.map((row) =>
|
|
78
78
|
computed(() => {
|
|
@@ -107,7 +107,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
107
107
|
|
|
108
108
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
109
109
|
const schema = createEchoSchema(Test);
|
|
110
|
-
const table =
|
|
111
|
-
const view = createView({
|
|
110
|
+
const table = Table.make();
|
|
111
|
+
const view = createView({
|
|
112
|
+
query: Query.select(Filter.type(schema)),
|
|
113
|
+
jsonSchema: schema.jsonSchema,
|
|
114
|
+
presentation: table,
|
|
115
|
+
});
|
|
112
116
|
return new TableModel({ view, schema: schema.jsonSchema, ...props });
|
|
113
117
|
};
|