@dxos/react-ui-table 0.8.2-main.f11618f → 0.8.2-staging.42af850
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-U6LL2AVU.mjs +26 -0
- package/dist/lib/browser/chunk-U6LL2AVU.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1074 -828
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node/{chunk-TGG6OZ54.cjs → chunk-CXDGXPM6.cjs} +12 -12
- package/dist/lib/node/chunk-CXDGXPM6.cjs.map +7 -0
- package/dist/lib/node/index.cjs +1080 -837
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types/index.cjs +3 -3
- package/dist/lib/node/types/index.cjs.map +1 -1
- package/dist/lib/node-esm/{chunk-KKLP54L3.mjs → chunk-EFSM52KQ.mjs} +10 -10
- package/dist/lib/node-esm/chunk-EFSM52KQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1074 -828
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +3 -4
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +2 -0
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -0
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +13 -0
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -0
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -0
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +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 +6 -3
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +39 -39
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/types/table.d.ts +46 -45
- package/dist/types/src/types/table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +4 -3
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +12 -0
- package/dist/types/src/util/schema.d.ts.map +1 -0
- package/dist/types/src/util/signals.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 +2 -2
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/src/util/tag.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +41 -34
- package/src/components/Table/CreateRefPanel.tsx +3 -3
- package/src/components/Table/DynamicTable.stories.tsx +2 -2
- package/src/components/Table/DynamicTable.tsx +4 -5
- package/src/components/Table/Table.stories.tsx +76 -42
- package/src/components/Table/Table.tsx +8 -5
- package/src/components/Table/relations.stories.tsx +17 -20
- package/src/components/TableCellEditor/FormCellEditor.tsx +94 -0
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +10 -8
- package/src/components/TableCellEditor/TableCellEditor.tsx +45 -4
- package/src/components/TableToolbar/TableToolbar.tsx +63 -33
- package/src/components/testing.ts +7 -6
- package/src/hooks/useTableModel.ts +4 -2
- package/src/model/selection-model.ts +1 -1
- package/src/model/table-model.test.ts +4 -3
- package/src/model/table-model.ts +1 -1
- package/src/model/table-presentation.test.ts +4 -3
- package/src/model/table-presentation.ts +53 -7
- package/src/types/table.ts +16 -11
- package/src/util/dynamic-table.ts +7 -4
- package/src/util/index.ts +1 -0
- package/src/util/schema.ts +36 -0
- package/src/util/table-buttons.ts +1 -1
- package/src/util/table.ts +11 -10
- package/dist/lib/browser/chunk-YWNRUF4J.mjs +0 -26
- package/dist/lib/browser/chunk-YWNRUF4J.mjs.map +0 -7
- package/dist/lib/node/chunk-TGG6OZ54.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-KKLP54L3.mjs.map +0 -7
|
@@ -5,9 +5,17 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type StoryObj, type Meta } from '@storybook/react';
|
|
8
|
+
import { Schema } from 'effect';
|
|
8
9
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
assertEchoSchema,
|
|
13
|
+
FormatEnum,
|
|
14
|
+
isMutable,
|
|
15
|
+
toJsonSchema,
|
|
16
|
+
EchoObject,
|
|
17
|
+
GeneratorAnnotation,
|
|
18
|
+
} from '@dxos/echo-schema';
|
|
11
19
|
import { invariant } from '@dxos/invariant';
|
|
12
20
|
import { useGlobalFilteredObjects } from '@dxos/plugin-search';
|
|
13
21
|
import { faker } from '@dxos/random';
|
|
@@ -28,8 +36,7 @@ import { TableType } from '../../types';
|
|
|
28
36
|
import { initializeTable } from '../../util';
|
|
29
37
|
import { TableToolbar } from '../TableToolbar';
|
|
30
38
|
|
|
31
|
-
// NOTE(ZaymonFC):
|
|
32
|
-
faker.seed(0);
|
|
39
|
+
faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
|
|
33
40
|
|
|
34
41
|
/**
|
|
35
42
|
* Custom hook to create and manage a test table model for storybook demonstrations.
|
|
@@ -39,14 +46,14 @@ const useTestTableModel = () => {
|
|
|
39
46
|
const client = useClient();
|
|
40
47
|
const { space } = useClientProvider();
|
|
41
48
|
|
|
42
|
-
const filter = useMemo(() => Filter.
|
|
49
|
+
const filter = useMemo(() => Filter.type(TableType), []);
|
|
43
50
|
const tables = useQuery(space, filter);
|
|
44
51
|
const table = useMemo(() => tables.at(0), [tables]);
|
|
45
52
|
const schema = useSchema(client, space, table?.view?.target?.query.typename);
|
|
46
53
|
|
|
47
54
|
const projection = useMemo(() => {
|
|
48
55
|
if (schema && table?.view?.target) {
|
|
49
|
-
return new ViewProjection(schema
|
|
56
|
+
return new ViewProjection(toJsonSchema(schema), table.view.target);
|
|
50
57
|
}
|
|
51
58
|
}, [schema, table?.view?.target]);
|
|
52
59
|
|
|
@@ -54,12 +61,12 @@ const useTestTableModel = () => {
|
|
|
54
61
|
() => ({
|
|
55
62
|
selection: { enabled: true, mode: 'multiple' as const },
|
|
56
63
|
dataEditable: true,
|
|
57
|
-
schemaEditable:
|
|
64
|
+
schemaEditable: schema && isMutable(schema),
|
|
58
65
|
}),
|
|
59
66
|
[schema],
|
|
60
67
|
);
|
|
61
68
|
|
|
62
|
-
const objects = useQuery(space, schema ? Filter.
|
|
69
|
+
const objects = useQuery(space, schema ? Filter.type(schema) : Filter.nothing());
|
|
63
70
|
const filteredObjects = useGlobalFilteredObjects(objects);
|
|
64
71
|
|
|
65
72
|
const tableRef = useRef<TableController>(null);
|
|
@@ -107,32 +114,16 @@ const useTestTableModel = () => {
|
|
|
107
114
|
onRowOrderChange: handleRowOrderChange,
|
|
108
115
|
});
|
|
109
116
|
|
|
117
|
+
const handleSaveView = useCallback(() => {
|
|
118
|
+
model?.saveView();
|
|
119
|
+
}, [model]);
|
|
120
|
+
|
|
110
121
|
const presentation = useMemo(() => {
|
|
111
122
|
if (model) {
|
|
112
123
|
return new TablePresentation(model);
|
|
113
124
|
}
|
|
114
125
|
}, [model]);
|
|
115
126
|
|
|
116
|
-
const handleToolbarAction = useCallback(
|
|
117
|
-
(action: { type: string }) => {
|
|
118
|
-
switch (action.type) {
|
|
119
|
-
case 'on-thread-create': {
|
|
120
|
-
console.log('Thread creation triggered');
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
case 'add-row': {
|
|
124
|
-
handleInsertRow();
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
case 'save-view': {
|
|
128
|
-
model?.saveView();
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
[table, model],
|
|
134
|
-
);
|
|
135
|
-
|
|
136
127
|
return {
|
|
137
128
|
schema,
|
|
138
129
|
table,
|
|
@@ -140,7 +131,8 @@ const useTestTableModel = () => {
|
|
|
140
131
|
model,
|
|
141
132
|
presentation,
|
|
142
133
|
space,
|
|
143
|
-
|
|
134
|
+
handleInsertRow,
|
|
135
|
+
handleSaveView,
|
|
144
136
|
handleDeleteRows,
|
|
145
137
|
handleDeleteColumn,
|
|
146
138
|
};
|
|
@@ -151,11 +143,10 @@ const StoryViewEditor = () => {
|
|
|
151
143
|
|
|
152
144
|
const handleTypenameChanged = useCallback(
|
|
153
145
|
(typename: string) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
146
|
+
invariant(schema);
|
|
147
|
+
invariant(table?.view?.target);
|
|
148
|
+
assertEchoSchema(schema).updateTypename(typename);
|
|
149
|
+
table.view.target.query.typename = typename;
|
|
159
150
|
},
|
|
160
151
|
[schema, table?.view?.target],
|
|
161
152
|
);
|
|
@@ -180,7 +171,7 @@ const StoryViewEditor = () => {
|
|
|
180
171
|
//
|
|
181
172
|
|
|
182
173
|
const DefaultStory = () => {
|
|
183
|
-
const { schema, table, tableRef, model, presentation,
|
|
174
|
+
const { schema, table, tableRef, model, presentation, handleInsertRow, handleSaveView } = useTestTableModel();
|
|
184
175
|
|
|
185
176
|
if (!schema || !table) {
|
|
186
177
|
return <div />;
|
|
@@ -189,9 +180,9 @@ const DefaultStory = () => {
|
|
|
189
180
|
return (
|
|
190
181
|
<div className='grow grid grid-cols-[1fr_350px]'>
|
|
191
182
|
<div className='grid grid-rows-[min-content_1fr] min-bs-0 overflow-hidden'>
|
|
192
|
-
<TableToolbar classNames='border-be border-separator'
|
|
183
|
+
<TableToolbar classNames='border-be border-separator' onAdd={handleInsertRow} onSave={handleSaveView} />
|
|
193
184
|
<Table.Root>
|
|
194
|
-
<Table.Main ref={tableRef} model={model} presentation={presentation} ignoreAttention />
|
|
185
|
+
<Table.Main ref={tableRef} model={model} presentation={presentation} schema={schema} ignoreAttention />
|
|
195
186
|
</Table.Root>
|
|
196
187
|
</div>
|
|
197
188
|
<div className='flex flex-col h-full border-l border-separator overflow-y-auto'>
|
|
@@ -233,8 +224,8 @@ const meta: Meta<StoryProps> = {
|
|
|
233
224
|
});
|
|
234
225
|
},
|
|
235
226
|
}),
|
|
227
|
+
withLayout({ fullscreen: true }),
|
|
236
228
|
withTheme,
|
|
237
|
-
withLayout({ fullscreen: true, tooltips: true }),
|
|
238
229
|
],
|
|
239
230
|
};
|
|
240
231
|
|
|
@@ -252,17 +243,60 @@ export const StaticSchema: StoryObj = {
|
|
|
252
243
|
createSpace: true,
|
|
253
244
|
onSpaceCreated: async ({ client, space }) => {
|
|
254
245
|
const table = space.db.add(live(TableType, {}));
|
|
255
|
-
await initializeTable({ client, space, table, typename: Testing.
|
|
246
|
+
await initializeTable({ client, space, table, typename: Testing.Organization.typename });
|
|
247
|
+
|
|
248
|
+
const factory = createObjectFactory(space.db, faker as any);
|
|
249
|
+
await factory([
|
|
250
|
+
// { type: Testing.Contact, count: 10 },
|
|
251
|
+
// { type: Testing.Organization, count: 1 },
|
|
252
|
+
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
253
|
+
]);
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
256
|
+
withLayout({ fullscreen: true }),
|
|
257
|
+
withTheme,
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const ContactWithArrayOfEmails = Schema.Struct({
|
|
262
|
+
name: Schema.String.pipe(GeneratorAnnotation.set('person.fullName')),
|
|
263
|
+
emails: Schema.optional(
|
|
264
|
+
Schema.Array(
|
|
265
|
+
Schema.Struct({
|
|
266
|
+
value: Schema.String,
|
|
267
|
+
label: Schema.String.pipe(Schema.optional),
|
|
268
|
+
}),
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
}).pipe(
|
|
272
|
+
EchoObject({
|
|
273
|
+
typename: 'dxos.org/type/ContactWithArrayOfEmails',
|
|
274
|
+
version: '0.1.0',
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
export const ArrayOfObjects: StoryObj = {
|
|
279
|
+
render: DefaultStory,
|
|
280
|
+
parameters: { translations },
|
|
281
|
+
decorators: [
|
|
282
|
+
withClientProvider({
|
|
283
|
+
types: [TableType, ViewType, Testing.Contact, Testing.Organization, ContactWithArrayOfEmails],
|
|
284
|
+
createIdentity: true,
|
|
285
|
+
createSpace: true,
|
|
286
|
+
onSpaceCreated: async ({ client, space }) => {
|
|
287
|
+
const table = space.db.add(live(TableType, {}));
|
|
288
|
+
await initializeTable({ client, space, table, typename: ContactWithArrayOfEmails.typename });
|
|
256
289
|
|
|
257
290
|
const factory = createObjectFactory(space.db, faker as any);
|
|
258
291
|
await factory([
|
|
259
|
-
{ type: Testing.Contact, count: 10 },
|
|
260
|
-
{ type: Testing.Organization, count: 1 },
|
|
292
|
+
// { type: Testing.Contact, count: 10 },
|
|
293
|
+
// { type: Testing.Organization, count: 1 },
|
|
294
|
+
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
261
295
|
]);
|
|
262
296
|
},
|
|
263
297
|
}),
|
|
298
|
+
withLayout({ fullscreen: true }),
|
|
264
299
|
withTheme,
|
|
265
|
-
withLayout({ fullscreen: true, tooltips: true }),
|
|
266
300
|
],
|
|
267
301
|
};
|
|
268
302
|
|
|
@@ -317,7 +351,7 @@ export const Tags: Meta<StoryProps> = {
|
|
|
317
351
|
});
|
|
318
352
|
},
|
|
319
353
|
}),
|
|
354
|
+
withLayout({ fullscreen: true }),
|
|
320
355
|
withTheme,
|
|
321
|
-
withLayout({ fullscreen: true, tooltips: true }),
|
|
322
356
|
],
|
|
323
357
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Schema } from 'effect/Schema';
|
|
5
6
|
import React, {
|
|
6
7
|
type MouseEvent,
|
|
7
8
|
type PropsWithChildren,
|
|
@@ -40,7 +41,7 @@ import { RowActionsMenu } from './RowActionsMenu';
|
|
|
40
41
|
import { ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
41
42
|
import { translationKey } from '../../translations';
|
|
42
43
|
import { tableButtons, tableControls } from '../../util';
|
|
43
|
-
import { createOption,
|
|
44
|
+
import { createOption, TableValueEditor, type TableCellEditorProps } from '../TableCellEditor';
|
|
44
45
|
|
|
45
46
|
//
|
|
46
47
|
// Table.Root
|
|
@@ -75,13 +76,14 @@ export type TableController = {
|
|
|
75
76
|
export type TableMainProps = {
|
|
76
77
|
model?: TableModel;
|
|
77
78
|
presentation?: TablePresentation;
|
|
79
|
+
schema?: Schema.AnyNoContext;
|
|
78
80
|
// TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
|
|
79
81
|
ignoreAttention?: boolean;
|
|
80
82
|
onRowClick?: (row: any) => void;
|
|
81
83
|
};
|
|
82
84
|
|
|
83
85
|
const TableMain = forwardRef<TableController, TableMainProps>(
|
|
84
|
-
({ model, presentation, ignoreAttention, onRowClick }, forwardedRef) => {
|
|
86
|
+
({ model, presentation, ignoreAttention, schema, onRowClick }, forwardedRef) => {
|
|
85
87
|
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
86
88
|
const { hasAttention } = useAttention(model?.id ?? 'table');
|
|
87
89
|
const { t } = useTranslation(translationKey);
|
|
@@ -307,7 +309,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
307
309
|
invariant(space);
|
|
308
310
|
const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
|
|
309
311
|
if (schema) {
|
|
310
|
-
const { objects } = await space.db.query(Filter.
|
|
312
|
+
const { objects } = await space.db.query(Filter.type(schema)).run();
|
|
311
313
|
const options = objects
|
|
312
314
|
.map((obj) => {
|
|
313
315
|
const value = getValue(obj, field.referencePath!);
|
|
@@ -343,15 +345,16 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
343
345
|
|
|
344
346
|
return (
|
|
345
347
|
<Grid.Root id={model.id ?? 'table-grid'}>
|
|
346
|
-
<
|
|
348
|
+
<TableValueEditor
|
|
347
349
|
model={model}
|
|
348
350
|
modals={modals}
|
|
351
|
+
schema={schema}
|
|
349
352
|
onEnter={handleEnter}
|
|
350
353
|
onFocus={handleFocus}
|
|
351
354
|
onQuery={handleQuery}
|
|
352
355
|
/>
|
|
353
356
|
<Grid.Content
|
|
354
|
-
className={mx('[--dx-grid-base:var(--
|
|
357
|
+
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
355
358
|
frozen={frozen}
|
|
356
359
|
// getCells={getCells}
|
|
357
360
|
columns={model.columnMeta.value}
|
|
@@ -5,14 +5,17 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type StoryObj, type Meta } from '@storybook/react';
|
|
8
|
+
import { type Schema } from 'effect';
|
|
9
|
+
import { SchemaAST } from 'effect';
|
|
8
10
|
import React, { useEffect, useMemo } from 'react';
|
|
9
11
|
|
|
10
|
-
import {
|
|
12
|
+
import { type BaseObject, getSchemaTypename, type HasId, toJsonSchema } from '@dxos/echo-schema';
|
|
11
13
|
import { getAnnotation } from '@dxos/effect';
|
|
14
|
+
import { invariant } from '@dxos/invariant';
|
|
12
15
|
import { faker } from '@dxos/random';
|
|
13
16
|
import { live, makeRef } from '@dxos/react-client/echo';
|
|
14
17
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
15
|
-
import {
|
|
18
|
+
import { DataType, createView, ViewProjection, ViewType } from '@dxos/schema';
|
|
16
19
|
import { createAsyncGenerator, type ValueGenerator } from '@dxos/schema/testing';
|
|
17
20
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
18
21
|
|
|
@@ -29,14 +32,15 @@ const generator: ValueGenerator = faker as any;
|
|
|
29
32
|
// TODO(burdon): Mutable and immutable views.
|
|
30
33
|
// TODO(burdon): Reconcile schemas types and utils (see API PR).
|
|
31
34
|
// TODO(burdon): Base type for T (with id); see ECHO API PR?
|
|
32
|
-
const useTestModel = <T extends BaseObject & HasId>(schema:
|
|
35
|
+
const useTestModel = <T extends BaseObject & HasId>(schema: Schema.Schema<T>, count: number) => {
|
|
33
36
|
const { space } = useClientProvider();
|
|
34
37
|
|
|
38
|
+
const jsonSchema = useMemo(() => toJsonSchema(schema), [schema]);
|
|
35
39
|
const table = useMemo(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const name = getAnnotation<string>(
|
|
39
|
-
const view = createView({ name, typename, jsonSchema
|
|
40
|
+
const typename = getSchemaTypename(schema);
|
|
41
|
+
invariant(typename);
|
|
42
|
+
const name = getAnnotation<string>(SchemaAST.TitleAnnotationId)(schema.ast) ?? typename;
|
|
43
|
+
const view = createView({ name, typename, jsonSchema });
|
|
40
44
|
return live(TableType, { view: makeRef(view) });
|
|
41
45
|
}, [schema]);
|
|
42
46
|
|
|
@@ -46,7 +50,7 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
// TODO(burdon): Just pass in view? Reuse same jsonSchema instance? View determines if mutable, etc.
|
|
49
|
-
return new ViewProjection(
|
|
53
|
+
return new ViewProjection(jsonSchema, table.view.target);
|
|
50
54
|
}, [schema, table]);
|
|
51
55
|
|
|
52
56
|
const features = useMemo<TableFeatures>(
|
|
@@ -60,14 +64,12 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
|
|
|
60
64
|
return;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
const objectGenerator = createAsyncGenerator(generator, schema, {
|
|
67
|
+
const objectGenerator = createAsyncGenerator(generator, schema, { db: space?.db, force: true });
|
|
64
68
|
void objectGenerator.createObjects(count).then((objects) => {
|
|
65
69
|
model.setRows(objects);
|
|
66
70
|
});
|
|
67
71
|
}, [model, space]);
|
|
68
72
|
|
|
69
|
-
console.log(projection?.getFieldProjections());
|
|
70
|
-
|
|
71
73
|
const presentation = useMemo(() => {
|
|
72
74
|
if (!model) {
|
|
73
75
|
return;
|
|
@@ -80,13 +82,8 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
|
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
const DefaultStory = () => {
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const { model: orgModel, presentation: orgPresentation } = useTestModel<Organization>(orgSchema, 50);
|
|
86
|
-
|
|
87
|
-
// TODO(burdon): Generate links with references.
|
|
88
|
-
const contactSchema = useMemo(() => new ImmutableSchema(Contact), []);
|
|
89
|
-
const { model: contactModel, presentation: contactPresentation } = useTestModel<Contact>(contactSchema, 50);
|
|
85
|
+
const { model: orgModel, presentation: orgPresentation } = useTestModel(DataType.Organization, 50);
|
|
86
|
+
const { model: contactModel, presentation: contactPresentation } = useTestModel(DataType.Person, 50);
|
|
90
87
|
|
|
91
88
|
return (
|
|
92
89
|
<div className='is-full bs-full grid grid-cols-2 divide-x divide-separator'>
|
|
@@ -106,12 +103,12 @@ const meta: Meta<typeof DefaultStory> = {
|
|
|
106
103
|
parameters: { translations },
|
|
107
104
|
decorators: [
|
|
108
105
|
withClientProvider({
|
|
109
|
-
types: [TableType, ViewType, Organization,
|
|
106
|
+
types: [TableType, ViewType, DataType.Organization, DataType.Person],
|
|
110
107
|
createIdentity: true,
|
|
111
108
|
createSpace: true,
|
|
112
109
|
}),
|
|
113
110
|
withTheme,
|
|
114
|
-
withLayout({ fullscreen: true
|
|
111
|
+
withLayout({ fullscreen: true }),
|
|
115
112
|
],
|
|
116
113
|
};
|
|
117
114
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Schema } from 'effect';
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
import { getSnapshot } from '@dxos/echo-schema';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
|
+
import { Popover } from '@dxos/react-ui';
|
|
11
|
+
import { Form } from '@dxos/react-ui-form';
|
|
12
|
+
import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
13
|
+
import { type FieldProjection } from '@dxos/schema';
|
|
14
|
+
import { getDeep, setDeep } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import { type TableModel } from '../../model';
|
|
17
|
+
import { narrowSchema } from '../../util';
|
|
18
|
+
|
|
19
|
+
type FormCellEditorProps = {
|
|
20
|
+
fieldProjection: FieldProjection;
|
|
21
|
+
model?: TableModel;
|
|
22
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
23
|
+
__gridScope: any;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }: FormCellEditorProps) => {
|
|
27
|
+
const { editing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
28
|
+
const cellRef = useRef<HTMLButtonElement | null>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (editing && editing.cellElement) {
|
|
32
|
+
cellRef.current = editing.cellElement as HTMLButtonElement;
|
|
33
|
+
}
|
|
34
|
+
}, [editing?.cellElement]);
|
|
35
|
+
|
|
36
|
+
const narrowedSchema = useMemo(() => {
|
|
37
|
+
if (!schema) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return narrowSchema(schema, [fieldProjection.field.path]);
|
|
41
|
+
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
42
|
+
|
|
43
|
+
const originalRow = useMemo(() => {
|
|
44
|
+
if (model && editing) {
|
|
45
|
+
const cell = parseCellIndex(editing.index);
|
|
46
|
+
const row = model.getRowAt(cell.row);
|
|
47
|
+
invariant(row);
|
|
48
|
+
|
|
49
|
+
return row;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return undefined;
|
|
53
|
+
}, [model, editing]);
|
|
54
|
+
|
|
55
|
+
const formValues = useMemo(() => {
|
|
56
|
+
if (originalRow) {
|
|
57
|
+
// NOTE(ZaymonFC): Important to get a snapshot to eject from the live object.
|
|
58
|
+
return getSnapshot(originalRow);
|
|
59
|
+
}
|
|
60
|
+
}, [originalRow]);
|
|
61
|
+
|
|
62
|
+
const handleSave = useCallback(
|
|
63
|
+
(values: any) => {
|
|
64
|
+
const path = fieldProjection.field.path;
|
|
65
|
+
const value = getDeep(values, [path]);
|
|
66
|
+
setDeep(originalRow, [path], value);
|
|
67
|
+
setEditing(null);
|
|
68
|
+
},
|
|
69
|
+
[fieldProjection.field.path, originalRow],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleOpenChange = useCallback(
|
|
73
|
+
(nextOpen: boolean) => {
|
|
74
|
+
if (nextOpen === false) {
|
|
75
|
+
setEditing(null);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[setEditing],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!editing) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Popover.Root open={editing !== null} onOpenChange={handleOpenChange}>
|
|
87
|
+
<Popover.VirtualTrigger virtualRef={cellRef} />
|
|
88
|
+
<Popover.Content tabIndex={-1} classNames='popover-card-width'>
|
|
89
|
+
<Popover.Arrow />
|
|
90
|
+
<Form values={formValues} schema={narrowedSchema as any} onSave={handleSave} />
|
|
91
|
+
</Popover.Content>
|
|
92
|
+
</Popover.Root>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
@@ -7,12 +7,12 @@ import '@dxos-theme';
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
8
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { type EchoSchema, isMutable } from '@dxos/echo-schema';
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { faker } from '@dxos/random';
|
|
13
13
|
import { Filter, useQuery, live } from '@dxos/react-client/echo';
|
|
14
14
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
15
|
-
import {
|
|
15
|
+
import { defaultRowSize, Grid, type GridEditing } from '@dxos/react-ui-grid';
|
|
16
16
|
import { ViewProjection, ViewType } from '@dxos/schema';
|
|
17
17
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
18
18
|
|
|
@@ -31,7 +31,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
31
31
|
const { space } = useClientProvider();
|
|
32
32
|
invariant(space);
|
|
33
33
|
|
|
34
|
-
const tables = useQuery(space, Filter.
|
|
34
|
+
const tables = useQuery(space, Filter.type(TableType));
|
|
35
35
|
const [table, setTable] = useState<TableType>();
|
|
36
36
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
37
37
|
useEffect(() => {
|
|
@@ -53,15 +53,16 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
53
53
|
() => ({
|
|
54
54
|
selection: { enabled: true, mode: 'multiple' },
|
|
55
55
|
dataEditable: true,
|
|
56
|
-
schemaEditable:
|
|
56
|
+
schemaEditable: schema && isMutable(schema),
|
|
57
57
|
}),
|
|
58
|
-
[],
|
|
58
|
+
[schema],
|
|
59
59
|
);
|
|
60
60
|
|
|
61
61
|
const model = useTableModel({ table, projection, features });
|
|
62
62
|
|
|
63
63
|
const handleQuery: TableCellEditorProps['onQuery'] = async ({ field }) => {
|
|
64
|
-
|
|
64
|
+
// TODO(dmaretskyi): If no schema query nothing
|
|
65
|
+
const { objects } = await space.db.query(schema ? Filter.type(schema) : Filter.everything()).run();
|
|
65
66
|
return objects.map((obj) => {
|
|
66
67
|
const label = obj[field.referencePath ?? 'id'];
|
|
67
68
|
return {
|
|
@@ -76,7 +77,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
return (
|
|
79
|
-
<div className='flex w-[300px] border border-separator' style={{ height:
|
|
80
|
+
<div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
|
|
80
81
|
<Grid.Root id='test' editing={editing}>
|
|
81
82
|
<TableCellEditor model={model} onQuery={handleQuery} />
|
|
82
83
|
</Grid.Root>
|
|
@@ -107,7 +108,7 @@ const meta: Meta<StoryProps> = {
|
|
|
107
108
|
},
|
|
108
109
|
}),
|
|
109
110
|
withTheme,
|
|
110
|
-
withLayout(
|
|
111
|
+
withLayout(),
|
|
111
112
|
],
|
|
112
113
|
};
|
|
113
114
|
|
|
@@ -120,6 +121,7 @@ export const Default: Story = {
|
|
|
120
121
|
editing: {
|
|
121
122
|
index: 'grid,0,3',
|
|
122
123
|
initialContent: 'Test',
|
|
124
|
+
cellElement: null,
|
|
123
125
|
},
|
|
124
126
|
},
|
|
125
127
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Completion } from '@codemirror/autocomplete';
|
|
6
|
+
import { type Schema } from 'effect/Schema';
|
|
6
7
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
7
8
|
|
|
8
|
-
import { FormatEnum } from '@dxos/echo-schema';
|
|
9
|
+
import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
|
|
9
10
|
import { invariant } from '@dxos/invariant';
|
|
10
11
|
import { type DxGrid } from '@dxos/lit-grid';
|
|
11
12
|
import { useThemeContext } from '@dxos/react-ui';
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
import { tagPickerExtension, createLinks } from '@dxos/react-ui-tag-picker';
|
|
27
28
|
import { type FieldProjection } from '@dxos/schema';
|
|
28
29
|
|
|
30
|
+
import { FormCellEditor } from './FormCellEditor';
|
|
29
31
|
import { completion } from './extension';
|
|
30
32
|
import { type TableModel, type ModalController } from '../../model';
|
|
31
33
|
|
|
@@ -43,11 +45,52 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
|
|
|
43
45
|
export type TableCellEditorProps = {
|
|
44
46
|
model?: TableModel;
|
|
45
47
|
modals?: ModalController;
|
|
48
|
+
schema?: Schema.AnyNoContext;
|
|
46
49
|
onEnter?: (cell: DxGridPlanePosition) => void;
|
|
47
50
|
onFocus?: DxGrid['refocus'];
|
|
48
51
|
onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
|
|
49
52
|
};
|
|
50
53
|
|
|
54
|
+
export const TableValueEditor = ({
|
|
55
|
+
model,
|
|
56
|
+
modals,
|
|
57
|
+
schema,
|
|
58
|
+
onEnter,
|
|
59
|
+
onFocus,
|
|
60
|
+
onQuery,
|
|
61
|
+
__gridScope,
|
|
62
|
+
}: GridScopedProps<TableCellEditorProps>) => {
|
|
63
|
+
const { editing } = useGridContext('TableValueEditor', __gridScope);
|
|
64
|
+
|
|
65
|
+
const fieldProjection = useMemo<FieldProjection | undefined>(() => {
|
|
66
|
+
if (!model || !editing) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { col } = parseCellIndex(editing.index);
|
|
71
|
+
const field = model.projection.view.fields[col];
|
|
72
|
+
const fieldProjection = model.projection.getFieldProjection(field.id);
|
|
73
|
+
invariant(fieldProjection);
|
|
74
|
+
return fieldProjection;
|
|
75
|
+
}, [model, editing]);
|
|
76
|
+
|
|
77
|
+
if (fieldProjection?.props.type === TypeEnum.Array) {
|
|
78
|
+
return <FormCellEditor fieldProjection={fieldProjection} model={model} schema={schema} __gridScope={__gridScope} />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// For all other types, use the existing cell editor
|
|
82
|
+
return (
|
|
83
|
+
<TableCellEditor
|
|
84
|
+
model={model}
|
|
85
|
+
modals={modals}
|
|
86
|
+
onEnter={onEnter}
|
|
87
|
+
onFocus={onFocus}
|
|
88
|
+
onQuery={onQuery}
|
|
89
|
+
__gridScope={__gridScope}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
51
94
|
export const TableCellEditor = ({
|
|
52
95
|
model,
|
|
53
96
|
modals,
|
|
@@ -261,11 +304,9 @@ export const TableCellEditor = ({
|
|
|
261
304
|
}
|
|
262
305
|
}
|
|
263
306
|
|
|
264
|
-
// Return empty string if no value
|
|
265
307
|
return '';
|
|
266
308
|
}
|
|
267
309
|
|
|
268
|
-
// Default behavior for other formats
|
|
269
310
|
const value = model.getCellData(cell);
|
|
270
311
|
return value !== undefined ? String(value) : '';
|
|
271
312
|
}
|