@dxos/react-ui-table 0.8.4-main.e098934 → 0.8.4-main.ead640a
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 → chunk-4K7JPALT.mjs} +5 -5
- package/dist/lib/browser/chunk-4K7JPALT.mjs.map +7 -0
- package/dist/lib/browser/{chunk-BBXDTWCX.mjs → chunk-OZTXLZGM.mjs} +24 -42
- package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +123 -269
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +8 -6
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node-esm/{chunk-K734DJB7.mjs → chunk-MK6OVUF6.mjs} +5 -5
- package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-RNQDU7IQ.mjs → chunk-S56Z4V4X.mjs} +24 -42
- package/dist/lib/node-esm/chunk-S56Z4V4X.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +123 -269
- 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 +8 -6
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +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 +3 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +71 -7
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +5 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +5 -6
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -5
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -2
- 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/testing.d.ts +3 -3
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +1 -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 +0 -7
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +1 -1
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +3 -3
- package/dist/types/src/types/schema.d.ts.map +1 -1
- 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/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -50
- package/src/components/Table/DynamicTable.stories.tsx +8 -7
- package/src/components/Table/DynamicTable.tsx +4 -7
- package/src/components/Table/Relations.stories.tsx +126 -11
- package/src/components/Table/Table.stories.tsx +33 -32
- package/src/components/Table/Table.tsx +13 -9
- package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +17 -45
- package/src/components/TableCellEditor/TableCellEditor.tsx +7 -94
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/testing.ts +2 -2
- package/src/hooks/useAddRow.ts +1 -1
- package/src/hooks/useTableModel.ts +1 -1
- package/src/model/modal-controller.ts +0 -28
- package/src/model/table-model.test.ts +10 -5
- package/src/model/table-model.ts +2 -1
- package/src/model/table-presentation.test.ts +13 -16
- package/src/model/table-presentation.ts +4 -5
- package/src/model/table-sorting.ts +1 -1
- package/src/testing/useTestTableModel.ts +6 -4
- package/src/types/schema.ts +4 -4
- package/src/util/dynamic-table.ts +5 -6
- package/src/util/schema.ts +4 -4
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
- package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RNQDU7IQ.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/src/components/Table/CreateRefPanel.tsx +0 -103
|
@@ -2,34 +2,37 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { Filter, Obj } from '@dxos/echo';
|
|
9
|
-
import { Ref, type TypeAnnotation, getValue } from '@dxos/echo
|
|
9
|
+
import { FormatEnum, Ref, type TypeAnnotation, getValue } from '@dxos/echo/internal';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
11
|
import { getSnapshot } from '@dxos/live-object';
|
|
12
12
|
import { type Client } from '@dxos/react-client';
|
|
13
13
|
import { getSpace } from '@dxos/react-client/echo';
|
|
14
14
|
import { Popover } from '@dxos/react-ui';
|
|
15
15
|
import { Form, type FormProps } from '@dxos/react-ui-form';
|
|
16
|
-
import {
|
|
16
|
+
import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
17
17
|
import { type FieldProjection } from '@dxos/schema';
|
|
18
|
-
import { getDeep,
|
|
18
|
+
import { getDeep, isTruthy, setDeep } from '@dxos/util';
|
|
19
19
|
|
|
20
20
|
import { type ModalController, type TableModel } from '../../model';
|
|
21
21
|
import { translationKey } from '../../translations';
|
|
22
22
|
import { narrowSchema } from '../../util';
|
|
23
23
|
|
|
24
|
-
type
|
|
24
|
+
export type OnCreateHandler = (schema: Schema.Schema.AnyNoContext, values: any) => Parameters<typeof Ref.make>[0];
|
|
25
|
+
|
|
26
|
+
export type FormCellEditorProps = {
|
|
25
27
|
fieldProjection: FieldProjection;
|
|
26
28
|
model?: TableModel;
|
|
27
29
|
schema?: Schema.Schema.AnyNoContext;
|
|
28
30
|
onSave?: () => void;
|
|
31
|
+
onCreate?: OnCreateHandler;
|
|
29
32
|
client?: Client;
|
|
30
33
|
modals?: ModalController;
|
|
31
34
|
__gridScope: any;
|
|
32
|
-
} & Omit<FormProps<any>, 'values' | 'schema'>;
|
|
35
|
+
} & Omit<FormProps<any>, 'values' | 'schema' | 'onCreate'>;
|
|
33
36
|
|
|
34
37
|
const createOptionLabel = ['create new object label', { ns: translationKey }] as [string, { ns: string }];
|
|
35
38
|
|
|
@@ -40,6 +43,7 @@ export const FormCellEditor = ({
|
|
|
40
43
|
onSave,
|
|
41
44
|
client,
|
|
42
45
|
modals,
|
|
46
|
+
onCreate,
|
|
43
47
|
__gridScope,
|
|
44
48
|
...formProps
|
|
45
49
|
}: FormCellEditorProps) => {
|
|
@@ -48,7 +52,7 @@ export const FormCellEditor = ({
|
|
|
48
52
|
const anchorRef = useRef<HTMLButtonElement | null>(null);
|
|
49
53
|
|
|
50
54
|
const getSchema = useCallback(
|
|
51
|
-
(typeAnnotation: TypeAnnotation) => {
|
|
55
|
+
(typeAnnotation: Pick<TypeAnnotation, 'typename'>) => {
|
|
52
56
|
const space = getSpace(model!.view);
|
|
53
57
|
invariant(space);
|
|
54
58
|
|
|
@@ -76,7 +80,7 @@ export const FormCellEditor = ({
|
|
|
76
80
|
label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
77
81
|
};
|
|
78
82
|
})
|
|
79
|
-
.filter(
|
|
83
|
+
.filter(isTruthy);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
return [];
|
|
@@ -116,36 +120,6 @@ export const FormCellEditor = ({
|
|
|
116
120
|
[fieldProjection.field.path, onSave, contextEditing, originalRow],
|
|
117
121
|
);
|
|
118
122
|
|
|
119
|
-
const handleCreate = useCallback(
|
|
120
|
-
(object: any) => {
|
|
121
|
-
const ref = Ref.make(object);
|
|
122
|
-
const path = fieldProjection.field.path;
|
|
123
|
-
setDeep(originalRow, [path], ref);
|
|
124
|
-
contextEditing?.cellElement?.focus();
|
|
125
|
-
setEditing(null);
|
|
126
|
-
setLocalEditing(false);
|
|
127
|
-
onSave?.();
|
|
128
|
-
},
|
|
129
|
-
[fieldProjection.field.path, onSave, contextEditing, originalRow],
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
const handleCreateFromQuery = useCallback(
|
|
133
|
-
async (typeAnnotation: TypeAnnotation, query: string) => {
|
|
134
|
-
if (model && modals && contextEditing?.index) {
|
|
135
|
-
setLocalEditing(false);
|
|
136
|
-
modals.openCreateRef(
|
|
137
|
-
typeAnnotation.typename,
|
|
138
|
-
document.querySelector(cellQuery(contextEditing.index, gridId)),
|
|
139
|
-
{
|
|
140
|
-
[fieldProjection.field.referencePath!]: query,
|
|
141
|
-
},
|
|
142
|
-
handleCreate,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
[model, modals, client, contextEditing?.index, gridId, fieldProjection, handleSave],
|
|
147
|
-
);
|
|
148
|
-
|
|
149
123
|
useEffect(() => {
|
|
150
124
|
if (contextEditing && contextEditing.cellElement) {
|
|
151
125
|
anchorRef.current = (contextEditing.cellElement as HTMLElement).querySelector(
|
|
@@ -176,6 +150,38 @@ export const FormCellEditor = ({
|
|
|
176
150
|
setLocalEditing(nextOpen);
|
|
177
151
|
}, []);
|
|
178
152
|
|
|
153
|
+
const createSchema = useMemo(() => {
|
|
154
|
+
if (fieldProjection.props.format === FormatEnum.Ref && fieldProjection.props.referenceSchema) {
|
|
155
|
+
const { schema: refSchema } = getSchema({
|
|
156
|
+
typename: fieldProjection.props.referenceSchema,
|
|
157
|
+
});
|
|
158
|
+
if (!refSchema) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const omit = Schema.omit<any, any, ['id']>('id');
|
|
162
|
+
return omit(refSchema);
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
|
|
166
|
+
|
|
167
|
+
const handleCreate = useCallback(
|
|
168
|
+
(values: any) => {
|
|
169
|
+
if (schema && onCreate) {
|
|
170
|
+
const objectWithId = onCreate(schema, values);
|
|
171
|
+
if (objectWithId) {
|
|
172
|
+
const ref = Ref.make(objectWithId);
|
|
173
|
+
const path = fieldProjection.field.path;
|
|
174
|
+
setDeep(originalRow, [path], ref);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
contextEditing?.cellElement?.focus();
|
|
178
|
+
setEditing(null);
|
|
179
|
+
setLocalEditing(false);
|
|
180
|
+
onSave?.();
|
|
181
|
+
},
|
|
182
|
+
[fieldProjection.field.path, onSave, contextEditing, originalRow, createSchema, onCreate],
|
|
183
|
+
);
|
|
184
|
+
|
|
179
185
|
if (!editing) {
|
|
180
186
|
return null;
|
|
181
187
|
}
|
|
@@ -194,9 +200,13 @@ export const FormCellEditor = ({
|
|
|
194
200
|
onSave={handleSave}
|
|
195
201
|
{...formProps}
|
|
196
202
|
onQueryRefOptions={handleQueryRefOptions}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
{...(createSchema && {
|
|
204
|
+
onCreate: handleCreate,
|
|
205
|
+
createSchema,
|
|
206
|
+
createInitialValuePath: fieldProjection.field.referencePath,
|
|
207
|
+
createOptionIcon: 'ph--plus--regular',
|
|
208
|
+
createOptionLabel,
|
|
209
|
+
})}
|
|
200
210
|
/>
|
|
201
211
|
</Popover.Viewport>
|
|
202
212
|
</Popover.Content>
|
|
@@ -2,23 +2,17 @@
|
|
|
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 {
|
|
10
|
+
import { withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
16
12
|
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
17
|
-
import { DataType
|
|
18
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
13
|
+
import { DataType } from '@dxos/schema';
|
|
19
14
|
|
|
20
|
-
import {
|
|
21
|
-
import { type TableFeatures } from '../../model';
|
|
15
|
+
import { useTestTableModel } from '../../testing';
|
|
22
16
|
import { translations } from '../../translations';
|
|
23
17
|
import { Table } from '../../types';
|
|
24
18
|
|
|
@@ -29,39 +23,16 @@ type StoryProps = {
|
|
|
29
23
|
};
|
|
30
24
|
|
|
31
25
|
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(typenameFromQuery(view.query)!));
|
|
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
|
-
);
|
|
26
|
+
const { model, view } = useTestTableModel();
|
|
54
27
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!model || !schema || !view) {
|
|
28
|
+
if (!model || !view) {
|
|
58
29
|
return <div />;
|
|
59
30
|
}
|
|
60
31
|
|
|
61
32
|
return (
|
|
62
33
|
<div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
|
|
63
34
|
<Grid.Root id='test' editing={editing}>
|
|
64
|
-
<TableCellEditor model={model} schema={
|
|
35
|
+
<TableCellEditor model={model} schema={DataType.Task} />
|
|
65
36
|
</Grid.Root>
|
|
66
37
|
</div>
|
|
67
38
|
);
|
|
@@ -71,19 +42,18 @@ const meta = {
|
|
|
71
42
|
title: 'ui/react-ui-table/TableCellEditor',
|
|
72
43
|
component: DefaultStory,
|
|
73
44
|
render: DefaultStory,
|
|
74
|
-
parameters: { translations, layout: 'centered' },
|
|
75
45
|
decorators: [
|
|
46
|
+
withTheme,
|
|
76
47
|
withClientProvider({
|
|
77
|
-
types: [DataType.View, Table.Table],
|
|
48
|
+
types: [DataType.View, DataType.Task, Table.Table],
|
|
78
49
|
createIdentity: true,
|
|
79
50
|
createSpace: true,
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const { view } = await Table.makeView({ client, space, typename: schema.typename });
|
|
51
|
+
onCreateSpace: async ({ client, space }) => {
|
|
52
|
+
const { view } = await Table.makeView({ client, space, typename: DataType.Task.typename });
|
|
83
53
|
space.db.add(view);
|
|
84
54
|
Array.from({ length: 10 }).forEach(() => {
|
|
85
55
|
space.db.add(
|
|
86
|
-
Obj.make(
|
|
56
|
+
Obj.make(DataType.Task, {
|
|
87
57
|
title: faker.person.fullName(),
|
|
88
58
|
status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
89
59
|
description: faker.lorem.sentence(),
|
|
@@ -92,9 +62,11 @@ const meta = {
|
|
|
92
62
|
});
|
|
93
63
|
},
|
|
94
64
|
}),
|
|
95
|
-
withTheme,
|
|
96
|
-
withLayout(),
|
|
97
65
|
],
|
|
66
|
+
parameters: {
|
|
67
|
+
translations,
|
|
68
|
+
layout: 'centered',
|
|
69
|
+
},
|
|
98
70
|
} satisfies Meta<typeof DefaultStory>;
|
|
99
71
|
|
|
100
72
|
export default meta;
|
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
6
|
import { EditorView } from '@codemirror/view';
|
|
7
|
-
import
|
|
7
|
+
import type * as Schema from 'effect/Schema';
|
|
8
8
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
9
9
|
|
|
10
10
|
import { debounce } from '@dxos/async';
|
|
11
11
|
import type { Client } from '@dxos/client';
|
|
12
|
-
import { FormatEnum, TypeEnum } from '@dxos/echo
|
|
12
|
+
import { FormatEnum, TypeEnum } from '@dxos/echo/internal';
|
|
13
13
|
import { invariant } from '@dxos/invariant';
|
|
14
14
|
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
15
|
-
import { createMarkdownExtensions } from '@dxos/react-ui-editor';
|
|
16
15
|
import {
|
|
17
16
|
type EditorBlurHandler,
|
|
18
17
|
type EditorKeyEvent,
|
|
@@ -24,13 +23,12 @@ import {
|
|
|
24
23
|
parseCellIndex,
|
|
25
24
|
useGridContext,
|
|
26
25
|
} from '@dxos/react-ui-grid';
|
|
27
|
-
import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
|
|
28
26
|
import { type FieldProjection } from '@dxos/schema';
|
|
29
27
|
|
|
30
28
|
import { type ModalController, type TableModel } from '../../model';
|
|
31
29
|
|
|
32
30
|
import { CellValidationMessage } from './CellValidationMessage';
|
|
33
|
-
import { FormCellEditor } from './FormCellEditor';
|
|
31
|
+
import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
|
|
34
32
|
|
|
35
33
|
/**
|
|
36
34
|
* Option to create new object/value.
|
|
@@ -40,8 +38,9 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
|
40
38
|
export type TableCellEditorProps = {
|
|
41
39
|
model?: TableModel;
|
|
42
40
|
modals?: ModalController;
|
|
43
|
-
schema?: Schema.AnyNoContext;
|
|
41
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
44
42
|
onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
|
|
43
|
+
onCreate?: OnCreateHandler;
|
|
45
44
|
onSave?: () => void;
|
|
46
45
|
client?: Client;
|
|
47
46
|
};
|
|
@@ -55,6 +54,7 @@ export const TableValueEditor = ({
|
|
|
55
54
|
schema,
|
|
56
55
|
onFocus,
|
|
57
56
|
onSave,
|
|
57
|
+
onCreate,
|
|
58
58
|
client,
|
|
59
59
|
__gridScope,
|
|
60
60
|
}: GridScopedProps<TableCellEditorProps>) => {
|
|
@@ -85,6 +85,7 @@ export const TableValueEditor = ({
|
|
|
85
85
|
schema={schema}
|
|
86
86
|
__gridScope={__gridScope}
|
|
87
87
|
onSave={onSave}
|
|
88
|
+
onCreate={onCreate}
|
|
88
89
|
client={client}
|
|
89
90
|
modals={modals}
|
|
90
91
|
/>
|
|
@@ -213,48 +214,6 @@ export const TableCellEditor = ({
|
|
|
213
214
|
...(editing?.initialContent && { onNav: handleClose }),
|
|
214
215
|
}),
|
|
215
216
|
];
|
|
216
|
-
|
|
217
|
-
const format = fieldProjection.props.format;
|
|
218
|
-
if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
|
|
219
|
-
// TODO(ZaymonFC): Reconcile this with the TagPicker component?
|
|
220
|
-
const options = fieldProjection.props.options || [];
|
|
221
|
-
const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
|
|
222
|
-
|
|
223
|
-
// Add markdown extensions needed by tag picker.
|
|
224
|
-
extensions.push(createMarkdownExtensions());
|
|
225
|
-
extensions.push(
|
|
226
|
-
tagPicker({
|
|
227
|
-
mode,
|
|
228
|
-
keymap: false,
|
|
229
|
-
onSearch: (text, selectedIds) => {
|
|
230
|
-
return options
|
|
231
|
-
.filter(
|
|
232
|
-
(option) =>
|
|
233
|
-
selectedIds.indexOf(option.id) === -1 &&
|
|
234
|
-
(text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase())),
|
|
235
|
-
)
|
|
236
|
-
.map((option) => ({
|
|
237
|
-
id: option.id,
|
|
238
|
-
label: option.title,
|
|
239
|
-
hue: option.color as any,
|
|
240
|
-
}));
|
|
241
|
-
},
|
|
242
|
-
onUpdate: (ids) => {
|
|
243
|
-
if (model && editing) {
|
|
244
|
-
if (ids.length === 0) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (mode === 'single-select') {
|
|
248
|
-
void handleEnter(ids[0]);
|
|
249
|
-
} else {
|
|
250
|
-
void handleEnter(ids);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
}),
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
217
|
// Add validation extension to handle content changes.
|
|
259
218
|
if (model && editing) {
|
|
260
219
|
extensions.push(
|
|
@@ -283,52 +242,6 @@ export const TableCellEditor = ({
|
|
|
283
242
|
const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
|
|
284
243
|
if (model && editing) {
|
|
285
244
|
const cell = parseCellIndex(editing.index);
|
|
286
|
-
const { col } = cell;
|
|
287
|
-
const field = model.projection.fields[col];
|
|
288
|
-
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
fieldProjection?.props.format === FormatEnum.SingleSelect ||
|
|
292
|
-
fieldProjection?.props.format === FormatEnum.MultiSelect
|
|
293
|
-
) {
|
|
294
|
-
const value = model.getCellData(cell);
|
|
295
|
-
if (value !== undefined) {
|
|
296
|
-
const options = fieldProjection.props.options || [];
|
|
297
|
-
if (fieldProjection.props.format === FormatEnum.MultiSelect) {
|
|
298
|
-
const tagItems = value
|
|
299
|
-
.split(',')
|
|
300
|
-
.map((id: string) => {
|
|
301
|
-
const option = options.find((o) => o.id === id);
|
|
302
|
-
if (option) {
|
|
303
|
-
return {
|
|
304
|
-
id,
|
|
305
|
-
label: option.title,
|
|
306
|
-
hue: option.color as any,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
return undefined;
|
|
310
|
-
})
|
|
311
|
-
.filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
|
|
312
|
-
|
|
313
|
-
return createLinks(tagItems);
|
|
314
|
-
} else {
|
|
315
|
-
const option = options.find((o) => o.id === value);
|
|
316
|
-
|
|
317
|
-
if (option) {
|
|
318
|
-
const tagItem = {
|
|
319
|
-
id: value,
|
|
320
|
-
label: option.title,
|
|
321
|
-
hue: option.color as any,
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
return createLinks([tagItem]);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return '';
|
|
330
|
-
}
|
|
331
|
-
|
|
332
245
|
const value = model.getCellData(cell);
|
|
333
246
|
return value !== undefined ? String(value) : '';
|
|
334
247
|
}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo
|
|
8
|
+
import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
|
|
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
|
@@ -6,7 +6,7 @@ import { effect } from '@preact/signals-core';
|
|
|
6
6
|
import orderBy from 'lodash.orderby';
|
|
7
7
|
import { useEffect, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import { type JsonSchemaType } from '@dxos/echo
|
|
9
|
+
import { type JsonSchemaType } from '@dxos/echo/internal';
|
|
10
10
|
import { type Live } from '@dxos/live-object';
|
|
11
11
|
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
12
12
|
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
@@ -5,21 +5,12 @@
|
|
|
5
5
|
import { signal } from '@preact/signals-core';
|
|
6
6
|
import { type MutableRefObject, type RefObject } from 'react';
|
|
7
7
|
|
|
8
|
-
import { type Live } from '@dxos/live-object';
|
|
9
|
-
import { log } from '@dxos/log';
|
|
10
|
-
|
|
11
8
|
export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
|
|
12
9
|
|
|
13
10
|
export type ModalState =
|
|
14
11
|
| { type: 'row'; rowIndex: number }
|
|
15
12
|
| { type: 'column'; fieldId: string }
|
|
16
13
|
| { type: 'refPanel'; targetId: string; typename: string }
|
|
17
|
-
| {
|
|
18
|
-
type: 'createRefPanel';
|
|
19
|
-
typename: string;
|
|
20
|
-
initialValues?: Record<string, string>;
|
|
21
|
-
onCreate?: (obj: Live<any>) => void;
|
|
22
|
-
}
|
|
23
14
|
| { type: 'columnSettings'; mode: ColumnSettingsMode }
|
|
24
15
|
| { type: 'closed' };
|
|
25
16
|
|
|
@@ -68,25 +59,6 @@ export class ModalController {
|
|
|
68
59
|
}
|
|
69
60
|
};
|
|
70
61
|
|
|
71
|
-
public openCreateRef = (
|
|
72
|
-
typename: string,
|
|
73
|
-
anchorCell: Element | null,
|
|
74
|
-
initialValues?: Record<string, string>,
|
|
75
|
-
onCreate?: (obj: Live<any>) => void,
|
|
76
|
-
) => {
|
|
77
|
-
if (anchorCell) {
|
|
78
|
-
this._triggerRef.current = anchorCell as HTMLElement;
|
|
79
|
-
this._state.value = {
|
|
80
|
-
type: 'createRefPanel',
|
|
81
|
-
typename,
|
|
82
|
-
initialValues,
|
|
83
|
-
onCreate,
|
|
84
|
-
};
|
|
85
|
-
} else {
|
|
86
|
-
log.warn('anchor cell not found while creating new ref');
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
62
|
public close = () => {
|
|
91
63
|
this._state.value = { type: 'closed' };
|
|
92
64
|
};
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { computed } from '@preact/signals-core';
|
|
6
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
8
8
|
|
|
9
9
|
import { Filter, Query } from '@dxos/echo';
|
|
10
|
-
import { TypedObject } from '@dxos/echo
|
|
11
|
-
import { updateCounter } from '@dxos/echo
|
|
10
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
11
|
+
import { updateCounter } from '@dxos/echo/testing';
|
|
12
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
12
13
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
13
14
|
import { live } from '@dxos/live-object';
|
|
14
|
-
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
15
15
|
import { createView } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { Table } from '../types';
|
|
@@ -25,7 +25,7 @@ registerSignalsRuntime();
|
|
|
25
25
|
|
|
26
26
|
describe('TableModel', () => {
|
|
27
27
|
let updateCount = 0;
|
|
28
|
-
let model:
|
|
28
|
+
let model: any;
|
|
29
29
|
|
|
30
30
|
beforeEach(async () => {
|
|
31
31
|
updateCount = 0;
|
|
@@ -33,6 +33,11 @@ describe('TableModel', () => {
|
|
|
33
33
|
onCellUpdate: () => updateCount++,
|
|
34
34
|
});
|
|
35
35
|
await model.open();
|
|
36
|
+
model.setRows([
|
|
37
|
+
{ id: '1', title: 'Test', completed: false },
|
|
38
|
+
{ id: '2', title: 'Test 2', completed: true },
|
|
39
|
+
{ id: '3', title: 'Test 3', completed: false },
|
|
40
|
+
]);
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
afterEach(async () => {
|
package/src/model/table-model.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getValue,
|
|
15
15
|
setValue,
|
|
16
16
|
toEffectSchema,
|
|
17
|
-
} from '@dxos/echo
|
|
17
|
+
} from '@dxos/echo/internal';
|
|
18
18
|
import { invariant } from '@dxos/invariant';
|
|
19
19
|
import { ObjectId } from '@dxos/keys';
|
|
20
20
|
import { getSnapshot, isLiveObject } from '@dxos/live-object';
|
|
@@ -141,6 +141,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
|
|
|
141
141
|
super();
|
|
142
142
|
this._view = view;
|
|
143
143
|
this._projection = projection ?? new ProjectionModel(schema, view.projection);
|
|
144
|
+
this._projection.normalizeView();
|
|
144
145
|
|
|
145
146
|
// TODO(ZaymonFC): Use our more robust config merging module?
|
|
146
147
|
this._features = { ...defaultFeatures, ...features };
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
7
7
|
|
|
8
8
|
import { Filter, Query } from '@dxos/echo';
|
|
9
|
-
import { TypedObject } from '@dxos/echo
|
|
9
|
+
import { TypedObject } from '@dxos/echo/internal';
|
|
10
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
10
11
|
import { live } from '@dxos/live-object';
|
|
11
|
-
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
12
12
|
import { createView } from '@dxos/schema';
|
|
13
13
|
|
|
14
14
|
import { Table } from '../types';
|
|
@@ -27,8 +27,8 @@ describe('TablePresentation', () => {
|
|
|
27
27
|
updateCount = 0;
|
|
28
28
|
({ data } = live({
|
|
29
29
|
data: [
|
|
30
|
-
{
|
|
31
|
-
{
|
|
30
|
+
{ title: 'A', count: 1 },
|
|
31
|
+
{ title: 'B', count: 2 },
|
|
32
32
|
],
|
|
33
33
|
}));
|
|
34
34
|
|
|
@@ -45,8 +45,8 @@ describe('TablePresentation', () => {
|
|
|
45
45
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
46
46
|
|
|
47
47
|
// Trigger a row update.
|
|
48
|
-
data[0].
|
|
49
|
-
expect(updateCount).toBe(
|
|
48
|
+
data[0].title = 'New Value';
|
|
49
|
+
expect(updateCount).toBe(2);
|
|
50
50
|
|
|
51
51
|
// Verify the new value through getCells.
|
|
52
52
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
|
|
@@ -57,15 +57,14 @@ describe('TablePresentation', () => {
|
|
|
57
57
|
// Set up visible range to include our test data.
|
|
58
58
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
59
59
|
|
|
60
|
-
data.push({
|
|
61
|
-
expect(updateCount).toBe(
|
|
60
|
+
data.push({ title: 'C', count: 3 });
|
|
61
|
+
expect(updateCount).toBe(2);
|
|
62
62
|
|
|
63
63
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
64
64
|
|
|
65
65
|
// Verify the new row's data.
|
|
66
66
|
expect(cells['0,2']?.value).toBe('C');
|
|
67
67
|
expect(cells['1,2']?.value).toBe('3');
|
|
68
|
-
expect(cells['2,2']?.value).toBe('true');
|
|
69
68
|
});
|
|
70
69
|
|
|
71
70
|
it('should handle combined operations reactively', () => {
|
|
@@ -73,27 +72,25 @@ describe('TablePresentation', () => {
|
|
|
73
72
|
presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
|
|
74
73
|
|
|
75
74
|
// Multiple operations.
|
|
76
|
-
data[0].
|
|
77
|
-
data.push({
|
|
75
|
+
data[0].title = 'Updated A';
|
|
76
|
+
data.push({ title: 'C', count: 3, completed: true });
|
|
78
77
|
data.splice(1, 1);
|
|
79
78
|
|
|
80
79
|
// We expect one update per operation affecting visible rows.
|
|
81
|
-
expect(updateCount).toBe(
|
|
80
|
+
expect(updateCount).toBe(4);
|
|
82
81
|
|
|
83
82
|
const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
|
|
84
83
|
|
|
85
84
|
// Verify final state.
|
|
86
85
|
expect(cells['0,0']?.value).toBe('Updated A');
|
|
87
86
|
expect(cells['0,1']?.value).toBe('C');
|
|
88
|
-
expect(cells['1,1']?.value).toBe('3');
|
|
89
|
-
expect(cells['2,1']?.value).toBe('true');
|
|
90
87
|
});
|
|
91
88
|
});
|
|
92
89
|
});
|
|
93
90
|
|
|
94
91
|
class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
95
92
|
title: Schema.String,
|
|
96
|
-
|
|
93
|
+
count: Schema.Number,
|
|
97
94
|
}) {}
|
|
98
95
|
|
|
99
96
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|