@dxos/react-ui-table 0.8.4-main.67995b8 → 0.8.4-main.70d3990
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-5XWSB3JP.mjs +71 -0
- package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5XLSHLOG.mjs → chunk-KNC7XYT6.mjs} +573 -541
- package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +625 -698
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +16 -24
- 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-Y66YLSTC.mjs → chunk-CWSGS3Y2.mjs} +573 -541
- package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MKTMOY46.mjs +72 -0
- package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +625 -698
- 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 +16 -24
- 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/DynamicTable.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +51 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +108 -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 +8 -4
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +105 -4
- 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 +12 -6
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -17
- 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 +2 -2
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -5
- 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 +3 -2
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +5 -6
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +0 -7
- 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 +14 -14
- 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 +9 -6
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +1 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +21 -34
- 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/Table.d.ts +28 -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 +9 -9
- 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/schema.d.ts +1 -1
- package/dist/types/src/util/schema.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 +61 -59
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +1 -1
- package/src/components/Table/DynamicTable.stories.tsx +21 -17
- package/src/components/Table/DynamicTable.tsx +19 -13
- package/src/components/Table/Relations.stories.tsx +237 -0
- package/src/components/Table/RowActionsMenu.tsx +23 -15
- package/src/components/Table/Table.stories.tsx +103 -86
- package/src/components/Table/Table.tsx +140 -115
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +28 -66
- package/src/components/TableCellEditor/TableCellEditor.tsx +75 -225
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +34 -24
- package/src/components/testing.ts +4 -4
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +7 -5
- package/src/hooks/useProjectionModel.ts +19 -0
- package/src/hooks/useTableModel.ts +12 -14
- package/src/model/modal-controller.ts +1 -29
- package/src/model/selection-model.ts +3 -2
- package/src/model/table-model.test.ts +21 -12
- package/src/model/table-model.ts +62 -56
- package/src/model/table-presentation.test.ts +25 -23
- package/src/model/table-presentation.ts +82 -41
- package/src/model/table-sorting.ts +32 -14
- package/src/playwright/TableManager.ts +3 -3
- package/src/playwright/smoke.spec.ts +132 -38
- package/src/testing/useTestTableModel.ts +34 -22
- package/src/translations.ts +2 -2
- package/src/types/Table.ts +89 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.ts +20 -24
- package/src/util/index.ts +0 -1
- package/src/util/schema.ts +4 -4
- 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/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/types/schema.d.ts +0 -8
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -11
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/Table/relations.stories.tsx +0 -116
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/types/schema.ts +0 -16
- package/src/util/table.ts +0 -49
|
@@ -2,100 +2,60 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import React
|
|
6
|
+
import React from 'react';
|
|
9
7
|
|
|
10
8
|
import { Obj } from '@dxos/echo';
|
|
11
|
-
import { type EchoSchema, isMutable } from '@dxos/echo-schema';
|
|
12
|
-
import { invariant } from '@dxos/invariant';
|
|
13
9
|
import { faker } from '@dxos/random';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
10
|
+
import { withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
12
|
+
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
13
|
+
import { View } from '@dxos/schema';
|
|
14
|
+
import { Task } from '@dxos/types';
|
|
19
15
|
|
|
20
|
-
import {
|
|
21
|
-
import { useTableModel } from '../../hooks';
|
|
22
|
-
import { type TableFeatures } from '../../model';
|
|
16
|
+
import { useTestTableModel } from '../../testing';
|
|
23
17
|
import { translations } from '../../translations';
|
|
24
|
-
import {
|
|
25
|
-
|
|
18
|
+
import { Table } from '../../types';
|
|
19
|
+
|
|
20
|
+
import { TableCellEditor } from './TableCellEditor';
|
|
26
21
|
|
|
27
22
|
type StoryProps = {
|
|
28
23
|
editing: GridEditing;
|
|
29
24
|
};
|
|
30
25
|
|
|
31
26
|
const DefaultStory = ({ editing }: StoryProps) => {
|
|
32
|
-
const {
|
|
33
|
-
invariant(space);
|
|
34
|
-
|
|
35
|
-
const views = useQuery(space, Filter.type(DataType.View));
|
|
36
|
-
const [view, setView] = useState<DataType.View>();
|
|
37
|
-
const [schema, setSchema] = useState<EchoSchema>();
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (space && views.length && !view) {
|
|
40
|
-
const view = views[0];
|
|
41
|
-
setView(view);
|
|
42
|
-
setSchema(space.db.schemaRegistry.getSchema(view.query.typename!));
|
|
43
|
-
}
|
|
44
|
-
}, [space, views, view]);
|
|
45
|
-
|
|
46
|
-
const features: Partial<TableFeatures> = useMemo(
|
|
47
|
-
() => ({
|
|
48
|
-
selection: { enabled: true, mode: 'multiple' },
|
|
49
|
-
dataEditable: true,
|
|
50
|
-
schemaEditable: schema && isMutable(schema),
|
|
51
|
-
}),
|
|
52
|
-
[schema],
|
|
53
|
-
);
|
|
27
|
+
const { model, table } = useTestTableModel();
|
|
54
28
|
|
|
55
|
-
|
|
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
|
-
if (!model || !schema || !view) {
|
|
29
|
+
if (!model || !table) {
|
|
70
30
|
return <div />;
|
|
71
31
|
}
|
|
72
32
|
|
|
73
33
|
return (
|
|
74
|
-
<div className='flex
|
|
34
|
+
<div className='flex is-[300px] border border-separator' style={{ height: defaultRowSize }}>
|
|
75
35
|
<Grid.Root id='test' editing={editing}>
|
|
76
|
-
<TableCellEditor model={model}
|
|
36
|
+
<TableCellEditor model={model} schema={Task.Task} />
|
|
77
37
|
</Grid.Root>
|
|
78
38
|
</div>
|
|
79
39
|
);
|
|
80
40
|
};
|
|
81
41
|
|
|
82
|
-
const meta
|
|
42
|
+
const meta = {
|
|
83
43
|
title: 'ui/react-ui-table/TableCellEditor',
|
|
84
44
|
component: DefaultStory,
|
|
85
45
|
render: DefaultStory,
|
|
86
|
-
parameters: { translations, layout: 'centered' },
|
|
87
46
|
decorators: [
|
|
47
|
+
withTheme,
|
|
88
48
|
withClientProvider({
|
|
89
|
-
types: [
|
|
49
|
+
types: [View.View, Task.Task, Table.Table],
|
|
90
50
|
createIdentity: true,
|
|
91
51
|
createSpace: true,
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
space.db.add(
|
|
52
|
+
onCreateSpace: async ({ client, space }) => {
|
|
53
|
+
const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: Task.Task.typename });
|
|
54
|
+
const table = Table.make({ view, jsonSchema });
|
|
55
|
+
space.db.add(table);
|
|
96
56
|
Array.from({ length: 10 }).forEach(() => {
|
|
97
57
|
space.db.add(
|
|
98
|
-
Obj.make(
|
|
58
|
+
Obj.make(Task.Task, {
|
|
99
59
|
title: faker.person.fullName(),
|
|
100
60
|
status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
101
61
|
description: faker.lorem.sentence(),
|
|
@@ -104,14 +64,16 @@ const meta: Meta<StoryProps> = {
|
|
|
104
64
|
});
|
|
105
65
|
},
|
|
106
66
|
}),
|
|
107
|
-
withTheme,
|
|
108
|
-
withLayout(),
|
|
109
67
|
],
|
|
110
|
-
|
|
68
|
+
parameters: {
|
|
69
|
+
translations,
|
|
70
|
+
layout: 'centered',
|
|
71
|
+
},
|
|
72
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
111
73
|
|
|
112
74
|
export default meta;
|
|
113
75
|
|
|
114
|
-
type Story = StoryObj<
|
|
76
|
+
type Story = StoryObj<typeof meta>;
|
|
115
77
|
|
|
116
78
|
export const Default: Story = {
|
|
117
79
|
args: {
|
|
@@ -3,64 +3,59 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
7
|
+
import type * as Schema from 'effect/Schema';
|
|
8
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
+
import { debounce } from '@dxos/async';
|
|
11
|
+
import type { Client } from '@dxos/client';
|
|
12
|
+
import { Format, TypeEnum } from '@dxos/echo/internal';
|
|
10
13
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { type
|
|
12
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
13
|
-
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
14
|
+
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
14
15
|
import {
|
|
15
|
-
|
|
16
|
-
editorKeys,
|
|
17
|
-
parseCellIndex,
|
|
18
|
-
useGridContext,
|
|
19
|
-
type DxGridPlanePosition,
|
|
16
|
+
type EditorBlurHandler,
|
|
20
17
|
type EditorKeyEvent,
|
|
21
18
|
type EditorKeyOrBlurHandler,
|
|
22
|
-
type EditorBlurHandler,
|
|
23
19
|
GridCellEditor,
|
|
24
20
|
type GridCellEditorProps,
|
|
25
21
|
type GridScopedProps,
|
|
22
|
+
editorKeys,
|
|
23
|
+
parseCellIndex,
|
|
24
|
+
useGridContext,
|
|
26
25
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import { tagPicker, createLinks } from '@dxos/react-ui-tag-picker';
|
|
28
26
|
import { type FieldProjection } from '@dxos/schema';
|
|
29
27
|
|
|
30
|
-
import {
|
|
31
|
-
import { FormCellEditor } from './FormCellEditor';
|
|
32
|
-
import { completion } from './extension';
|
|
33
|
-
import { type TableModel, type ModalController } from '../../model';
|
|
28
|
+
import { type ModalController, type TableModel } from '../../model';
|
|
34
29
|
|
|
35
|
-
|
|
30
|
+
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
|
+
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
34
|
* Option to create new object/value.
|
|
39
35
|
*/
|
|
40
|
-
export const createOption = (text: string) => ({ [newValue]: true, text });
|
|
41
|
-
|
|
42
|
-
const isCreateOption = (data: any) => typeof data === 'object' && data[newValue];
|
|
43
|
-
|
|
44
36
|
export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
45
37
|
|
|
46
38
|
export type TableCellEditorProps = {
|
|
47
39
|
model?: TableModel;
|
|
48
40
|
modals?: ModalController;
|
|
49
|
-
schema?: Schema.AnyNoContext;
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
42
|
+
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
43
|
+
onCreate?: OnCreateHandler;
|
|
52
44
|
onSave?: () => void;
|
|
53
|
-
|
|
45
|
+
client?: Client;
|
|
54
46
|
};
|
|
55
47
|
|
|
48
|
+
const adaptValidationMessage = (message: string | null) =>
|
|
49
|
+
message ? (message.endsWith('is missing') ? 'Can’t be blank' : message) : null;
|
|
50
|
+
|
|
56
51
|
export const TableValueEditor = ({
|
|
57
52
|
model,
|
|
58
53
|
modals,
|
|
59
54
|
schema,
|
|
60
|
-
onEnter,
|
|
61
55
|
onFocus,
|
|
62
56
|
onSave,
|
|
63
|
-
|
|
57
|
+
onCreate,
|
|
58
|
+
client,
|
|
64
59
|
__gridScope,
|
|
65
60
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
66
61
|
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
@@ -79,8 +74,9 @@ export const TableValueEditor = ({
|
|
|
79
74
|
|
|
80
75
|
if (
|
|
81
76
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
82
|
-
fieldProjection?.props.format ===
|
|
83
|
-
|
|
77
|
+
fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
|
|
78
|
+
fieldProjection?.props.format === Format.TypeFormat.Ref
|
|
79
|
+
// TODO(thure): Support `Format.TypeFormat.MultiSelect`
|
|
84
80
|
) {
|
|
85
81
|
return (
|
|
86
82
|
<FormCellEditor
|
|
@@ -89,40 +85,30 @@ export const TableValueEditor = ({
|
|
|
89
85
|
schema={schema}
|
|
90
86
|
__gridScope={__gridScope}
|
|
91
87
|
onSave={onSave}
|
|
88
|
+
onCreate={onCreate}
|
|
89
|
+
client={client}
|
|
90
|
+
modals={modals}
|
|
92
91
|
/>
|
|
93
92
|
);
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
// For all other types, use the existing cell editor
|
|
97
|
-
return
|
|
98
|
-
<TableCellEditor
|
|
99
|
-
model={model}
|
|
100
|
-
modals={modals}
|
|
101
|
-
onEnter={onEnter}
|
|
102
|
-
onFocus={onFocus}
|
|
103
|
-
onQuery={onQuery}
|
|
104
|
-
onSave={onSave}
|
|
105
|
-
__gridScope={__gridScope}
|
|
106
|
-
/>
|
|
107
|
-
);
|
|
96
|
+
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
108
97
|
};
|
|
109
98
|
|
|
110
|
-
const editorSlots = {
|
|
99
|
+
const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
111
100
|
|
|
112
101
|
export const TableCellEditor = ({
|
|
113
102
|
model,
|
|
114
103
|
modals,
|
|
115
|
-
onEnter,
|
|
116
104
|
onFocus,
|
|
117
|
-
onQuery,
|
|
118
105
|
onSave,
|
|
119
106
|
__gridScope,
|
|
120
107
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
121
|
-
const {
|
|
108
|
+
const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
122
109
|
const suppressNextBlur = useRef(false);
|
|
123
|
-
const
|
|
124
|
-
const [
|
|
125
|
-
const [_validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
110
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
111
|
+
const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
126
112
|
|
|
127
113
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
128
114
|
if (!model || !editing) {
|
|
@@ -136,38 +122,6 @@ export const TableCellEditor = ({
|
|
|
136
122
|
return fieldProjection;
|
|
137
123
|
}, [model, editing]);
|
|
138
124
|
|
|
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
125
|
const handleEnter = useCallback(
|
|
172
126
|
async (value: any) => {
|
|
173
127
|
if (!model || !editing) {
|
|
@@ -180,28 +134,34 @@ export const TableCellEditor = ({
|
|
|
180
134
|
if (validationResult.valid) {
|
|
181
135
|
setValidationError(null);
|
|
182
136
|
model.setCellData(cell, value);
|
|
183
|
-
onEnter?.(cell);
|
|
184
137
|
onFocus?.();
|
|
185
138
|
setEditing(null);
|
|
186
139
|
onSave?.();
|
|
187
140
|
} else {
|
|
188
|
-
setValidationError(validationResult.error);
|
|
141
|
+
setValidationError(adaptValidationMessage(validationResult.error));
|
|
189
142
|
setValidationVariant('error');
|
|
190
143
|
}
|
|
191
144
|
},
|
|
192
|
-
[model, editing,
|
|
145
|
+
[model, editing, onFocus, setEditing],
|
|
193
146
|
);
|
|
194
147
|
|
|
195
148
|
const handleBlur = useCallback<EditorBlurHandler>(
|
|
196
149
|
async (value) => {
|
|
197
|
-
if (!model || !editing) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
150
|
if (suppressNextBlur.current) {
|
|
201
151
|
suppressNextBlur.current = false;
|
|
152
|
+
} else if (model && editing) {
|
|
153
|
+
// Save silently if validation passes
|
|
154
|
+
const cell = parseCellIndex(editing.index);
|
|
155
|
+
if (value !== undefined) {
|
|
156
|
+
const result = await model.validateCellData(cell, value);
|
|
157
|
+
if (result.valid) {
|
|
158
|
+
setValidationError(null);
|
|
159
|
+
model.setCellData(cell, value);
|
|
160
|
+
setEditing(null);
|
|
161
|
+
onSave?.();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
202
164
|
}
|
|
203
|
-
|
|
204
|
-
// Don't save on blur - let handleClose handle validation and saving
|
|
205
165
|
},
|
|
206
166
|
[model, editing],
|
|
207
167
|
);
|
|
@@ -213,25 +173,25 @@ export const TableCellEditor = ({
|
|
|
213
173
|
}
|
|
214
174
|
|
|
215
175
|
const cell = parseCellIndex(editing.index);
|
|
216
|
-
|
|
217
176
|
if (value !== undefined) {
|
|
218
177
|
// Pre-commit validation check.
|
|
219
178
|
const result = await model.validateCellData(cell, value);
|
|
220
179
|
|
|
221
180
|
if (result.valid) {
|
|
181
|
+
suppressNextBlur.current = true;
|
|
222
182
|
setValidationError(null);
|
|
223
183
|
model.setCellData(cell, value);
|
|
224
184
|
setEditing(null);
|
|
225
185
|
onSave?.();
|
|
226
|
-
onEnter?.(cell);
|
|
227
186
|
if (event && onFocus) {
|
|
228
|
-
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
187
|
+
onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
|
|
229
188
|
}
|
|
230
189
|
} else {
|
|
231
190
|
setValidationError(result.error);
|
|
232
191
|
setValidationVariant('error');
|
|
233
192
|
}
|
|
234
193
|
} else {
|
|
194
|
+
suppressNextBlur.current = true;
|
|
235
195
|
setValidationError(null);
|
|
236
196
|
setEditing(null);
|
|
237
197
|
onSave?.();
|
|
@@ -240,154 +200,48 @@ export const TableCellEditor = ({
|
|
|
240
200
|
}
|
|
241
201
|
}
|
|
242
202
|
},
|
|
243
|
-
[model, editing, onFocus,
|
|
203
|
+
[model, editing, onFocus, fieldProjection, setEditing, onSave],
|
|
244
204
|
);
|
|
245
205
|
|
|
246
|
-
const
|
|
206
|
+
const extensions = useMemo(() => {
|
|
247
207
|
if (!fieldProjection) {
|
|
248
208
|
return [];
|
|
249
209
|
}
|
|
250
210
|
|
|
251
|
-
const
|
|
211
|
+
const extensions = [
|
|
252
212
|
editorKeys({
|
|
253
213
|
onClose: handleClose,
|
|
254
214
|
...(editing?.initialContent && { onNav: handleClose }),
|
|
255
215
|
}),
|
|
256
216
|
];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
extension.push(
|
|
270
|
-
tagPicker({
|
|
271
|
-
mode,
|
|
272
|
-
keymap: false,
|
|
273
|
-
onSearch: (text, selectedIds) => {
|
|
274
|
-
return options
|
|
275
|
-
.filter(
|
|
276
|
-
(option) =>
|
|
277
|
-
selectedIds.indexOf(option.id) === -1 &&
|
|
278
|
-
(text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase())),
|
|
279
|
-
)
|
|
280
|
-
.map((option) => ({
|
|
281
|
-
id: option.id,
|
|
282
|
-
label: option.title,
|
|
283
|
-
hue: option.color as any,
|
|
284
|
-
}));
|
|
285
|
-
},
|
|
286
|
-
onUpdate: (ids) => {
|
|
287
|
-
if (model && editing) {
|
|
288
|
-
if (ids.length === 0) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (mode === 'single-select') {
|
|
292
|
-
void handleEnter(ids[0]);
|
|
217
|
+
// Add validation extension to handle content changes.
|
|
218
|
+
if (model && editing) {
|
|
219
|
+
extensions.push(
|
|
220
|
+
EditorView.updateListener.of(
|
|
221
|
+
debounce((update) => {
|
|
222
|
+
const content = update.state.doc.toString();
|
|
223
|
+
const cell = parseCellIndex(editing.index);
|
|
224
|
+
|
|
225
|
+
// Perform validation on content change.
|
|
226
|
+
void model.validateCellData(cell, content).then((result) => {
|
|
227
|
+
if (result.valid) {
|
|
228
|
+
setValidationError(null);
|
|
293
229
|
} else {
|
|
294
|
-
|
|
230
|
+
setValidationError(result.error);
|
|
231
|
+
setValidationVariant('error');
|
|
295
232
|
}
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
|
|
233
|
+
});
|
|
234
|
+
}, 10),
|
|
235
|
+
),
|
|
299
236
|
);
|
|
300
237
|
}
|
|
301
238
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
case FormatEnum.Ref: {
|
|
305
|
-
extension.push([
|
|
306
|
-
completion({
|
|
307
|
-
onQuery: (text) => onQuery(fieldProjection, text),
|
|
308
|
-
onMatch: (data) => {
|
|
309
|
-
if (model && editing && modals) {
|
|
310
|
-
if (isCreateOption(data)) {
|
|
311
|
-
const { field, props } = fieldProjection;
|
|
312
|
-
if (props.referenceSchema) {
|
|
313
|
-
suppressNextBlur.current = true;
|
|
314
|
-
modals.openCreateRef(
|
|
315
|
-
props.referenceSchema,
|
|
316
|
-
document.querySelector(cellQuery(editing.index, gridId)),
|
|
317
|
-
{
|
|
318
|
-
[field.referencePath!]: data.text,
|
|
319
|
-
},
|
|
320
|
-
(data) => {
|
|
321
|
-
void handleEnter(data);
|
|
322
|
-
},
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
void handleEnter(data);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
}),
|
|
331
|
-
]);
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return extension;
|
|
338
|
-
}, [model, modals, editing, fieldProjection, handleClose, themeMode]);
|
|
239
|
+
return extensions;
|
|
240
|
+
}, [model, modals, editing, fieldProjection, handleClose]);
|
|
339
241
|
|
|
340
242
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
341
243
|
if (model && editing) {
|
|
342
244
|
const cell = parseCellIndex(editing.index);
|
|
343
|
-
const { col } = cell;
|
|
344
|
-
const field = model.projection.fields[col];
|
|
345
|
-
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
349
|
-
fieldProjection?.props.format === FormatEnum.MultiSelect
|
|
350
|
-
) {
|
|
351
|
-
const value = model.getCellData(cell);
|
|
352
|
-
|
|
353
|
-
if (value !== undefined) {
|
|
354
|
-
const options = fieldProjection.props.options || [];
|
|
355
|
-
|
|
356
|
-
if (fieldProjection.props.format === FormatEnum.MultiSelect) {
|
|
357
|
-
const tagItems = value
|
|
358
|
-
.split(',')
|
|
359
|
-
.map((id: string) => {
|
|
360
|
-
const option = options.find((o) => o.id === id);
|
|
361
|
-
if (option) {
|
|
362
|
-
return {
|
|
363
|
-
id,
|
|
364
|
-
label: option.title,
|
|
365
|
-
hue: option.color as any,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
return undefined;
|
|
369
|
-
})
|
|
370
|
-
.filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
371
|
-
|
|
372
|
-
return createLinks(tagItems);
|
|
373
|
-
} else {
|
|
374
|
-
const option = options.find((o) => o.id === value);
|
|
375
|
-
|
|
376
|
-
if (option) {
|
|
377
|
-
const tagItem = {
|
|
378
|
-
id: value,
|
|
379
|
-
label: option.title,
|
|
380
|
-
hue: option.color as any,
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
return createLinks([tagItem]);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return '';
|
|
389
|
-
}
|
|
390
|
-
|
|
391
245
|
const value = model.getCellData(cell);
|
|
392
246
|
return value !== undefined ? String(value) : '';
|
|
393
247
|
}
|
|
@@ -395,12 +249,8 @@ export const TableCellEditor = ({
|
|
|
395
249
|
|
|
396
250
|
return (
|
|
397
251
|
<>
|
|
398
|
-
<CellValidationMessage
|
|
399
|
-
|
|
400
|
-
variant={_validationVariant}
|
|
401
|
-
__gridScope={__gridScope}
|
|
402
|
-
/>
|
|
403
|
-
<GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
252
|
+
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
253
|
+
<GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
404
254
|
</>
|
|
405
255
|
);
|
|
406
256
|
};
|