@dxos/react-ui-table 0.8.3-staging.0fa589b → 0.8.4-main.28f8d3d
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-L2LSB6KV.mjs +18 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
- package/dist/lib/browser/chunk-ZAOSIG3L.mjs +1814 -0
- package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +728 -2104
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +123 -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-NXZ4CJYX.mjs +20 -0
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +1815 -0
- package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +728 -2104
- 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 +124 -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 +1 -1
- 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/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 +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/{relations.stories.d.ts → Relations.stories.d.ts} +2 -2
- 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 +1 -0
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
- 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 +2 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -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/useTableModel.d.ts +8 -7
- 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.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +2 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +58 -21
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +10 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +4 -4
- package/dist/types/src/model/table-sorting.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 +41 -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/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +8 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +3 -4
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- 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/src/util/table.d.ts +9 -11
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +52 -40
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +15 -11
- package/src/components/Table/CreateRefPanel.tsx +6 -4
- package/src/components/Table/DynamicTable.stories.tsx +4 -3
- package/src/components/Table/DynamicTable.tsx +17 -10
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +37 -37
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +109 -148
- package/src/components/Table/Table.tsx +95 -66
- package/src/components/TableCellEditor/CellValidationMessage.tsx +7 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +27 -30
- package/src/components/TableCellEditor/TableCellEditor.tsx +88 -34
- package/src/components/TableCellEditor/extension.ts +2 -2
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +7 -24
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddRow.ts +36 -0
- package/src/hooks/useTableModel.ts +21 -17
- package/src/index.ts +1 -2
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +6 -3
- package/src/model/table-model.test.ts +8 -8
- package/src/model/table-model.ts +278 -86
- package/src/model/table-presentation.test.ts +8 -8
- package/src/model/table-presentation.ts +240 -135
- package/src/model/table-sorting.ts +11 -11
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +113 -20
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +123 -0
- package/src/translations.ts +7 -5
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +16 -0
- package/src/util/dynamic-table.ts +20 -18
- package/src/util/schema.ts +1 -1
- package/src/util/table-buttons.ts +26 -2
- package/src/util/table-controls.ts +4 -1
- package/src/util/table.ts +37 -111
- 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/relations.stories.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/src/playwright/playwright.config.cts +0 -18
- package/src/types/table.ts +0 -27
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { type GridScopedProps, useGridContext } from '@dxos/react-ui-grid';
|
|
8
8
|
|
|
9
9
|
export type CellValidationMessageProps = {
|
|
10
10
|
validationError: string | null;
|
|
11
|
+
variant?: 'error' | 'warning';
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export const CellValidationMessage = ({
|
|
14
15
|
validationError,
|
|
16
|
+
variant = 'error',
|
|
15
17
|
__gridScope,
|
|
16
18
|
}: GridScopedProps<CellValidationMessageProps>) => {
|
|
17
19
|
const { editing, editBox: box } = useGridContext('GridSheetCellEditor', __gridScope);
|
|
@@ -20,10 +22,13 @@ export const CellValidationMessage = ({
|
|
|
20
22
|
return null;
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
const bgClass = variant === 'error' ? 'bg-errorSurface' : 'bg-warningSurface';
|
|
26
|
+
const textClass = variant === 'error' ? 'text-errorSurfaceText' : 'text-warningSurfaceText';
|
|
27
|
+
|
|
23
28
|
return (
|
|
24
29
|
<div
|
|
25
30
|
role='none'
|
|
26
|
-
className=
|
|
31
|
+
className={`absolute ${bgClass} ${textClass} rounded-bs-sm text-xs p-1`}
|
|
27
32
|
style={{
|
|
28
33
|
...{ '--dx-gridCellWidth': `${box?.inlineSize ?? 200}px` },
|
|
29
34
|
zIndex: 10,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Schema } from 'effect';
|
|
6
|
-
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { getSnapshot } from '@dxos/echo-schema';
|
|
9
9
|
import { invariant } from '@dxos/invariant';
|
|
@@ -20,18 +20,26 @@ type FormCellEditorProps = {
|
|
|
20
20
|
fieldProjection: FieldProjection;
|
|
21
21
|
model?: TableModel;
|
|
22
22
|
schema?: Schema.Schema.AnyNoContext;
|
|
23
|
+
onSave?: () => void;
|
|
23
24
|
__gridScope: any;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }: FormCellEditorProps) => {
|
|
27
|
-
const { editing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
28
|
-
const
|
|
27
|
+
export const FormCellEditor = ({ fieldProjection, model, schema, onSave, __gridScope }: FormCellEditorProps) => {
|
|
28
|
+
const { editing: contextEditing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
29
|
+
const [editing, setLocalEditing] = useState(false);
|
|
30
|
+
const anchorRef = useRef<HTMLButtonElement | null>(null);
|
|
29
31
|
|
|
30
32
|
useEffect(() => {
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
+
if (contextEditing && contextEditing.cellElement) {
|
|
34
|
+
anchorRef.current = (contextEditing.cellElement as HTMLElement).querySelector(
|
|
35
|
+
'.dx-grid__cell__content',
|
|
36
|
+
) as HTMLButtonElement;
|
|
37
|
+
setLocalEditing(true);
|
|
38
|
+
} else {
|
|
39
|
+
anchorRef.current = null;
|
|
40
|
+
setLocalEditing(false);
|
|
33
41
|
}
|
|
34
|
-
}, [
|
|
42
|
+
}, [contextEditing]);
|
|
35
43
|
|
|
36
44
|
const narrowedSchema = useMemo(() => {
|
|
37
45
|
if (!schema) {
|
|
@@ -41,8 +49,8 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
|
|
|
41
49
|
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
42
50
|
|
|
43
51
|
const originalRow = useMemo(() => {
|
|
44
|
-
if (model &&
|
|
45
|
-
const cell = parseCellIndex(
|
|
52
|
+
if (model && contextEditing) {
|
|
53
|
+
const cell = parseCellIndex(contextEditing.index);
|
|
46
54
|
const row = model.getRowAt(cell.row);
|
|
47
55
|
invariant(row);
|
|
48
56
|
|
|
@@ -50,7 +58,7 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
|
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
return undefined;
|
|
53
|
-
}, [model,
|
|
61
|
+
}, [model, contextEditing]);
|
|
54
62
|
|
|
55
63
|
const formValues = useMemo(() => {
|
|
56
64
|
if (originalRow) {
|
|
@@ -65,27 +73,28 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
|
|
|
65
73
|
const value = getDeep(values, [path]);
|
|
66
74
|
setDeep(originalRow, [path], value);
|
|
67
75
|
setEditing(null);
|
|
76
|
+
setLocalEditing(false);
|
|
77
|
+
onSave?.();
|
|
68
78
|
},
|
|
69
79
|
[fieldProjection.field.path, originalRow],
|
|
70
80
|
);
|
|
71
81
|
|
|
72
|
-
const handleOpenChange = useCallback(
|
|
73
|
-
(nextOpen
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
82
|
+
const handleOpenChange = useCallback((nextOpen: boolean) => {
|
|
83
|
+
if (nextOpen === false) {
|
|
84
|
+
setEditing(null);
|
|
85
|
+
onSave?.();
|
|
86
|
+
}
|
|
87
|
+
setLocalEditing(nextOpen);
|
|
88
|
+
}, []);
|
|
80
89
|
|
|
81
90
|
if (!editing) {
|
|
82
91
|
return null;
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
return (
|
|
86
|
-
<Popover.Root open={editing
|
|
87
|
-
<Popover.VirtualTrigger virtualRef={
|
|
88
|
-
<Popover.Content tabIndex={-1} classNames='popover-card-width'>
|
|
95
|
+
<Popover.Root open={editing} onOpenChange={handleOpenChange}>
|
|
96
|
+
<Popover.VirtualTrigger virtualRef={anchorRef} />
|
|
97
|
+
<Popover.Content tabIndex={-1} classNames='popover-card-width density-fine'>
|
|
89
98
|
<Popover.Arrow />
|
|
90
99
|
<Popover.Viewport>
|
|
91
100
|
<Form values={formValues} schema={narrowedSchema as any} onSave={handleSave} />
|
|
@@ -4,25 +4,26 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
-
import { type Meta, type StoryObj } from '@storybook/react';
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
8
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
10
|
import { Obj } from '@dxos/echo';
|
|
11
11
|
import { type EchoSchema, isMutable } from '@dxos/echo-schema';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
13
|
import { faker } from '@dxos/random';
|
|
14
|
-
import { Filter, useQuery
|
|
14
|
+
import { Filter, useQuery } from '@dxos/react-client/echo';
|
|
15
15
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
17
|
+
import { DataType, createDefaultSchema } 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
|
-
import translations from '../../translations';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
22
|
+
import { translations } from '../../translations';
|
|
23
|
+
import { TableView } from '../../types';
|
|
24
|
+
import { createTable } from '../../util';
|
|
25
|
+
|
|
26
|
+
import { TableCellEditor, type TableCellEditorProps } from './TableCellEditor';
|
|
26
27
|
|
|
27
28
|
type StoryProps = {
|
|
28
29
|
editing: GridEditing;
|
|
@@ -32,23 +33,16 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
32
33
|
const { space } = useClientProvider();
|
|
33
34
|
invariant(space);
|
|
34
35
|
|
|
35
|
-
const
|
|
36
|
-
const [
|
|
36
|
+
const views = useQuery(space, Filter.type(DataType.View));
|
|
37
|
+
const [view, setView] = useState<DataType.View>();
|
|
37
38
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
38
39
|
useEffect(() => {
|
|
39
|
-
if (space &&
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
setSchema(space.db.schemaRegistry.getSchema(table.view.target!.query.typename!));
|
|
44
|
-
}
|
|
45
|
-
}, [space, tables]);
|
|
46
|
-
|
|
47
|
-
const projection = useMemo(() => {
|
|
48
|
-
if (schema && table?.view) {
|
|
49
|
-
return new ViewProjection(schema.jsonSchema, table.view.target!);
|
|
40
|
+
if (space && views.length && !view) {
|
|
41
|
+
const view = views[0];
|
|
42
|
+
setView(view);
|
|
43
|
+
setSchema(space.db.schemaRegistry.getSchema(view.query.typename!));
|
|
50
44
|
}
|
|
51
|
-
}, [
|
|
45
|
+
}, [space, views, view]);
|
|
52
46
|
|
|
53
47
|
const features: Partial<TableFeatures> = useMemo(
|
|
54
48
|
() => ({
|
|
@@ -59,7 +53,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
59
53
|
[schema],
|
|
60
54
|
);
|
|
61
55
|
|
|
62
|
-
const model = useTableModel({
|
|
56
|
+
const model = useTableModel({ view, schema: schema?.jsonSchema, features });
|
|
63
57
|
|
|
64
58
|
const handleQuery: TableCellEditorProps['onQuery'] = async ({ field }) => {
|
|
65
59
|
// TODO(dmaretskyi): If no schema query nothing
|
|
@@ -73,7 +67,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
73
67
|
});
|
|
74
68
|
};
|
|
75
69
|
|
|
76
|
-
if (!model || !schema || !
|
|
70
|
+
if (!model || !schema || !view) {
|
|
77
71
|
return <div />;
|
|
78
72
|
}
|
|
79
73
|
|
|
@@ -87,22 +81,25 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
87
81
|
};
|
|
88
82
|
|
|
89
83
|
const meta: Meta<StoryProps> = {
|
|
90
|
-
title: '
|
|
84
|
+
title: 'ui/react-ui-table/TableCellEditor',
|
|
91
85
|
component: DefaultStory,
|
|
92
86
|
render: DefaultStory,
|
|
93
87
|
parameters: { translations, layout: 'centered' },
|
|
94
88
|
decorators: [
|
|
95
89
|
withClientProvider({
|
|
96
|
-
types: [
|
|
90
|
+
types: [DataType.View, TableView],
|
|
97
91
|
createIdentity: true,
|
|
98
92
|
createSpace: true,
|
|
99
93
|
onSpaceCreated: async ({ client, space }) => {
|
|
100
|
-
const
|
|
101
|
-
const
|
|
94
|
+
const schema = createDefaultSchema();
|
|
95
|
+
const { view } = await createTable({ client, space, typename: schema.typename });
|
|
96
|
+
space.db.add(view);
|
|
102
97
|
Array.from({ length: 10 }).forEach(() => {
|
|
103
98
|
space.db.add(
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
Obj.make(schema, {
|
|
100
|
+
title: faker.person.fullName(),
|
|
101
|
+
status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
102
|
+
description: faker.lorem.sentence(),
|
|
106
103
|
}),
|
|
107
104
|
);
|
|
108
105
|
});
|
|
@@ -4,33 +4,33 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
6
|
import { type Schema } from 'effect/Schema';
|
|
7
|
-
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
7
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
8
|
|
|
9
9
|
import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { type
|
|
11
|
+
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
12
12
|
import { useThemeContext } from '@dxos/react-ui';
|
|
13
13
|
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
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
|
+
cellQuery,
|
|
22
|
+
editorKeys,
|
|
23
|
+
parseCellIndex,
|
|
24
|
+
useGridContext,
|
|
26
25
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import {
|
|
26
|
+
import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
|
|
28
27
|
import { type FieldProjection } from '@dxos/schema';
|
|
29
28
|
|
|
29
|
+
import { type ModalController, type TableModel } from '../../model';
|
|
30
|
+
|
|
30
31
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
31
|
-
import { FormCellEditor } from './FormCellEditor';
|
|
32
32
|
import { completion } from './extension';
|
|
33
|
-
import {
|
|
33
|
+
import { FormCellEditor } from './FormCellEditor';
|
|
34
34
|
|
|
35
35
|
const newValue = Symbol.for('newValue');
|
|
36
36
|
|
|
@@ -47,8 +47,9 @@ export type TableCellEditorProps = {
|
|
|
47
47
|
model?: TableModel;
|
|
48
48
|
modals?: ModalController;
|
|
49
49
|
schema?: Schema.AnyNoContext;
|
|
50
|
-
onEnter?: (cell:
|
|
51
|
-
onFocus?:
|
|
50
|
+
onEnter?: (cell: DxGridPosition) => void;
|
|
51
|
+
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
52
|
+
onSave?: () => void;
|
|
52
53
|
onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
|
|
53
54
|
};
|
|
54
55
|
|
|
@@ -58,6 +59,7 @@ export const TableValueEditor = ({
|
|
|
58
59
|
schema,
|
|
59
60
|
onEnter,
|
|
60
61
|
onFocus,
|
|
62
|
+
onSave,
|
|
61
63
|
onQuery,
|
|
62
64
|
__gridScope,
|
|
63
65
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
@@ -69,14 +71,26 @@ export const TableValueEditor = ({
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
const { col } = parseCellIndex(editing.index);
|
|
72
|
-
const field = model.projection.
|
|
74
|
+
const field = model.projection.fields[col];
|
|
73
75
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
74
76
|
invariant(fieldProjection);
|
|
75
77
|
return fieldProjection;
|
|
76
78
|
}, [model, editing]);
|
|
77
79
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
+
if (
|
|
81
|
+
fieldProjection?.props.type === TypeEnum.Array ||
|
|
82
|
+
fieldProjection?.props.format === FormatEnum.SingleSelect
|
|
83
|
+
// TODO(thure): Support `FormatEnum.MultiSelect`
|
|
84
|
+
) {
|
|
85
|
+
return (
|
|
86
|
+
<FormCellEditor
|
|
87
|
+
fieldProjection={fieldProjection}
|
|
88
|
+
model={model}
|
|
89
|
+
schema={schema}
|
|
90
|
+
__gridScope={__gridScope}
|
|
91
|
+
onSave={onSave}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
80
94
|
}
|
|
81
95
|
|
|
82
96
|
// For all other types, use the existing cell editor
|
|
@@ -87,23 +101,28 @@ export const TableValueEditor = ({
|
|
|
87
101
|
onEnter={onEnter}
|
|
88
102
|
onFocus={onFocus}
|
|
89
103
|
onQuery={onQuery}
|
|
104
|
+
onSave={onSave}
|
|
90
105
|
__gridScope={__gridScope}
|
|
91
106
|
/>
|
|
92
107
|
);
|
|
93
108
|
};
|
|
94
109
|
|
|
110
|
+
const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
|
|
111
|
+
|
|
95
112
|
export const TableCellEditor = ({
|
|
96
113
|
model,
|
|
97
114
|
modals,
|
|
98
115
|
onEnter,
|
|
99
116
|
onFocus,
|
|
100
117
|
onQuery,
|
|
118
|
+
onSave,
|
|
101
119
|
__gridScope,
|
|
102
120
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
103
121
|
const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
|
|
104
122
|
const suppressNextBlur = useRef(false);
|
|
105
123
|
const { themeMode } = useThemeContext();
|
|
106
|
-
const [
|
|
124
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
125
|
+
const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
|
|
107
126
|
|
|
108
127
|
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
109
128
|
if (!model || !editing) {
|
|
@@ -111,12 +130,44 @@ export const TableCellEditor = ({
|
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
const { col } = parseCellIndex(editing.index);
|
|
114
|
-
const field = model.projection.
|
|
133
|
+
const field = model.projection.fields[col];
|
|
115
134
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
116
135
|
invariant(fieldProjection);
|
|
117
136
|
return fieldProjection;
|
|
118
137
|
}, [model, editing]);
|
|
119
138
|
|
|
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
|
+
|
|
120
171
|
const handleEnter = useCallback(
|
|
121
172
|
async (value: any) => {
|
|
122
173
|
if (!model || !editing) {
|
|
@@ -124,19 +175,18 @@ export const TableCellEditor = ({
|
|
|
124
175
|
}
|
|
125
176
|
|
|
126
177
|
const cell = parseCellIndex(editing.index);
|
|
178
|
+
const validationResult = await model.validateCellData(cell, value);
|
|
127
179
|
|
|
128
|
-
|
|
129
|
-
const result = await model.validateCellData(cell, value);
|
|
130
|
-
|
|
131
|
-
if (result.valid) {
|
|
180
|
+
if (validationResult.valid) {
|
|
132
181
|
setValidationError(null);
|
|
133
182
|
model.setCellData(cell, value);
|
|
134
183
|
onEnter?.(cell);
|
|
135
184
|
onFocus?.();
|
|
136
185
|
setEditing(null);
|
|
186
|
+
onSave?.();
|
|
137
187
|
} else {
|
|
138
|
-
setValidationError(
|
|
139
|
-
|
|
188
|
+
setValidationError(validationResult.error);
|
|
189
|
+
setValidationVariant('error');
|
|
140
190
|
}
|
|
141
191
|
},
|
|
142
192
|
[model, editing, onEnter, onFocus, setEditing],
|
|
@@ -172,22 +222,25 @@ export const TableCellEditor = ({
|
|
|
172
222
|
setValidationError(null);
|
|
173
223
|
model.setCellData(cell, value);
|
|
174
224
|
setEditing(null);
|
|
225
|
+
onSave?.();
|
|
175
226
|
onEnter?.(cell);
|
|
176
227
|
if (event && onFocus) {
|
|
177
|
-
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
228
|
+
onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
|
|
178
229
|
}
|
|
179
230
|
} else {
|
|
180
231
|
setValidationError(result.error);
|
|
232
|
+
setValidationVariant('error');
|
|
181
233
|
}
|
|
182
234
|
} else {
|
|
183
235
|
setValidationError(null);
|
|
184
236
|
setEditing(null);
|
|
237
|
+
onSave?.();
|
|
185
238
|
if (event && onFocus) {
|
|
186
239
|
onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
|
|
187
240
|
}
|
|
188
241
|
}
|
|
189
242
|
},
|
|
190
|
-
[model, editing, onFocus, onEnter, fieldProjection, setEditing],
|
|
243
|
+
[model, editing, onFocus, onEnter, fieldProjection, setEditing, onSave],
|
|
191
244
|
);
|
|
192
245
|
|
|
193
246
|
const extension = useMemo(() => {
|
|
@@ -214,9 +267,9 @@ export const TableCellEditor = ({
|
|
|
214
267
|
const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
|
|
215
268
|
|
|
216
269
|
extension.push(
|
|
217
|
-
|
|
270
|
+
tagPicker({
|
|
218
271
|
mode,
|
|
219
|
-
|
|
272
|
+
keymap: false,
|
|
220
273
|
onSearch: (text, selectedIds) => {
|
|
221
274
|
return options
|
|
222
275
|
.filter(
|
|
@@ -288,7 +341,7 @@ export const TableCellEditor = ({
|
|
|
288
341
|
if (model && editing) {
|
|
289
342
|
const cell = parseCellIndex(editing.index);
|
|
290
343
|
const { col } = cell;
|
|
291
|
-
const field = model.projection.
|
|
344
|
+
const field = model.projection.fields[col];
|
|
292
345
|
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
293
346
|
|
|
294
347
|
if (
|
|
@@ -300,9 +353,10 @@ export const TableCellEditor = ({
|
|
|
300
353
|
if (value !== undefined) {
|
|
301
354
|
const options = fieldProjection.props.options || [];
|
|
302
355
|
|
|
303
|
-
if (fieldProjection.props.format === FormatEnum.MultiSelect
|
|
356
|
+
if (fieldProjection.props.format === FormatEnum.MultiSelect) {
|
|
304
357
|
const tagItems = value
|
|
305
|
-
.
|
|
358
|
+
.split(',')
|
|
359
|
+
.map((id: string) => {
|
|
306
360
|
const option = options.find((o) => o.id === id);
|
|
307
361
|
if (option) {
|
|
308
362
|
return {
|
|
@@ -313,7 +367,7 @@ export const TableCellEditor = ({
|
|
|
313
367
|
}
|
|
314
368
|
return undefined;
|
|
315
369
|
})
|
|
316
|
-
.filter((item): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
370
|
+
.filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
317
371
|
|
|
318
372
|
return createLinks(tagItems);
|
|
319
373
|
} else {
|
|
@@ -341,8 +395,8 @@ export const TableCellEditor = ({
|
|
|
341
395
|
|
|
342
396
|
return (
|
|
343
397
|
<>
|
|
344
|
-
<CellValidationMessage validationError={
|
|
345
|
-
<GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} />
|
|
398
|
+
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
399
|
+
<GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
|
|
346
400
|
</>
|
|
347
401
|
);
|
|
348
402
|
};
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
autocompletion,
|
|
7
|
-
pickedCompletion,
|
|
8
6
|
type CompletionContext,
|
|
9
7
|
type CompletionResult,
|
|
8
|
+
autocompletion,
|
|
9
|
+
pickedCompletion,
|
|
10
10
|
} from '@codemirror/autocomplete';
|
|
11
11
|
import { type Extension } from '@codemirror/state';
|
|
12
12
|
import { EditorView } from '@codemirror/view';
|
|
@@ -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,13 +5,10 @@
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo-schema';
|
|
9
|
+
import { live } from '@dxos/live-object';
|
|
10
10
|
import { faker } from '@dxos/random';
|
|
11
|
-
import {
|
|
12
|
-
import { createView, type ViewProjection } from '@dxos/schema';
|
|
13
|
-
|
|
14
|
-
import { TableType } from '../types';
|
|
11
|
+
import { type ProjectionModel } from '@dxos/schema';
|
|
15
12
|
|
|
16
13
|
export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
17
14
|
id: ObjectId,
|
|
@@ -21,18 +18,6 @@ export const TestSchema = TypedObject({ typename: 'example.com/type/Test', versi
|
|
|
21
18
|
netWorth: Schema.optional(Schema.Number),
|
|
22
19
|
});
|
|
23
20
|
|
|
24
|
-
export const createTable = (schema = TestSchema) => {
|
|
25
|
-
return Obj.make(TableType, {
|
|
26
|
-
view: Ref.make(
|
|
27
|
-
createView({
|
|
28
|
-
name: 'Test',
|
|
29
|
-
typename: schema.typename,
|
|
30
|
-
jsonSchema: toJsonSchema(schema),
|
|
31
|
-
}),
|
|
32
|
-
),
|
|
33
|
-
});
|
|
34
|
-
};
|
|
35
|
-
|
|
36
21
|
export const createItems = (n: number) => {
|
|
37
22
|
const { data } = live({
|
|
38
23
|
data: Array.from({ length: n }, () => ({
|
|
@@ -47,13 +32,13 @@ export const createItems = (n: number) => {
|
|
|
47
32
|
};
|
|
48
33
|
|
|
49
34
|
export type SimulatorProps = {
|
|
50
|
-
|
|
35
|
+
projection: ProjectionModel;
|
|
51
36
|
items: any[];
|
|
52
37
|
insertInterval?: number;
|
|
53
38
|
updateInterval?: number;
|
|
54
39
|
};
|
|
55
40
|
|
|
56
|
-
export const useSimulator = ({ items,
|
|
41
|
+
export const useSimulator = ({ items, projection, insertInterval, updateInterval }: SimulatorProps) => {
|
|
57
42
|
useEffect(() => {
|
|
58
43
|
if (!insertInterval) {
|
|
59
44
|
return;
|
|
@@ -74,10 +59,8 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
|
|
|
74
59
|
|
|
75
60
|
const i = setInterval(() => {
|
|
76
61
|
const rowIdx = Math.floor(Math.random() * items.length);
|
|
77
|
-
const fields =
|
|
62
|
+
const fields = projection.fields ?? [];
|
|
78
63
|
const columnIdx = Math.floor(Math.random() * fields.length);
|
|
79
|
-
// TODO(ZaymonFC): ... This is borked.
|
|
80
|
-
const projection: ViewProjection = (table as any)._projection;
|
|
81
64
|
const field = fields[columnIdx];
|
|
82
65
|
const item = items[rowIdx];
|
|
83
66
|
|
|
@@ -115,5 +98,5 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
|
|
|
115
98
|
}, updateInterval);
|
|
116
99
|
|
|
117
100
|
return () => clearInterval(i);
|
|
118
|
-
}, [items,
|
|
101
|
+
}, [items, projection.fields, updateInterval]);
|
|
119
102
|
};
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Schema } from 'effect';
|
|
6
|
+
import { useCallback } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type Space } from '@dxos/client/echo';
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
|
|
11
|
+
export type UseAddRowParams = {
|
|
12
|
+
space?: Space;
|
|
13
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook that provides a safe row creation function that handles schema validation failures.
|
|
18
|
+
* Returns a callback that attempts to create an object and returns boolean success/failure.
|
|
19
|
+
* Can accept optional data to create objects with specific content (used for committing draft rows).
|
|
20
|
+
*/
|
|
21
|
+
export const useAddRow = ({ space, schema }: UseAddRowParams) => {
|
|
22
|
+
return useCallback(
|
|
23
|
+
(data?: any) => {
|
|
24
|
+
if (space && schema) {
|
|
25
|
+
try {
|
|
26
|
+
space.db.add(Obj.make(schema, data ?? {}));
|
|
27
|
+
return true;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
},
|
|
34
|
+
[space, schema],
|
|
35
|
+
);
|
|
36
|
+
};
|