@dxos/react-ui-table 0.8.4-main.5ad4a44 → 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-OZTXLZGM.mjs → chunk-KNC7XYT6.mjs} +521 -542
- package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +75 -59
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +14 -27
- 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-S56Z4V4X.mjs → chunk-CWSGS3Y2.mjs} +521 -542
- 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 +75 -59
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +14 -27
- 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.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +7 -7
- 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.stories.d.ts +67 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +2 -2
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.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 +0 -2
- 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/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/table-model.d.ts +7 -9
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +8 -4
- 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 -15
- 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 +7 -6
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +41 -40
- package/src/components/Table/DynamicTable.stories.tsx +7 -7
- package/src/components/Table/DynamicTable.tsx +12 -4
- package/src/components/Table/Relations.stories.tsx +24 -35
- package/src/components/Table/RowActionsMenu.tsx +21 -13
- package/src/components/Table/Table.stories.tsx +58 -53
- package/src/components/TableCellEditor/FormCellEditor.tsx +11 -11
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +11 -9
- package/src/components/TableCellEditor/TableCellEditor.tsx +4 -4
- package/src/components/TableToolbar/TableToolbar.tsx +32 -22
- package/src/components/testing.ts +3 -3
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useProjectionModel.ts +19 -0
- package/src/hooks/useTableModel.ts +11 -13
- package/src/model/table-model.test.ts +5 -5
- package/src/model/table-model.ts +34 -40
- package/src/model/table-presentation.test.ts +5 -5
- package/src/model/table-presentation.ts +9 -9
- package/src/model/table-sorting.ts +28 -19
- package/src/playwright/TableManager.ts +3 -3
- package/src/playwright/smoke.spec.ts +8 -6
- package/src/testing/useTestTableModel.ts +32 -24
- 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 +12 -16
- package/dist/lib/browser/chunk-4K7JPALT.mjs +0 -66
- package/dist/lib/browser/chunk-4K7JPALT.mjs.map +0 -7
- package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-MK6OVUF6.mjs +0 -67
- package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-S56Z4V4X.mjs.map +0 -7
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/types/schema.ts +0 -73
|
@@ -6,26 +6,18 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import React, { useCallback } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
EchoObject,
|
|
12
|
-
FormatAnnotation,
|
|
13
|
-
FormatEnum,
|
|
14
|
-
GeneratorAnnotation,
|
|
15
|
-
LabelAnnotation,
|
|
16
|
-
PropertyMetaAnnotationId,
|
|
17
|
-
} from '@dxos/echo/internal';
|
|
18
|
-
import { live } from '@dxos/echo/internal';
|
|
9
|
+
import { Annotation, Format, Obj, type QueryAST, Type } from '@dxos/echo';
|
|
10
|
+
import { PropertyMetaAnnotationId } from '@dxos/echo/internal';
|
|
19
11
|
import { invariant } from '@dxos/invariant';
|
|
20
12
|
import { faker } from '@dxos/random';
|
|
21
13
|
import { PublicKey } from '@dxos/react-client';
|
|
22
14
|
import { type Space } from '@dxos/react-client/echo';
|
|
23
15
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
24
16
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
25
|
-
import { ViewEditor } from '@dxos/react-ui-form';
|
|
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 {
|
|
19
|
+
import { View, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
|
|
20
|
+
import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
|
|
29
21
|
|
|
30
22
|
import { useTestTableModel } from '../../testing';
|
|
31
23
|
import { translations } from '../../translations';
|
|
@@ -34,13 +26,13 @@ import { TableToolbar } from '../TableToolbar';
|
|
|
34
26
|
|
|
35
27
|
import { Table as TableComponent } from './Table';
|
|
36
28
|
|
|
37
|
-
const
|
|
29
|
+
const Example = Schema.Struct({
|
|
38
30
|
// TODO(wittjosiah): Should be title. Currently name to work with default label.
|
|
39
31
|
name: Schema.optional(Schema.String).annotations({ title: 'Title' }),
|
|
40
32
|
urgent: Schema.optional(Schema.Boolean).annotations({ title: 'Urgent' }),
|
|
41
33
|
status: Schema.optional(
|
|
42
34
|
Schema.Literal('todo', 'in-progress', 'done')
|
|
43
|
-
.pipe(FormatAnnotation.set(
|
|
35
|
+
.pipe(Format.FormatAnnotation.set(Format.TypeFormat.SingleSelect))
|
|
44
36
|
.annotations({
|
|
45
37
|
title: 'Status',
|
|
46
38
|
[PropertyMetaAnnotationId]: {
|
|
@@ -55,14 +47,14 @@ const TestSchema = Schema.Struct({
|
|
|
55
47
|
}),
|
|
56
48
|
),
|
|
57
49
|
description: Schema.optional(Schema.String).annotations({ title: 'Description' }),
|
|
58
|
-
parent: Schema.optional(Schema.suspend((): Type.Ref<
|
|
50
|
+
parent: Schema.optional(Schema.suspend((): Type.Ref<Example> => Type.Ref(Example))).annotations({
|
|
59
51
|
title: 'Parent',
|
|
60
52
|
}),
|
|
61
53
|
}).pipe(
|
|
62
54
|
Type.Obj({ typename: `example.com/type/${PublicKey.random().truncate()}`, version: '0.1.0' }),
|
|
63
|
-
LabelAnnotation.set(['name']),
|
|
55
|
+
Annotation.LabelAnnotation.set(['name']),
|
|
64
56
|
);
|
|
65
|
-
interface
|
|
57
|
+
interface Example extends Schema.Schema.Type<typeof Example> {}
|
|
66
58
|
|
|
67
59
|
const StoryViewEditor = ({
|
|
68
60
|
view,
|
|
@@ -70,18 +62,18 @@ const StoryViewEditor = ({
|
|
|
70
62
|
space,
|
|
71
63
|
handleDeleteColumn,
|
|
72
64
|
}: {
|
|
73
|
-
view?:
|
|
65
|
+
view?: View.View;
|
|
74
66
|
schema?: Schema.Schema.AnyNoContext;
|
|
75
67
|
space?: Space;
|
|
76
68
|
handleDeleteColumn: (fieldId: string) => void;
|
|
77
69
|
}) => {
|
|
78
70
|
const handleQueryChanged = useCallback(
|
|
79
|
-
(
|
|
71
|
+
(newQuery: QueryAST.Query) => {
|
|
80
72
|
invariant(schema);
|
|
81
73
|
invariant(Type.isMutable(schema));
|
|
82
|
-
schema.updateTypename(
|
|
74
|
+
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
83
75
|
invariant(view);
|
|
84
|
-
view.query.ast =
|
|
76
|
+
view.query.ast = newQuery;
|
|
85
77
|
},
|
|
86
78
|
[schema, view],
|
|
87
79
|
);
|
|
@@ -109,7 +101,7 @@ const DefaultStory = () => {
|
|
|
109
101
|
const {
|
|
110
102
|
space,
|
|
111
103
|
schema,
|
|
112
|
-
|
|
104
|
+
table,
|
|
113
105
|
tableRef,
|
|
114
106
|
model,
|
|
115
107
|
presentation,
|
|
@@ -119,7 +111,7 @@ const DefaultStory = () => {
|
|
|
119
111
|
handleDeleteColumn,
|
|
120
112
|
} = useTestTableModel();
|
|
121
113
|
|
|
122
|
-
if (!schema || !view) {
|
|
114
|
+
if (!schema || !table?.view.target) {
|
|
123
115
|
return <div />;
|
|
124
116
|
}
|
|
125
117
|
|
|
@@ -138,10 +130,15 @@ const DefaultStory = () => {
|
|
|
138
130
|
/>
|
|
139
131
|
</TableComponent.Root>
|
|
140
132
|
</div>
|
|
141
|
-
<div className='flex flex-col
|
|
142
|
-
<StoryViewEditor
|
|
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
|
+
/>
|
|
143
140
|
<SyntaxHighlighter language='json' className='text-xs'>
|
|
144
|
-
{JSON.stringify({ view, schema }, null, 2)}
|
|
141
|
+
{JSON.stringify({ view: table.view.target, schema }, null, 2)}
|
|
145
142
|
</SyntaxHighlighter>
|
|
146
143
|
</div>
|
|
147
144
|
</div>
|
|
@@ -162,18 +159,19 @@ const meta = {
|
|
|
162
159
|
decorators: [
|
|
163
160
|
withTheme,
|
|
164
161
|
withClientProvider({
|
|
165
|
-
types: [
|
|
162
|
+
types: [View.View, Table.Table],
|
|
166
163
|
createIdentity: true,
|
|
167
164
|
createSpace: true,
|
|
168
165
|
onCreateSpace: async ({ client, space }) => {
|
|
169
|
-
const [schema] = await space.db.schemaRegistry.register([
|
|
170
|
-
const { view } = await
|
|
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 });
|
|
171
169
|
view.projection.fields = [
|
|
172
|
-
view.projection.fields.find((field) => field.path === 'name')!,
|
|
173
|
-
...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'),
|
|
174
172
|
];
|
|
175
173
|
|
|
176
|
-
space.db.add(
|
|
174
|
+
space.db.add(table);
|
|
177
175
|
|
|
178
176
|
Array.from({ length: 10 }).map(() => {
|
|
179
177
|
return space.db.add(
|
|
@@ -192,7 +190,7 @@ const meta = {
|
|
|
192
190
|
controls: {
|
|
193
191
|
disable: true,
|
|
194
192
|
},
|
|
195
|
-
translations,
|
|
193
|
+
translations: [...translations, ...formTranslations],
|
|
196
194
|
},
|
|
197
195
|
} satisfies Meta<typeof TableComponent>;
|
|
198
196
|
|
|
@@ -206,29 +204,30 @@ export const StaticSchema: StoryObj = {
|
|
|
206
204
|
render: DefaultStory,
|
|
207
205
|
decorators: [
|
|
208
206
|
withClientProvider({
|
|
209
|
-
types: [
|
|
207
|
+
types: [View.View, Table.Table],
|
|
210
208
|
createIdentity: true,
|
|
211
209
|
createSpace: true,
|
|
212
210
|
onCreateSpace: async ({ client, space }) => {
|
|
213
|
-
const { view } = await
|
|
214
|
-
|
|
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);
|
|
215
214
|
|
|
216
215
|
const factory = createObjectFactory(space.db, faker as any);
|
|
217
216
|
await factory([
|
|
218
|
-
{ type:
|
|
219
|
-
// { type:
|
|
217
|
+
{ type: TestSchema.Person, count: 10 },
|
|
218
|
+
// { type: TestSchema.Organization, count: 1 },
|
|
220
219
|
]);
|
|
221
220
|
},
|
|
222
221
|
}),
|
|
223
222
|
],
|
|
224
223
|
parameters: {
|
|
225
224
|
layout: 'fullscreen',
|
|
226
|
-
translations,
|
|
225
|
+
translations: [...translations, ...formTranslations],
|
|
227
226
|
},
|
|
228
227
|
};
|
|
229
228
|
|
|
230
229
|
const ContactWithArrayOfEmails = Schema.Struct({
|
|
231
|
-
name: Schema.String.pipe(GeneratorAnnotation.set('person.fullName')),
|
|
230
|
+
name: Schema.String.pipe(Annotation.GeneratorAnnotation.set('person.fullName')),
|
|
232
231
|
emails: Schema.optional(
|
|
233
232
|
Schema.Array(
|
|
234
233
|
Schema.Struct({
|
|
@@ -238,7 +237,7 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
238
237
|
),
|
|
239
238
|
),
|
|
240
239
|
}).pipe(
|
|
241
|
-
|
|
240
|
+
Type.Obj({
|
|
242
241
|
typename: 'dxos.org/type/ContactWithArrayOfEmails',
|
|
243
242
|
version: '0.1.0',
|
|
244
243
|
}),
|
|
@@ -248,17 +247,22 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
248
247
|
render: DefaultStory,
|
|
249
248
|
decorators: [
|
|
250
249
|
withClientProvider({
|
|
251
|
-
types: [
|
|
250
|
+
types: [View.View, Table.Table, TestSchema.Person, TestSchema.Organization, ContactWithArrayOfEmails],
|
|
252
251
|
createIdentity: true,
|
|
253
252
|
createSpace: true,
|
|
254
253
|
onCreateSpace: async ({ client, space }) => {
|
|
255
|
-
const { view } = await
|
|
256
|
-
|
|
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);
|
|
257
261
|
|
|
258
262
|
const factory = createObjectFactory(space.db, faker as any);
|
|
259
263
|
await factory([
|
|
260
|
-
// { type:
|
|
261
|
-
// { type:
|
|
264
|
+
// { type: TestSchema.Person, count: 10 },
|
|
265
|
+
// { type: TestSchema.Organization, count: 1 },
|
|
262
266
|
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
263
267
|
]);
|
|
264
268
|
},
|
|
@@ -275,7 +279,7 @@ export const Tags: Meta<StoryProps> = {
|
|
|
275
279
|
render: DefaultStory,
|
|
276
280
|
decorators: [
|
|
277
281
|
withClientProvider({
|
|
278
|
-
types: [
|
|
282
|
+
types: [View.View, Table.Table],
|
|
279
283
|
createIdentity: true,
|
|
280
284
|
createSpace: true,
|
|
281
285
|
onCreateSpace: async ({ client, space }) => {
|
|
@@ -294,25 +298,26 @@ export const Tags: Meta<StoryProps> = {
|
|
|
294
298
|
const schema = getSchemaFromPropertyDefinitions(typename, [
|
|
295
299
|
{
|
|
296
300
|
name: 'single',
|
|
297
|
-
format:
|
|
301
|
+
format: Format.TypeFormat.SingleSelect,
|
|
298
302
|
config: { options: selectOptions },
|
|
299
303
|
},
|
|
300
304
|
{
|
|
301
305
|
name: 'multiple',
|
|
302
|
-
format:
|
|
306
|
+
format: Format.TypeFormat.MultiSelect,
|
|
303
307
|
config: { options: selectOptions },
|
|
304
308
|
},
|
|
305
309
|
]);
|
|
306
310
|
const [storedSchema] = await space.db.schemaRegistry.register([schema]);
|
|
307
311
|
|
|
308
312
|
// Initialize table.
|
|
309
|
-
const { view } = await
|
|
310
|
-
|
|
313
|
+
const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename });
|
|
314
|
+
const table = Table.make({ view, jsonSchema });
|
|
315
|
+
space.db.add(table);
|
|
311
316
|
|
|
312
317
|
// Populate.
|
|
313
318
|
Array.from({ length: 10 }).map(() => {
|
|
314
319
|
return space.db.add(
|
|
315
|
-
|
|
320
|
+
Obj.make(storedSchema, {
|
|
316
321
|
single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
|
|
317
322
|
multiple: faker.helpers.randomSubset(selectOptionIds),
|
|
318
323
|
}),
|
|
@@ -6,7 +6,7 @@ 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 {
|
|
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';
|
|
@@ -150,24 +150,24 @@ export const FormCellEditor = ({
|
|
|
150
150
|
setLocalEditing(nextOpen);
|
|
151
151
|
}, []);
|
|
152
152
|
|
|
153
|
-
const
|
|
154
|
-
if (fieldProjection.props.format ===
|
|
153
|
+
const refSchema = useMemo(() => {
|
|
154
|
+
if (fieldProjection.props.format === Format.TypeFormat.Ref && fieldProjection.props.referenceSchema) {
|
|
155
155
|
const { schema: refSchema } = getSchema({
|
|
156
156
|
typename: fieldProjection.props.referenceSchema,
|
|
157
157
|
});
|
|
158
|
-
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
const omit = Schema.omit<any, any, ['id']>('id');
|
|
162
|
-
return omit(refSchema);
|
|
158
|
+
return refSchema;
|
|
163
159
|
}
|
|
164
160
|
return null;
|
|
165
161
|
}, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
|
|
166
162
|
|
|
163
|
+
const createSchema = useMemo(() => {
|
|
164
|
+
return refSchema ? Schema.omit<any, any, ['id']>('id')(refSchema) : null;
|
|
165
|
+
}, [refSchema]);
|
|
166
|
+
|
|
167
167
|
const handleCreate = useCallback(
|
|
168
168
|
(values: any) => {
|
|
169
|
-
if (
|
|
170
|
-
const objectWithId = onCreate(
|
|
169
|
+
if (refSchema && onCreate) {
|
|
170
|
+
const objectWithId = onCreate(refSchema, values);
|
|
171
171
|
if (objectWithId) {
|
|
172
172
|
const ref = Ref.make(objectWithId);
|
|
173
173
|
const path = fieldProjection.field.path;
|
|
@@ -179,7 +179,7 @@ export const FormCellEditor = ({
|
|
|
179
179
|
setLocalEditing(false);
|
|
180
180
|
onSave?.();
|
|
181
181
|
},
|
|
182
|
-
[fieldProjection.field.path, onSave, contextEditing, originalRow,
|
|
182
|
+
[fieldProjection.field.path, onSave, contextEditing, originalRow, refSchema, onCreate],
|
|
183
183
|
);
|
|
184
184
|
|
|
185
185
|
if (!editing) {
|
|
@@ -10,7 +10,8 @@ import { faker } from '@dxos/random';
|
|
|
10
10
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
11
11
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
12
12
|
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
13
|
-
import {
|
|
13
|
+
import { View } from '@dxos/schema';
|
|
14
|
+
import { Task } from '@dxos/types';
|
|
14
15
|
|
|
15
16
|
import { useTestTableModel } from '../../testing';
|
|
16
17
|
import { translations } from '../../translations';
|
|
@@ -23,16 +24,16 @@ type StoryProps = {
|
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
const DefaultStory = ({ editing }: StoryProps) => {
|
|
26
|
-
const { model,
|
|
27
|
+
const { model, table } = useTestTableModel();
|
|
27
28
|
|
|
28
|
-
if (!model || !
|
|
29
|
+
if (!model || !table) {
|
|
29
30
|
return <div />;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
return (
|
|
33
|
-
<div className='flex
|
|
34
|
+
<div className='flex is-[300px] border border-separator' style={{ height: defaultRowSize }}>
|
|
34
35
|
<Grid.Root id='test' editing={editing}>
|
|
35
|
-
<TableCellEditor model={model} schema={
|
|
36
|
+
<TableCellEditor model={model} schema={Task.Task} />
|
|
36
37
|
</Grid.Root>
|
|
37
38
|
</div>
|
|
38
39
|
);
|
|
@@ -45,15 +46,16 @@ const meta = {
|
|
|
45
46
|
decorators: [
|
|
46
47
|
withTheme,
|
|
47
48
|
withClientProvider({
|
|
48
|
-
types: [
|
|
49
|
+
types: [View.View, Task.Task, Table.Table],
|
|
49
50
|
createIdentity: true,
|
|
50
51
|
createSpace: true,
|
|
51
52
|
onCreateSpace: async ({ client, space }) => {
|
|
52
|
-
const { view } = await
|
|
53
|
-
|
|
53
|
+
const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: Task.Task.typename });
|
|
54
|
+
const table = Table.make({ view, jsonSchema });
|
|
55
|
+
space.db.add(table);
|
|
54
56
|
Array.from({ length: 10 }).forEach(() => {
|
|
55
57
|
space.db.add(
|
|
56
|
-
Obj.make(
|
|
58
|
+
Obj.make(Task.Task, {
|
|
57
59
|
title: faker.person.fullName(),
|
|
58
60
|
status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
59
61
|
description: faker.lorem.sentence(),
|
|
@@ -9,7 +9,7 @@ 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 {
|
|
12
|
+
import { Format, TypeEnum } from '@dxos/echo/internal';
|
|
13
13
|
import { invariant } from '@dxos/invariant';
|
|
14
14
|
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
15
15
|
import {
|
|
@@ -74,9 +74,9 @@ export const TableValueEditor = ({
|
|
|
74
74
|
|
|
75
75
|
if (
|
|
76
76
|
fieldProjection?.props.type === TypeEnum.Array ||
|
|
77
|
-
fieldProjection?.props.format ===
|
|
78
|
-
fieldProjection?.props.format ===
|
|
79
|
-
// TODO(thure): Support `
|
|
77
|
+
fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
|
|
78
|
+
fieldProjection?.props.format === Format.TypeFormat.Ref
|
|
79
|
+
// TODO(thure): Support `Format.TypeFormat.MultiSelect`
|
|
80
80
|
) {
|
|
81
81
|
return (
|
|
82
82
|
<FormCellEditor
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
6
|
import React, { useMemo } from 'react';
|
|
7
7
|
|
|
8
8
|
import { live } from '@dxos/live-object';
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
createMenuAction,
|
|
19
19
|
useMenuActions,
|
|
20
20
|
} from '@dxos/react-ui-menu';
|
|
21
|
-
import {
|
|
21
|
+
import { atomFromSignal } from '@dxos/react-ui-menu';
|
|
22
22
|
|
|
23
23
|
import { translationKey } from '../../translations';
|
|
24
24
|
|
|
@@ -30,12 +30,14 @@ export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
|
|
|
30
30
|
|
|
31
31
|
type TableToolbarState = Partial<{ viewDirty: boolean }>;
|
|
32
32
|
|
|
33
|
+
// TODO(burdon): Radix style layout.
|
|
34
|
+
|
|
33
35
|
export type TableToolbarProps = ThemedClassName<
|
|
34
36
|
TableToolbarState & {
|
|
35
37
|
onAdd: () => void;
|
|
36
38
|
onSave: () => void;
|
|
37
39
|
attendableId?: string;
|
|
38
|
-
customActions?:
|
|
40
|
+
customActions?: Atom.Atom<ActionGraphProps>;
|
|
39
41
|
}
|
|
40
42
|
>;
|
|
41
43
|
|
|
@@ -46,33 +48,41 @@ const createTableToolbarActions = ({
|
|
|
46
48
|
customActions,
|
|
47
49
|
}: {
|
|
48
50
|
state: TableToolbarState;
|
|
49
|
-
onAdd
|
|
50
|
-
onSave
|
|
51
|
-
customActions?:
|
|
51
|
+
onAdd?: () => void;
|
|
52
|
+
onSave?: () => void;
|
|
53
|
+
customActions?: Atom.Atom<ActionGraphProps>;
|
|
52
54
|
}) =>
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
Atom.make((get) => {
|
|
56
|
+
const nodes: ActionGraphNodes = [];
|
|
57
|
+
if (onAdd) {
|
|
58
|
+
const add = createMenuAction<TableToolbarActionProperties>('add-row', onAdd, {
|
|
59
|
+
type: 'add-row' as const,
|
|
60
|
+
icon: 'ph--plus--regular',
|
|
61
|
+
label: ['add row label', { ns: translationKey }],
|
|
62
|
+
testId: 'table.toolbar.add-row',
|
|
63
|
+
});
|
|
64
|
+
nodes.push(add);
|
|
65
|
+
}
|
|
66
|
+
if (onSave) {
|
|
67
|
+
const save = createMenuAction<TableToolbarActionProperties>('save-view', onSave, {
|
|
68
|
+
type: 'save-view' as const,
|
|
69
|
+
icon: 'ph--floppy-disk--regular',
|
|
70
|
+
label: ['save view label', { ns: translationKey }],
|
|
71
|
+
testId: 'table.toolbar.save-view',
|
|
72
|
+
iconOnly: false,
|
|
73
|
+
hidden: get(atomFromSignal(() => !state.viewDirty)),
|
|
74
|
+
});
|
|
75
|
+
nodes.push(save);
|
|
76
|
+
}
|
|
68
77
|
const gap = createGapSeparator();
|
|
69
|
-
|
|
78
|
+
nodes.push(...gap.nodes);
|
|
70
79
|
const edges: ActionGraphEdges = nodes.map(({ id: target }) => ({ source: 'root', target }));
|
|
71
80
|
if (customActions) {
|
|
72
81
|
const custom = get(customActions);
|
|
73
82
|
nodes.push(...custom.nodes);
|
|
74
83
|
edges.push(...custom.edges);
|
|
75
84
|
}
|
|
85
|
+
|
|
76
86
|
return {
|
|
77
87
|
nodes,
|
|
78
88
|
edges,
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Format } from '@dxos/echo';
|
|
9
|
+
import { TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
|
|
9
10
|
import { live } from '@dxos/live-object';
|
|
10
11
|
import { faker } from '@dxos/random';
|
|
11
12
|
import { type ProjectionModel } from '@dxos/schema';
|
|
12
13
|
|
|
13
14
|
export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
14
|
-
id: ObjectId,
|
|
15
15
|
name: Schema.optional(Schema.String),
|
|
16
16
|
age: Schema.optional(Schema.Number),
|
|
17
17
|
active: Schema.optional(Schema.Boolean),
|
|
@@ -88,7 +88,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
|
|
|
88
88
|
|
|
89
89
|
if (format) {
|
|
90
90
|
switch (format) {
|
|
91
|
-
case
|
|
91
|
+
case Format.TypeFormat.Currency: {
|
|
92
92
|
item[path] = Math.floor(Math.random() * 1000);
|
|
93
93
|
break;
|
|
94
94
|
}
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Type } from '@dxos/echo';
|
|
8
|
+
import { ProjectionModel } from '@dxos/schema';
|
|
9
|
+
|
|
10
|
+
import { type Table } from '../types';
|
|
11
|
+
|
|
12
|
+
export const useProjectionModel = <S extends Type.Entity.Any>(schema: S | undefined, table: Table.Table | undefined) =>
|
|
13
|
+
useMemo(() => {
|
|
14
|
+
if (schema && table?.view.target?.projection) {
|
|
15
|
+
const projection = new ProjectionModel(Type.toJsonSchema(schema), table.view.target.projection);
|
|
16
|
+
projection.normalizeView();
|
|
17
|
+
return projection;
|
|
18
|
+
}
|
|
19
|
+
}, [schema, table?.view.target?.projection]);
|
|
@@ -6,18 +6,17 @@ 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 {
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
10
|
import { type Live } from '@dxos/live-object';
|
|
11
|
-
import { fullyQualifiedId } from '@dxos/react-client/echo';
|
|
12
11
|
import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
|
|
13
|
-
import { type
|
|
12
|
+
import { type ProjectionModel } from '@dxos/schema';
|
|
14
13
|
import { isNonNullable } from '@dxos/util';
|
|
15
14
|
|
|
16
15
|
import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
|
|
16
|
+
import { type Table } from '../types';
|
|
17
17
|
|
|
18
18
|
export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
19
|
-
|
|
20
|
-
schema?: JsonSchemaType;
|
|
19
|
+
object?: Table.Table;
|
|
21
20
|
projection?: ProjectionModel;
|
|
22
21
|
rows?: Live<T>[];
|
|
23
22
|
rowActions?: TableRowAction[];
|
|
@@ -29,8 +28,7 @@ export type UseTableModelParams<T extends TableRow = TableRow> = {
|
|
|
29
28
|
>;
|
|
30
29
|
|
|
31
30
|
export const useTableModel = <T extends TableRow = TableRow>({
|
|
32
|
-
|
|
33
|
-
schema,
|
|
31
|
+
object,
|
|
34
32
|
projection,
|
|
35
33
|
rows,
|
|
36
34
|
rowActions,
|
|
@@ -39,20 +37,19 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
39
37
|
onRowAction,
|
|
40
38
|
...props
|
|
41
39
|
}: UseTableModelParams<T>): TableModel<T> | undefined => {
|
|
42
|
-
const selected = useSelected(
|
|
43
|
-
const initialSelection = useMemo(() => selected, [
|
|
40
|
+
const selected = useSelected(object && Obj.getDXN(object).toString(), 'multi');
|
|
41
|
+
const initialSelection = useMemo(() => selected, [object]);
|
|
44
42
|
|
|
45
43
|
const [model, setModel] = useState<TableModel<T>>();
|
|
46
44
|
useEffect(() => {
|
|
47
|
-
if (!
|
|
45
|
+
if (!object || !projection) {
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
let model: TableModel<T> | undefined;
|
|
52
50
|
const t = setTimeout(async () => {
|
|
53
51
|
model = new TableModel<T>({
|
|
54
|
-
|
|
55
|
-
schema,
|
|
52
|
+
object,
|
|
56
53
|
projection,
|
|
57
54
|
features,
|
|
58
55
|
rowActions,
|
|
@@ -68,7 +65,8 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
68
65
|
clearTimeout(t);
|
|
69
66
|
void model?.close();
|
|
70
67
|
};
|
|
71
|
-
|
|
68
|
+
// TODO(burdon): Trigger if callbacks change?
|
|
69
|
+
}, [object, projection, features, rowActions, initialSelection]);
|
|
72
70
|
|
|
73
71
|
// Update data.
|
|
74
72
|
useEffect(() => {
|
|
@@ -12,7 +12,7 @@ import { updateCounter } from '@dxos/echo/testing';
|
|
|
12
12
|
import { createEchoSchema } from '@dxos/echo/testing';
|
|
13
13
|
import { registerSignalsRuntime } from '@dxos/echo-signals';
|
|
14
14
|
import { live } from '@dxos/live-object';
|
|
15
|
-
import {
|
|
15
|
+
import { ProjectionModel, View } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { Table } from '../types';
|
|
18
18
|
|
|
@@ -112,11 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
|
|
|
112
112
|
|
|
113
113
|
const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
|
|
114
114
|
const schema = createEchoSchema(Test);
|
|
115
|
-
const
|
|
116
|
-
const view = createView({
|
|
115
|
+
const view = View.make({
|
|
117
116
|
query: Query.select(Filter.type(schema)),
|
|
118
117
|
jsonSchema: schema.jsonSchema,
|
|
119
|
-
presentation: table,
|
|
120
118
|
});
|
|
121
|
-
|
|
119
|
+
const object = Table.make({ view });
|
|
120
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
121
|
+
return new TableModel({ object, projection, ...props });
|
|
122
122
|
};
|