@dxos/react-ui-table 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/chunk-5XWSB3JP.mjs +71 -0
- package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +7 -0
- package/dist/lib/browser/{chunk-RQYXNWWN.mjs → chunk-KNC7XYT6.mjs} +529 -568
- package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +187 -322
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +15 -25
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +3 -3
- package/dist/lib/node-esm/{chunk-QNDUGIS7.mjs → chunk-CWSGS3Y2.mjs} +529 -568
- package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MKTMOY46.mjs +72 -0
- package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +187 -322
- 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 +15 -25
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- package/dist/types/src/components/Table/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 +5 -5
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +73 -9
- package/dist/types/src/components/Table/Relations.stories.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 +5 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +72 -10
- 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 +3 -4
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -5
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +4 -5
- 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 +8 -10
- 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/model/table-sorting.d.ts +8 -5
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +1 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +21 -29
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/types/Table.d.ts +28 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +9 -9
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/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 +53 -52
- package/src/components/Table/DynamicTable.stories.tsx +14 -13
- package/src/components/Table/DynamicTable.tsx +15 -10
- package/src/components/Table/Relations.stories.tsx +133 -29
- package/src/components/Table/RowActionsMenu.tsx +21 -13
- package/src/components/Table/Table.stories.tsx +85 -79
- package/src/components/Table/Table.tsx +13 -9
- package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +21 -47
- package/src/components/TableCellEditor/TableCellEditor.tsx +20 -115
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +32 -22
- package/src/components/testing.ts +4 -4
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +1 -1
- package/src/hooks/useProjectionModel.ts +19 -0
- package/src/hooks/useTableModel.ts +11 -13
- package/src/model/modal-controller.ts +0 -28
- package/src/model/table-model.test.ts +19 -9
- package/src/model/table-model.ts +42 -41
- package/src/model/table-presentation.test.ts +22 -20
- package/src/model/table-presentation.ts +12 -13
- package/src/model/table-sorting.ts +31 -13
- package/src/playwright/TableManager.ts +3 -3
- package/src/playwright/smoke.spec.ts +8 -6
- package/src/testing/useTestTableModel.ts +33 -22
- package/src/translations.ts +2 -2
- package/src/types/Table.ts +89 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.ts +17 -21
- package/src/util/schema.ts +4 -4
- package/dist/lib/browser/chunk-63UUF4IG.mjs +0 -66
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
- package/dist/lib/browser/chunk-RQYXNWWN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-K734DJB7.mjs +0 -67
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-QNDUGIS7.mjs.map +0 -7
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -103
- package/src/types/schema.ts +0 -73
|
@@ -2,31 +2,22 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
9
7
|
import React, { useCallback } from 'react';
|
|
10
8
|
|
|
11
|
-
import { Obj, Type } from '@dxos/echo';
|
|
12
|
-
import {
|
|
13
|
-
EchoObject,
|
|
14
|
-
FormatAnnotation,
|
|
15
|
-
FormatEnum,
|
|
16
|
-
GeneratorAnnotation,
|
|
17
|
-
LabelAnnotation,
|
|
18
|
-
PropertyMetaAnnotationId,
|
|
19
|
-
} from '@dxos/echo-schema';
|
|
9
|
+
import { Annotation, Format, Obj, type QueryAST, Type } from '@dxos/echo';
|
|
10
|
+
import { PropertyMetaAnnotationId } from '@dxos/echo/internal';
|
|
20
11
|
import { invariant } from '@dxos/invariant';
|
|
21
12
|
import { faker } from '@dxos/random';
|
|
22
13
|
import { PublicKey } from '@dxos/react-client';
|
|
23
|
-
import { type Space
|
|
14
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
24
15
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
25
|
-
import {
|
|
16
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
17
|
+
import { ViewEditor, translations as formTranslations } from '@dxos/react-ui-form';
|
|
26
18
|
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
19
|
+
import { View, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
|
|
20
|
+
import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
|
|
30
21
|
|
|
31
22
|
import { useTestTableModel } from '../../testing';
|
|
32
23
|
import { translations } from '../../translations';
|
|
@@ -35,13 +26,13 @@ import { TableToolbar } from '../TableToolbar';
|
|
|
35
26
|
|
|
36
27
|
import { Table as TableComponent } from './Table';
|
|
37
28
|
|
|
38
|
-
const
|
|
29
|
+
const Example = Schema.Struct({
|
|
39
30
|
// TODO(wittjosiah): Should be title. Currently name to work with default label.
|
|
40
31
|
name: Schema.optional(Schema.String).annotations({ title: 'Title' }),
|
|
41
32
|
urgent: Schema.optional(Schema.Boolean).annotations({ title: 'Urgent' }),
|
|
42
33
|
status: Schema.optional(
|
|
43
34
|
Schema.Literal('todo', 'in-progress', 'done')
|
|
44
|
-
.pipe(FormatAnnotation.set(
|
|
35
|
+
.pipe(Format.FormatAnnotation.set(Format.TypeFormat.SingleSelect))
|
|
45
36
|
.annotations({
|
|
46
37
|
title: 'Status',
|
|
47
38
|
[PropertyMetaAnnotationId]: {
|
|
@@ -56,14 +47,14 @@ const TestSchema = Schema.Struct({
|
|
|
56
47
|
}),
|
|
57
48
|
),
|
|
58
49
|
description: Schema.optional(Schema.String).annotations({ title: 'Description' }),
|
|
59
|
-
parent: Schema.optional(Schema.suspend((): Type.Ref<
|
|
50
|
+
parent: Schema.optional(Schema.suspend((): Type.Ref<Example> => Type.Ref(Example))).annotations({
|
|
60
51
|
title: 'Parent',
|
|
61
52
|
}),
|
|
62
53
|
}).pipe(
|
|
63
54
|
Type.Obj({ typename: `example.com/type/${PublicKey.random().truncate()}`, version: '0.1.0' }),
|
|
64
|
-
LabelAnnotation.set(['name']),
|
|
55
|
+
Annotation.LabelAnnotation.set(['name']),
|
|
65
56
|
);
|
|
66
|
-
interface
|
|
57
|
+
interface Example extends Schema.Schema.Type<typeof Example> {}
|
|
67
58
|
|
|
68
59
|
const StoryViewEditor = ({
|
|
69
60
|
view,
|
|
@@ -71,18 +62,18 @@ const StoryViewEditor = ({
|
|
|
71
62
|
space,
|
|
72
63
|
handleDeleteColumn,
|
|
73
64
|
}: {
|
|
74
|
-
view?:
|
|
65
|
+
view?: View.View;
|
|
75
66
|
schema?: Schema.Schema.AnyNoContext;
|
|
76
67
|
space?: Space;
|
|
77
68
|
handleDeleteColumn: (fieldId: string) => void;
|
|
78
69
|
}) => {
|
|
79
|
-
const
|
|
80
|
-
(
|
|
70
|
+
const handleQueryChanged = useCallback(
|
|
71
|
+
(newQuery: QueryAST.Query) => {
|
|
81
72
|
invariant(schema);
|
|
82
73
|
invariant(Type.isMutable(schema));
|
|
83
|
-
schema.updateTypename(
|
|
74
|
+
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
84
75
|
invariant(view);
|
|
85
|
-
view.query.
|
|
76
|
+
view.query.ast = newQuery;
|
|
86
77
|
},
|
|
87
78
|
[schema, view],
|
|
88
79
|
);
|
|
@@ -96,7 +87,7 @@ const StoryViewEditor = ({
|
|
|
96
87
|
registry={space?.db.schemaRegistry}
|
|
97
88
|
schema={schema}
|
|
98
89
|
view={view}
|
|
99
|
-
|
|
90
|
+
onQueryChanged={handleQueryChanged}
|
|
100
91
|
onDelete={handleDeleteColumn}
|
|
101
92
|
/>
|
|
102
93
|
);
|
|
@@ -110,7 +101,7 @@ const DefaultStory = () => {
|
|
|
110
101
|
const {
|
|
111
102
|
space,
|
|
112
103
|
schema,
|
|
113
|
-
|
|
104
|
+
table,
|
|
114
105
|
tableRef,
|
|
115
106
|
model,
|
|
116
107
|
presentation,
|
|
@@ -120,7 +111,7 @@ const DefaultStory = () => {
|
|
|
120
111
|
handleDeleteColumn,
|
|
121
112
|
} = useTestTableModel();
|
|
122
113
|
|
|
123
|
-
if (!schema || !view) {
|
|
114
|
+
if (!schema || !table?.view.target) {
|
|
124
115
|
return <div />;
|
|
125
116
|
}
|
|
126
117
|
|
|
@@ -139,10 +130,15 @@ const DefaultStory = () => {
|
|
|
139
130
|
/>
|
|
140
131
|
</TableComponent.Root>
|
|
141
132
|
</div>
|
|
142
|
-
<div className='flex flex-col
|
|
143
|
-
<StoryViewEditor
|
|
144
|
-
|
|
145
|
-
{
|
|
133
|
+
<div className='flex flex-col bs-full border-l border-separator overflow-y-auto'>
|
|
134
|
+
<StoryViewEditor
|
|
135
|
+
view={table.view.target}
|
|
136
|
+
schema={schema}
|
|
137
|
+
space={space}
|
|
138
|
+
handleDeleteColumn={handleDeleteColumn}
|
|
139
|
+
/>
|
|
140
|
+
<SyntaxHighlighter language='json' className='text-xs'>
|
|
141
|
+
{JSON.stringify({ view: table.view.target, schema }, null, 2)}
|
|
146
142
|
</SyntaxHighlighter>
|
|
147
143
|
</div>
|
|
148
144
|
</div>
|
|
@@ -160,29 +156,22 @@ type StoryProps = { rows?: number };
|
|
|
160
156
|
const meta = {
|
|
161
157
|
title: 'ui/react-ui-table/Table',
|
|
162
158
|
render: DefaultStory,
|
|
163
|
-
parameters: {
|
|
164
|
-
translations,
|
|
165
|
-
layout: 'fullscreen',
|
|
166
|
-
controls: {
|
|
167
|
-
disable: true,
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
159
|
decorators: [
|
|
171
160
|
withTheme,
|
|
172
|
-
withLayout({ fullscreen: true }),
|
|
173
161
|
withClientProvider({
|
|
174
|
-
types: [
|
|
162
|
+
types: [View.View, Table.Table],
|
|
175
163
|
createIdentity: true,
|
|
176
164
|
createSpace: true,
|
|
177
|
-
|
|
178
|
-
const [schema] = await space.db.schemaRegistry.register([
|
|
179
|
-
const { view } = await
|
|
165
|
+
onCreateSpace: async ({ client, space }) => {
|
|
166
|
+
const [schema] = await space.db.schemaRegistry.register([Example]);
|
|
167
|
+
const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: schema.typename });
|
|
168
|
+
const table = Table.make({ view, jsonSchema });
|
|
180
169
|
view.projection.fields = [
|
|
181
|
-
view.projection.fields.find((field) => field.path === 'name')!,
|
|
182
|
-
...view.projection.fields.filter((field) => field.path !== 'name'),
|
|
170
|
+
view.projection.fields.find((field: any) => field.path === 'name')!,
|
|
171
|
+
...view.projection.fields.filter((field: any) => field.path !== 'name'),
|
|
183
172
|
];
|
|
184
173
|
|
|
185
|
-
space.db.add(
|
|
174
|
+
space.db.add(table);
|
|
186
175
|
|
|
187
176
|
Array.from({ length: 10 }).map(() => {
|
|
188
177
|
return space.db.add(
|
|
@@ -196,6 +185,13 @@ const meta = {
|
|
|
196
185
|
},
|
|
197
186
|
}),
|
|
198
187
|
],
|
|
188
|
+
parameters: {
|
|
189
|
+
layout: 'fullscreen',
|
|
190
|
+
controls: {
|
|
191
|
+
disable: true,
|
|
192
|
+
},
|
|
193
|
+
translations: [...translations, ...formTranslations],
|
|
194
|
+
},
|
|
199
195
|
} satisfies Meta<typeof TableComponent>;
|
|
200
196
|
|
|
201
197
|
export default meta;
|
|
@@ -206,30 +202,32 @@ export const Default: Story = {};
|
|
|
206
202
|
|
|
207
203
|
export const StaticSchema: StoryObj = {
|
|
208
204
|
render: DefaultStory,
|
|
209
|
-
parameters: { translations },
|
|
210
205
|
decorators: [
|
|
211
206
|
withClientProvider({
|
|
212
|
-
types: [
|
|
207
|
+
types: [View.View, Table.Table],
|
|
213
208
|
createIdentity: true,
|
|
214
209
|
createSpace: true,
|
|
215
|
-
|
|
216
|
-
const { view } = await
|
|
217
|
-
|
|
210
|
+
onCreateSpace: async ({ client, space }) => {
|
|
211
|
+
const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: TestSchema.Person.typename });
|
|
212
|
+
const table = Table.make({ view, jsonSchema });
|
|
213
|
+
space.db.add(table);
|
|
218
214
|
|
|
219
215
|
const factory = createObjectFactory(space.db, faker as any);
|
|
220
216
|
await factory([
|
|
221
|
-
{ type:
|
|
222
|
-
// { type:
|
|
217
|
+
{ type: TestSchema.Person, count: 10 },
|
|
218
|
+
// { type: TestSchema.Organization, count: 1 },
|
|
223
219
|
]);
|
|
224
220
|
},
|
|
225
221
|
}),
|
|
226
|
-
withLayout({ fullscreen: true }),
|
|
227
|
-
withTheme,
|
|
228
222
|
],
|
|
223
|
+
parameters: {
|
|
224
|
+
layout: 'fullscreen',
|
|
225
|
+
translations: [...translations, ...formTranslations],
|
|
226
|
+
},
|
|
229
227
|
};
|
|
230
228
|
|
|
231
229
|
const ContactWithArrayOfEmails = Schema.Struct({
|
|
232
|
-
name: Schema.String.pipe(GeneratorAnnotation.set('person.fullName')),
|
|
230
|
+
name: Schema.String.pipe(Annotation.GeneratorAnnotation.set('person.fullName')),
|
|
233
231
|
emails: Schema.optional(
|
|
234
232
|
Schema.Array(
|
|
235
233
|
Schema.Struct({
|
|
@@ -239,7 +237,7 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
239
237
|
),
|
|
240
238
|
),
|
|
241
239
|
}).pipe(
|
|
242
|
-
|
|
240
|
+
Type.Obj({
|
|
243
241
|
typename: 'dxos.org/type/ContactWithArrayOfEmails',
|
|
244
242
|
version: '0.1.0',
|
|
245
243
|
}),
|
|
@@ -247,39 +245,44 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
247
245
|
|
|
248
246
|
export const ArrayOfObjects: StoryObj = {
|
|
249
247
|
render: DefaultStory,
|
|
250
|
-
parameters: { translations },
|
|
251
248
|
decorators: [
|
|
252
249
|
withClientProvider({
|
|
253
|
-
types: [
|
|
250
|
+
types: [View.View, Table.Table, TestSchema.Person, TestSchema.Organization, ContactWithArrayOfEmails],
|
|
254
251
|
createIdentity: true,
|
|
255
252
|
createSpace: true,
|
|
256
|
-
|
|
257
|
-
const { view } = await
|
|
258
|
-
|
|
253
|
+
onCreateSpace: async ({ client, space }) => {
|
|
254
|
+
const { view, jsonSchema } = await View.makeFromSpace({
|
|
255
|
+
client,
|
|
256
|
+
space,
|
|
257
|
+
typename: ContactWithArrayOfEmails.typename,
|
|
258
|
+
});
|
|
259
|
+
const table = Table.make({ view, jsonSchema });
|
|
260
|
+
space.db.add(table);
|
|
259
261
|
|
|
260
262
|
const factory = createObjectFactory(space.db, faker as any);
|
|
261
263
|
await factory([
|
|
262
|
-
// { type:
|
|
263
|
-
// { type:
|
|
264
|
+
// { type: TestSchema.Person, count: 10 },
|
|
265
|
+
// { type: TestSchema.Organization, count: 1 },
|
|
264
266
|
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
265
267
|
]);
|
|
266
268
|
},
|
|
267
269
|
}),
|
|
268
|
-
withLayout({ fullscreen: true }),
|
|
269
|
-
withTheme,
|
|
270
270
|
],
|
|
271
|
+
parameters: {
|
|
272
|
+
layout: 'fullscreen',
|
|
273
|
+
translations,
|
|
274
|
+
},
|
|
271
275
|
};
|
|
272
276
|
|
|
273
277
|
export const Tags: Meta<StoryProps> = {
|
|
274
278
|
title: 'ui/react-ui-table/Table',
|
|
275
279
|
render: DefaultStory,
|
|
276
|
-
parameters: { translations },
|
|
277
280
|
decorators: [
|
|
278
281
|
withClientProvider({
|
|
279
|
-
types: [
|
|
282
|
+
types: [View.View, Table.Table],
|
|
280
283
|
createIdentity: true,
|
|
281
284
|
createSpace: true,
|
|
282
|
-
|
|
285
|
+
onCreateSpace: async ({ client, space }) => {
|
|
283
286
|
// Configure schema.
|
|
284
287
|
const typename = 'example.com/SingleSelect';
|
|
285
288
|
const selectOptions = [
|
|
@@ -295,25 +298,26 @@ export const Tags: Meta<StoryProps> = {
|
|
|
295
298
|
const schema = getSchemaFromPropertyDefinitions(typename, [
|
|
296
299
|
{
|
|
297
300
|
name: 'single',
|
|
298
|
-
format:
|
|
301
|
+
format: Format.TypeFormat.SingleSelect,
|
|
299
302
|
config: { options: selectOptions },
|
|
300
303
|
},
|
|
301
304
|
{
|
|
302
305
|
name: 'multiple',
|
|
303
|
-
format:
|
|
306
|
+
format: Format.TypeFormat.MultiSelect,
|
|
304
307
|
config: { options: selectOptions },
|
|
305
308
|
},
|
|
306
309
|
]);
|
|
307
310
|
const [storedSchema] = await space.db.schemaRegistry.register([schema]);
|
|
308
311
|
|
|
309
312
|
// Initialize table.
|
|
310
|
-
const { view } = await
|
|
311
|
-
|
|
313
|
+
const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename });
|
|
314
|
+
const table = Table.make({ view, jsonSchema });
|
|
315
|
+
space.db.add(table);
|
|
312
316
|
|
|
313
317
|
// Populate.
|
|
314
318
|
Array.from({ length: 10 }).map(() => {
|
|
315
319
|
return space.db.add(
|
|
316
|
-
|
|
320
|
+
Obj.make(storedSchema, {
|
|
317
321
|
single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
|
|
318
322
|
multiple: faker.helpers.randomSubset(selectOptionIds),
|
|
319
323
|
}),
|
|
@@ -321,7 +325,9 @@ export const Tags: Meta<StoryProps> = {
|
|
|
321
325
|
});
|
|
322
326
|
},
|
|
323
327
|
}),
|
|
324
|
-
withLayout({ fullscreen: true }),
|
|
325
|
-
withTheme,
|
|
326
328
|
],
|
|
329
|
+
parameters: {
|
|
330
|
+
layout: 'fullscreen',
|
|
331
|
+
translations,
|
|
332
|
+
},
|
|
327
333
|
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import type * as Schema from 'effect/Schema';
|
|
6
|
+
import * as String from 'effect/String';
|
|
6
7
|
import React, {
|
|
7
8
|
type MouseEvent,
|
|
8
9
|
type PropsWithChildren,
|
|
@@ -15,8 +16,8 @@ import React, {
|
|
|
15
16
|
useState,
|
|
16
17
|
} from 'react';
|
|
17
18
|
|
|
18
|
-
import { type Client } from '@dxos/react-client';
|
|
19
19
|
// TODO(wittjosiah): Remove dependency on react-client.
|
|
20
|
+
import { type Client } from '@dxos/react-client';
|
|
20
21
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
21
22
|
import {
|
|
22
23
|
type DxGridElement,
|
|
@@ -34,11 +35,10 @@ import { mx } from '@dxos/react-ui-theme';
|
|
|
34
35
|
|
|
35
36
|
import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
36
37
|
import { tableButtons, tableControls } from '../../util';
|
|
37
|
-
import { type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
|
|
38
|
+
import { type OnCreateHandler, type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
|
|
38
39
|
|
|
39
40
|
import { ColumnActionsMenu } from './ColumnActionsMenu';
|
|
40
41
|
import { ColumnSettings } from './ColumnSettings';
|
|
41
|
-
import { CreateRefPanel } from './CreateRefPanel';
|
|
42
42
|
import { RowActionsMenu } from './RowActionsMenu';
|
|
43
43
|
|
|
44
44
|
const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
|
|
@@ -80,15 +80,17 @@ export type TableController = {
|
|
|
80
80
|
export type TableMainProps = {
|
|
81
81
|
model?: TableModel;
|
|
82
82
|
presentation?: TablePresentation;
|
|
83
|
-
schema?: Schema.AnyNoContext;
|
|
83
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
84
84
|
client?: Client;
|
|
85
85
|
// TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
|
|
86
86
|
ignoreAttention?: boolean;
|
|
87
|
+
onCreate?: OnCreateHandler;
|
|
87
88
|
onRowClick?: (row: any) => void;
|
|
89
|
+
testId?: string;
|
|
88
90
|
};
|
|
89
91
|
|
|
90
92
|
const TableMain = forwardRef<TableController, TableMainProps>(
|
|
91
|
-
({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
|
|
93
|
+
({ model, presentation, ignoreAttention, schema, client, onCreate, onRowClick, testId }, forwardedRef) => {
|
|
92
94
|
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
93
95
|
const { hasAttention } = useAttention(model?.id ?? 'table');
|
|
94
96
|
const modals = useMemo(() => new ModalController(), []);
|
|
@@ -275,7 +277,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
275
277
|
const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
|
|
276
278
|
(increment, delta, cell) => {
|
|
277
279
|
if (dxGrid && model) {
|
|
278
|
-
if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1) {
|
|
280
|
+
if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1 && increment !== 'col') {
|
|
279
281
|
handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
|
|
280
282
|
} else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
|
|
281
283
|
handleSaveDraftRow(cell.row);
|
|
@@ -320,7 +322,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
320
322
|
void navigator.clipboard.readText().then((clipboardText) => {
|
|
321
323
|
try {
|
|
322
324
|
// Attempt to set the cell's content to clipboard content
|
|
323
|
-
model.setCellData(cell, clipboardText);
|
|
325
|
+
model.setCellData(cell, String.trim(clipboardText).replace(/[\n\r]+/, ' '));
|
|
324
326
|
handleSave();
|
|
325
327
|
} catch {
|
|
326
328
|
// If validation fails, emit a DxEditRequest event with initialContent from clipboard
|
|
@@ -356,6 +358,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
356
358
|
try {
|
|
357
359
|
model.setCellData(cell, undefined);
|
|
358
360
|
event.preventDefault();
|
|
361
|
+
handleSave();
|
|
359
362
|
} catch {
|
|
360
363
|
// Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
|
|
361
364
|
}
|
|
@@ -404,6 +407,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
404
407
|
schema={schema}
|
|
405
408
|
onFocus={handleFocus}
|
|
406
409
|
onSave={handleSave}
|
|
410
|
+
onCreate={onCreate}
|
|
407
411
|
client={client}
|
|
408
412
|
/>
|
|
409
413
|
<Grid.Content
|
|
@@ -419,12 +423,12 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
419
423
|
onClick={handleGridClick}
|
|
420
424
|
onKeyDownCapture={handleKeyDown}
|
|
421
425
|
onWheelCapture={handleWheel}
|
|
426
|
+
{...(testId && { 'data-testid': testId })}
|
|
422
427
|
ref={setDxGrid}
|
|
423
428
|
/>
|
|
424
429
|
<RowActionsMenu model={model} modals={modals} />
|
|
425
430
|
<ColumnActionsMenu model={model} modals={modals} />
|
|
426
431
|
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
427
|
-
<CreateRefPanel model={model} modals={modals} />
|
|
428
432
|
</Grid.Root>
|
|
429
433
|
);
|
|
430
434
|
},
|
|
@@ -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 { Format, 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 refSchema = useMemo(() => {
|
|
154
|
+
if (fieldProjection.props.format === Format.TypeFormat.Ref && fieldProjection.props.referenceSchema) {
|
|
155
|
+
const { schema: refSchema } = getSchema({
|
|
156
|
+
typename: fieldProjection.props.referenceSchema,
|
|
157
|
+
});
|
|
158
|
+
return refSchema;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
|
|
162
|
+
|
|
163
|
+
const createSchema = useMemo(() => {
|
|
164
|
+
return refSchema ? Schema.omit<any, any, ['id']>('id')(refSchema) : null;
|
|
165
|
+
}, [refSchema]);
|
|
166
|
+
|
|
167
|
+
const handleCreate = useCallback(
|
|
168
|
+
(values: any) => {
|
|
169
|
+
if (refSchema && onCreate) {
|
|
170
|
+
const objectWithId = onCreate(refSchema, 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, refSchema, 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>
|