@dxos/react-ui-table 0.8.4-main.fcfe5033a5 → 0.8.4-staging.60fe92afc8
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/LICENSE +102 -5
- package/dist/lib/browser/{chunk-F7Y374KL.mjs → chunk-4KOQTPFC.mjs} +62 -111
- package/dist/lib/browser/chunk-4KOQTPFC.mjs.map +7 -0
- package/dist/lib/browser/{chunk-BF54SSNL.mjs → chunk-62PYOJPY.mjs} +13 -16
- package/dist/lib/browser/chunk-62PYOJPY.mjs.map +7 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +57 -131
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +9 -8
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/translations.mjs +36 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +2 -1
- package/dist/lib/node-esm/{chunk-SIRDIPHZ.mjs → chunk-7LMOZUTS.mjs} +13 -16
- package/dist/lib/node-esm/chunk-7LMOZUTS.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-CCJ4LOVM.mjs → chunk-KOSV2J5N.mjs} +62 -111
- package/dist/lib/node-esm/chunk-KOSV2J5N.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +57 -131
- 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 +9 -8
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/translations.mjs +37 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +2 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
- package/dist/types/src/components/Table/ColumnSettings.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/DynamicTable.stories.d.ts +21 -20
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +87 -81
- 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 +2 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +88 -81
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +2 -1
- package/dist/types/src/components/Table/TableContent.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableToolbar.d.ts +1 -1
- package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +1 -2
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +19 -19
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +7 -7
- 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/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +19 -19
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +12 -6
- package/dist/types/src/types/Table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +6 -6
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/signals.d.ts.map +1 -1
- package/dist/types/src/util/sort.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts +20 -20
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts +4 -4
- package/dist/types/src/util/table-controls.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 +53 -63
- package/src/components/Table/ColumnActionsMenu.tsx +2 -1
- package/src/components/Table/ColumnSettings.tsx +2 -2
- package/src/components/Table/DynamicTable.stories.tsx +9 -8
- package/src/components/Table/DynamicTable.tsx +11 -12
- package/src/components/Table/Relations.stories.tsx +46 -9
- package/src/components/Table/RowActionsMenu.tsx +3 -2
- package/src/components/Table/Table.stories.tsx +86 -33
- package/src/components/Table/TableContent.tsx +9 -4
- package/src/components/Table/TableToolbar.tsx +2 -2
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +8 -7
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +7 -4
- package/src/components/TableCellEditor/TableCellEditor.tsx +8 -6
- package/src/components/testing.ts +7 -12
- package/src/hooks/useProjectionModel.ts +5 -6
- package/src/hooks/useTableModel.ts +1 -1
- package/src/index.ts +0 -4
- package/src/model/table-model.test.ts +4 -9
- package/src/model/table-model.ts +43 -36
- package/src/model/table-presentation.test.ts +4 -9
- package/src/model/table-presentation.ts +15 -15
- package/src/testing/useTestTableModel.ts +6 -7
- package/src/types/Table.ts +16 -15
- package/src/util/dynamic-table.test.ts +13 -20
- package/src/util/dynamic-table.ts +19 -18
- package/dist/lib/browser/chunk-BF54SSNL.mjs.map +0 -7
- package/dist/lib/browser/chunk-F7Y374KL.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CCJ4LOVM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs.map +0 -7
|
@@ -5,24 +5,26 @@
|
|
|
5
5
|
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
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
8
9
|
|
|
9
|
-
import { Annotation, type Database, Format, Obj, type QueryAST, Ref, Type } from '@dxos/echo';
|
|
10
|
-
import { View } from '@dxos/echo';
|
|
10
|
+
import { DXN, Annotation, type Database, Format, Obj, type QueryAST, Ref, Type, View } from '@dxos/echo';
|
|
11
11
|
import { type Mutable, PropertyMetaAnnotationId } from '@dxos/echo/internal';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
13
|
import { random } from '@dxos/random';
|
|
14
14
|
import { PublicKey } from '@dxos/react-client';
|
|
15
15
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
16
16
|
import { Panel, ScrollArea } from '@dxos/react-ui';
|
|
17
|
-
import { ViewEditor
|
|
18
|
-
import {
|
|
17
|
+
import { ViewEditor } from '@dxos/react-ui-form';
|
|
18
|
+
import { translations as formTranslations } from '@dxos/react-ui-form/translations';
|
|
19
|
+
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
19
20
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
20
|
-
import { ViewModel, getSchemaFromPropertyDefinitions
|
|
21
|
+
import { ViewModel, getSchemaFromPropertyDefinitions } from '@dxos/schema';
|
|
21
22
|
import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
|
|
22
23
|
import { withRegistry } from '@dxos/storybook-utils';
|
|
23
24
|
|
|
25
|
+
import { translations } from '#translations';
|
|
26
|
+
|
|
24
27
|
import { useTestTableModel } from '../../testing';
|
|
25
|
-
import { translations } from '../../translations';
|
|
26
28
|
import { Table } from '../../types';
|
|
27
29
|
import { Table as TableComponent } from './Table';
|
|
28
30
|
|
|
@@ -51,10 +53,11 @@ const Example = Schema.Struct({
|
|
|
51
53
|
title: 'Parent',
|
|
52
54
|
}),
|
|
53
55
|
}).pipe(
|
|
54
|
-
Type.object({ typename: `com.example.type.${PublicKey.random().truncate()}`, version: '0.1.0' }),
|
|
55
56
|
Annotation.LabelAnnotation.set(['name']),
|
|
57
|
+
// NSID last segment must start with a letter (DXN spec), so prefix the random hex.
|
|
58
|
+
Type.makeObject(DXN.make(`com.example.type.example${PublicKey.random().truncate()}`, '0.1.0')),
|
|
56
59
|
);
|
|
57
|
-
interface Example extends
|
|
60
|
+
interface Example extends Type.InstanceType<typeof Example> {}
|
|
58
61
|
|
|
59
62
|
const StoryViewEditor = ({
|
|
60
63
|
view,
|
|
@@ -63,17 +66,16 @@ const StoryViewEditor = ({
|
|
|
63
66
|
handleDeleteColumn,
|
|
64
67
|
}: {
|
|
65
68
|
view?: View.View;
|
|
66
|
-
schema?:
|
|
69
|
+
schema?: Type.AnyEntity;
|
|
67
70
|
db?: Database.Database;
|
|
68
71
|
handleDeleteColumn: (fieldId: string) => void;
|
|
69
72
|
}) => {
|
|
70
73
|
const handleQueryChanged = useCallback(
|
|
71
74
|
(newQuery: QueryAST.Query) => {
|
|
72
75
|
invariant(schema);
|
|
73
|
-
invariant(Type.
|
|
74
|
-
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
76
|
+
invariant(Type.getDatabase(schema) != null);
|
|
75
77
|
invariant(view);
|
|
76
|
-
Obj.
|
|
78
|
+
Obj.update(view, (view) => {
|
|
77
79
|
view.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
78
80
|
});
|
|
79
81
|
},
|
|
@@ -86,8 +88,8 @@ const StoryViewEditor = ({
|
|
|
86
88
|
|
|
87
89
|
return (
|
|
88
90
|
<ViewEditor
|
|
89
|
-
registry={db?.
|
|
90
|
-
|
|
91
|
+
registry={db?.graph.registry}
|
|
92
|
+
type={schema}
|
|
91
93
|
view={view}
|
|
92
94
|
onQueryChanged={handleQueryChanged}
|
|
93
95
|
onDelete={handleDeleteColumn}
|
|
@@ -141,9 +143,7 @@ const DefaultStory = () => {
|
|
|
141
143
|
<ScrollArea.Root orientation='vertical' classNames='border-l border-separator'>
|
|
142
144
|
<ScrollArea.Viewport>
|
|
143
145
|
<StoryViewEditor view={table.view.target} schema={schema} db={db} handleDeleteColumn={handleDeleteColumn} />
|
|
144
|
-
<
|
|
145
|
-
{JSON.stringify({ view: table.view.target, schema }, null, 2)}
|
|
146
|
-
</SyntaxHighlighter>
|
|
146
|
+
<JsonHighlighter data={{ view: table.view.target, schema }} classNames='text-xs' />
|
|
147
147
|
</ScrollArea.Viewport>
|
|
148
148
|
</ScrollArea.Root>
|
|
149
149
|
</div>
|
|
@@ -170,10 +170,13 @@ const meta = {
|
|
|
170
170
|
createIdentity: true,
|
|
171
171
|
createSpace: true,
|
|
172
172
|
onCreateSpace: async ({ space }) => {
|
|
173
|
-
const
|
|
174
|
-
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
173
|
+
const type = await space.db.addType(Example);
|
|
174
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
175
|
+
db: space.db,
|
|
176
|
+
typename: Type.getTypename(type),
|
|
177
|
+
});
|
|
175
178
|
const table = Table.make({ view, jsonSchema });
|
|
176
|
-
Obj.
|
|
179
|
+
Obj.update(view, (view) => {
|
|
177
180
|
view.projection.fields = [
|
|
178
181
|
view.projection.fields.find((field) => field.path === 'name')!,
|
|
179
182
|
...view.projection.fields.filter((field) => field.path !== 'name'),
|
|
@@ -184,7 +187,7 @@ const meta = {
|
|
|
184
187
|
|
|
185
188
|
Array.from({ length: 10 }).map(() => {
|
|
186
189
|
return space.db.add(
|
|
187
|
-
Obj.make(
|
|
190
|
+
Obj.make(type, {
|
|
188
191
|
name: random.lorem.sentence(),
|
|
189
192
|
status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
190
193
|
description: random.lorem.paragraph(),
|
|
@@ -219,7 +222,7 @@ export const StaticSchema: StoryObj = {
|
|
|
219
222
|
onCreateSpace: async ({ space }) => {
|
|
220
223
|
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
221
224
|
db: space.db,
|
|
222
|
-
typename: TestSchema.Person
|
|
225
|
+
typename: Type.getTypename(TestSchema.Person),
|
|
223
226
|
});
|
|
224
227
|
const table = Table.make({ view, jsonSchema });
|
|
225
228
|
space.db.add(table);
|
|
@@ -248,12 +251,7 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
248
251
|
}),
|
|
249
252
|
),
|
|
250
253
|
),
|
|
251
|
-
}).pipe(
|
|
252
|
-
Type.object({
|
|
253
|
-
typename: 'org.dxos.type.contactWithArrayOfEmails',
|
|
254
|
-
version: '0.1.0',
|
|
255
|
-
}),
|
|
256
|
-
);
|
|
254
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.type.contactWithArrayOfEmails', '0.1.0')));
|
|
257
255
|
|
|
258
256
|
export const ArrayOfObjects: StoryObj = {
|
|
259
257
|
render: DefaultStory,
|
|
@@ -265,7 +263,7 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
265
263
|
onCreateSpace: async ({ space }) => {
|
|
266
264
|
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
267
265
|
db: space.db,
|
|
268
|
-
typename: ContactWithArrayOfEmails
|
|
266
|
+
typename: Type.getTypename(ContactWithArrayOfEmails),
|
|
269
267
|
});
|
|
270
268
|
const table = Table.make({ view, jsonSchema });
|
|
271
269
|
space.db.add(table);
|
|
@@ -285,6 +283,61 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
285
283
|
},
|
|
286
284
|
};
|
|
287
285
|
|
|
286
|
+
export const RequiredSchema: StoryObj = {
|
|
287
|
+
render: DefaultStory,
|
|
288
|
+
decorators: [
|
|
289
|
+
withClientProvider({
|
|
290
|
+
types: [View.View, Table.Table, TestSchema.Person],
|
|
291
|
+
createIdentity: true,
|
|
292
|
+
createSpace: true,
|
|
293
|
+
onCreateSpace: async ({ space }) => {
|
|
294
|
+
// TestSchema.Person has a required `name: Schema.String` field.
|
|
295
|
+
// No pre-populated rows so the add-row flow is the first interaction.
|
|
296
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
297
|
+
db: space.db,
|
|
298
|
+
typename: Type.getTypename(TestSchema.Person),
|
|
299
|
+
});
|
|
300
|
+
const table = Table.make({ view, jsonSchema });
|
|
301
|
+
space.db.add(table);
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
],
|
|
305
|
+
parameters: {
|
|
306
|
+
layout: 'fullscreen',
|
|
307
|
+
translations: [...translations, ...formTranslations],
|
|
308
|
+
},
|
|
309
|
+
play: async ({ canvasElement }) => {
|
|
310
|
+
const canvas = within(canvasElement);
|
|
311
|
+
|
|
312
|
+
// 30 s covers ECHO client + space + table creation (the only inherently slow step).
|
|
313
|
+
const addRowButton = await canvas.findByTestId('table.toolbar.add-row', undefined, { timeout: 30_000 });
|
|
314
|
+
|
|
315
|
+
// Person.name is required, so db.add throws → useAddRow returns 'draft' →
|
|
316
|
+
// a draft row appears in frozenRowsEnd and focus is set to its first cell.
|
|
317
|
+
await userEvent.click(addRowButton);
|
|
318
|
+
|
|
319
|
+
const draftCell = await canvas.findByTestId('frozenRowsEnd.0.0');
|
|
320
|
+
await userEvent.click(draftCell);
|
|
321
|
+
|
|
322
|
+
// Open the editor with Enter rather than typing to open — the latter routes the first
|
|
323
|
+
// character into dx-grid's `initialContent` and races the editor mount. The empty value
|
|
324
|
+
// fails validation; the editor must stay open so the value below can be entered.
|
|
325
|
+
await userEvent.keyboard('{Enter}');
|
|
326
|
+
await canvas.findByTestId('grid.cell-editor');
|
|
327
|
+
|
|
328
|
+
// The editor is focused (autoFocus); type the required value and commit.
|
|
329
|
+
await userEvent.keyboard('Alice');
|
|
330
|
+
await userEvent.keyboard('{Enter}');
|
|
331
|
+
|
|
332
|
+
// The draft row is committed and appears in the grid with the typed value.
|
|
333
|
+
await waitFor(async () => {
|
|
334
|
+
const cell = canvas.getByTestId('grid.0.0');
|
|
335
|
+
const text = cell.querySelector('.dx-grid__cell__content')?.textContent;
|
|
336
|
+
await expect(text).toBe('Alice');
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
|
|
288
341
|
export const Tags: Meta<DefaultStoryProps> = {
|
|
289
342
|
title: 'ui/react-ui-table/Table',
|
|
290
343
|
render: DefaultStory,
|
|
@@ -295,7 +348,7 @@ export const Tags: Meta<DefaultStoryProps> = {
|
|
|
295
348
|
createSpace: true,
|
|
296
349
|
onCreateSpace: async ({ space }) => {
|
|
297
350
|
// Configure schema.
|
|
298
|
-
const typename = 'com.example.type.
|
|
351
|
+
const typename = 'com.example.type.singleSelect';
|
|
299
352
|
const selectOptions = [
|
|
300
353
|
{ id: 'one', title: 'One', color: 'emerald' },
|
|
301
354
|
{ id: 'two', title: 'Two', color: 'blue' },
|
|
@@ -306,7 +359,7 @@ export const Tags: Meta<DefaultStoryProps> = {
|
|
|
306
359
|
|
|
307
360
|
const selectOptionIds = selectOptions.map((o) => o.id);
|
|
308
361
|
|
|
309
|
-
const
|
|
362
|
+
const type = getSchemaFromPropertyDefinitions(typename, [
|
|
310
363
|
{
|
|
311
364
|
name: 'single',
|
|
312
365
|
format: Format.TypeFormat.SingleSelect,
|
|
@@ -318,7 +371,7 @@ export const Tags: Meta<DefaultStoryProps> = {
|
|
|
318
371
|
config: { options: selectOptions },
|
|
319
372
|
},
|
|
320
373
|
]);
|
|
321
|
-
const
|
|
374
|
+
const storedType = await space.db.addType(type);
|
|
322
375
|
|
|
323
376
|
// Initialize table.
|
|
324
377
|
const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename });
|
|
@@ -328,7 +381,7 @@ export const Tags: Meta<DefaultStoryProps> = {
|
|
|
328
381
|
// Populate.
|
|
329
382
|
Array.from({ length: 10 }).map(() => {
|
|
330
383
|
return space.db.add(
|
|
331
|
-
Obj.make(
|
|
384
|
+
Obj.make(Type.assertObject(storedType), {
|
|
332
385
|
single: random.helpers.arrayElement([...selectOptionIds, undefined]),
|
|
333
386
|
multiple: random.helpers.randomSubset(selectOptionIds),
|
|
334
387
|
}),
|
|
@@ -8,6 +8,7 @@ import React, { type MouseEvent, type WheelEvent, useCallback, useContext, useEf
|
|
|
8
8
|
|
|
9
9
|
import { type Type } from '@dxos/echo';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
|
+
import { composable, composableProps } from '@dxos/react-ui';
|
|
11
12
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
12
13
|
import {
|
|
13
14
|
type DxGridAxisMeta,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
gridSeparatorInlineEnd,
|
|
22
23
|
} from '@dxos/react-ui-grid';
|
|
23
24
|
import { DxEditRequest } from '@dxos/react-ui-grid';
|
|
24
|
-
import {
|
|
25
|
+
import { mx } from '@dxos/ui-theme';
|
|
25
26
|
|
|
26
27
|
import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
27
28
|
import { tableButtons, tableControls } from '../../util';
|
|
@@ -49,16 +50,20 @@ export type TableContentProps = {
|
|
|
49
50
|
presentation?: TablePresentation;
|
|
50
51
|
// TODO(burdon): Factor out attention.
|
|
51
52
|
ignoreAttention?: boolean;
|
|
53
|
+
attendableId?: string;
|
|
52
54
|
onCreate?: OnCreateHandler;
|
|
53
55
|
onRowClick?: (row: any) => void;
|
|
54
56
|
testId?: string;
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
export const TableContent = composable<HTMLDivElement, TableContentProps>(
|
|
58
|
-
(
|
|
60
|
+
(
|
|
61
|
+
{ schema, model, presentation, ignoreAttention, attendableId, onCreate, onRowClick, testId, ...props },
|
|
62
|
+
forwardedRef,
|
|
63
|
+
) => {
|
|
59
64
|
const registry = useContext(RegistryContext);
|
|
60
65
|
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
61
|
-
const { hasAttention } = useAttention(model?.id ?? 'table');
|
|
66
|
+
const { hasAttention } = useAttention(attendableId ?? model?.id ?? 'table');
|
|
62
67
|
const modals = useMemo(() => new ModalController(registry), [registry]);
|
|
63
68
|
const columnMeta = useAtomValue(model?.columnMeta ?? emptyColumnMeta);
|
|
64
69
|
// Subscribe to rows atom to trigger re-render when rows change.
|
|
@@ -379,7 +384,7 @@ export const TableContent = composable<HTMLDivElement, TableContentProps>(
|
|
|
379
384
|
}, [dxGrid, model, presentation]);
|
|
380
385
|
|
|
381
386
|
if (!model || !modals) {
|
|
382
|
-
return <span
|
|
387
|
+
return <span className='dx-attention-surface' />;
|
|
383
388
|
}
|
|
384
389
|
|
|
385
390
|
return (
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Atom, RegistryContext } from '@effect-atom/atom-react';
|
|
6
6
|
import React, { useContext, useEffect, useMemo } from 'react';
|
|
7
7
|
|
|
8
|
+
import { composable, composableProps } from '@dxos/react-ui';
|
|
8
9
|
import {
|
|
9
10
|
type ActionGraphEdges,
|
|
10
11
|
type ActionGraphNodes,
|
|
@@ -16,9 +17,8 @@ import {
|
|
|
16
17
|
createMenuAction,
|
|
17
18
|
useMenuActions,
|
|
18
19
|
} from '@dxos/react-ui-menu';
|
|
19
|
-
import { composable, composableProps } from '@dxos/ui-theme';
|
|
20
20
|
|
|
21
|
-
import { translationKey } from '
|
|
21
|
+
import { translationKey } from '#translations';
|
|
22
22
|
|
|
23
23
|
export type TableToolbarActionProperties = { type: TableToolbarActionType };
|
|
24
24
|
|
|
@@ -23,11 +23,10 @@ export const CellValidationMessage = ({
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const bgClass = variant === 'error' ? 'bg-error-surface' : 'bg-warning-surface';
|
|
26
|
-
const textClass = variant === 'error' ? 'text-error-
|
|
26
|
+
const textClass = variant === 'error' ? 'text-error-fg' : 'text-warning-fg';
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<div
|
|
30
|
-
role='none'
|
|
31
30
|
className={`absolute ${bgClass} ${textClass} rounded-h-sm text-xs p-1`}
|
|
32
31
|
style={{
|
|
33
32
|
...{ '--dx-grid-cell-width': `${box?.inlineSize ?? 200}px` },
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type * as Schema from 'effect/Schema';
|
|
6
6
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { Entity,
|
|
9
|
-
import {
|
|
8
|
+
import { Entity, Ref, Type } from '@dxos/echo';
|
|
9
|
+
import { SchemaEx } from '@dxos/effect';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
11
|
import { type Label, Popover } from '@dxos/react-ui';
|
|
12
12
|
import { Form, type FormRootProps, type RefFieldProps } from '@dxos/react-ui-form';
|
|
@@ -14,8 +14,9 @@ import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
|
14
14
|
import { type FieldProjection } from '@dxos/schema';
|
|
15
15
|
import { getDeep, isTruthy, setDeep } from '@dxos/util';
|
|
16
16
|
|
|
17
|
+
import { translationKey } from '#translations';
|
|
18
|
+
|
|
17
19
|
import { type ModalController, type TableModel, type TableRow } from '../../model';
|
|
18
|
-
import { translationKey } from '../../translations';
|
|
19
20
|
import { narrowSchema } from '../../util';
|
|
20
21
|
|
|
21
22
|
const createOptionLabel: Label = ['create-new-object.label', { ns: translationKey }];
|
|
@@ -62,7 +63,7 @@ export const FormCellEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
|
62
63
|
return undefined;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
return narrowSchema(schema, [fieldProjection.field.path]);
|
|
66
|
+
return narrowSchema(Type.getSchema(schema), [fieldProjection.field.path]);
|
|
66
67
|
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
67
68
|
|
|
68
69
|
const originalRow = useMemo<TableRow | undefined>(() => {
|
|
@@ -150,8 +151,8 @@ export const FormCellEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
|
150
151
|
results
|
|
151
152
|
.map((obj) => {
|
|
152
153
|
return {
|
|
153
|
-
id: Entity.
|
|
154
|
-
label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
154
|
+
id: Entity.getURI(obj),
|
|
155
|
+
label: SchemaEx.getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
155
156
|
};
|
|
156
157
|
})
|
|
157
158
|
.filter(isTruthy),
|
|
@@ -166,7 +167,7 @@ export const FormCellEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
|
166
167
|
<Popover.Root open={editing} onOpenChange={handleOpenChange}>
|
|
167
168
|
<Popover.VirtualTrigger virtualRef={anchorRef} />
|
|
168
169
|
<Popover.Portal>
|
|
169
|
-
<Popover.Content tabIndex={-1} classNames='dx-card-popover-width dx-density-
|
|
170
|
+
<Popover.Content tabIndex={-1} classNames='dx-card-popover-width dx-density-md'>
|
|
170
171
|
<Popover.Arrow />
|
|
171
172
|
<Popover.Viewport>
|
|
172
173
|
<Form.Root
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
|
-
import { Obj } from '@dxos/echo';
|
|
9
|
-
import { View } from '@dxos/echo';
|
|
8
|
+
import { Obj, Type, View } from '@dxos/echo';
|
|
10
9
|
import { random } from '@dxos/random';
|
|
11
10
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
12
11
|
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
@@ -14,8 +13,9 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
|
14
13
|
import { ViewModel } from '@dxos/schema';
|
|
15
14
|
import { Task } from '@dxos/types';
|
|
16
15
|
|
|
16
|
+
import { translations } from '#translations';
|
|
17
|
+
|
|
17
18
|
import { useTestTableModel } from '../../testing';
|
|
18
|
-
import { translations } from '../../translations';
|
|
19
19
|
import { Table } from '../../types';
|
|
20
20
|
import { TableCellEditor } from './TableCellEditor';
|
|
21
21
|
|
|
@@ -52,7 +52,10 @@ const meta = {
|
|
|
52
52
|
createIdentity: true,
|
|
53
53
|
createSpace: true,
|
|
54
54
|
onCreateSpace: async ({ space }) => {
|
|
55
|
-
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
55
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
56
|
+
db: space.db,
|
|
57
|
+
typename: Type.getTypename(Task.Task),
|
|
58
|
+
});
|
|
56
59
|
const table = Table.make({ view, jsonSchema });
|
|
57
60
|
space.db.add(table);
|
|
58
61
|
Array.from({ length: 10 }).forEach(() => {
|
|
@@ -8,7 +8,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
|
8
8
|
|
|
9
9
|
import { debounce } from '@dxos/async';
|
|
10
10
|
import { type Type } from '@dxos/echo';
|
|
11
|
-
import { Format, TypeEnum } from '@dxos/echo/
|
|
11
|
+
import { Format, TypeEnum } from '@dxos/echo/Format';
|
|
12
12
|
import { invariant } from '@dxos/invariant';
|
|
13
13
|
import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
|
|
14
14
|
import {
|
|
@@ -90,6 +90,12 @@ export const TableValueEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
|
|
|
90
90
|
return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
const cellEditorSlots: GridCellEditorProps['slots'] = {
|
|
94
|
+
content: {
|
|
95
|
+
className: '!py-(--dx-grid-cell-editor-padding-block)',
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
93
99
|
export const TableCellEditor = ({
|
|
94
100
|
__gridScope,
|
|
95
101
|
model,
|
|
@@ -245,11 +251,7 @@ export const TableCellEditor = ({
|
|
|
245
251
|
<CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
|
|
246
252
|
<GridCellEditor
|
|
247
253
|
extensions={extensions}
|
|
248
|
-
slots={
|
|
249
|
-
content: {
|
|
250
|
-
className: '!py-(--dx-grid-cell-editor-padding-block)',
|
|
251
|
-
},
|
|
252
|
-
}}
|
|
254
|
+
slots={cellEditorSlots}
|
|
253
255
|
getCellContent={getCellContent}
|
|
254
256
|
onBlur={handleBlur}
|
|
255
257
|
/>
|
|
@@ -6,9 +6,9 @@ import { Atom } from '@effect-atom/atom-react';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import { useEffect } from 'react';
|
|
8
8
|
|
|
9
|
-
import { Format, Type } from '@dxos/echo';
|
|
10
|
-
import { TypeEnum } from '@dxos/echo/
|
|
11
|
-
import {
|
|
9
|
+
import { DXN, Format, Type } from '@dxos/echo';
|
|
10
|
+
import { TypeEnum } from '@dxos/echo/Format';
|
|
11
|
+
import { SchemaEx } from '@dxos/effect';
|
|
12
12
|
import { random } from '@dxos/random';
|
|
13
13
|
import { type ProjectionModel } from '@dxos/schema';
|
|
14
14
|
|
|
@@ -17,12 +17,7 @@ export const TestSchema = Schema.Struct({
|
|
|
17
17
|
age: Schema.optional(Schema.Number),
|
|
18
18
|
active: Schema.optional(Schema.Boolean),
|
|
19
19
|
netWorth: Schema.optional(Schema.Number),
|
|
20
|
-
}).pipe(
|
|
21
|
-
Type.object({
|
|
22
|
-
typename: 'com.example.type.test',
|
|
23
|
-
version: '0.1.0',
|
|
24
|
-
}),
|
|
25
|
-
);
|
|
20
|
+
}).pipe(Type.makeObject(DXN.make('com.example.type.test', '0.1.0')));
|
|
26
21
|
|
|
27
22
|
export type TestItem = {
|
|
28
23
|
name: string;
|
|
@@ -99,15 +94,15 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
|
|
|
99
94
|
// TODO(ZaymonFC): Restore this once I know how to derive the type from the schema.
|
|
100
95
|
switch (type) {
|
|
101
96
|
case TypeEnum.String: {
|
|
102
|
-
setValue(item, path, `Updated ${Date.now()}`);
|
|
97
|
+
SchemaEx.setValue(item, path, `Updated ${Date.now()}`);
|
|
103
98
|
break;
|
|
104
99
|
}
|
|
105
100
|
case TypeEnum.Number: {
|
|
106
|
-
setValue(item, path, Math.floor(Math.random() * 100));
|
|
101
|
+
SchemaEx.setValue(item, path, Math.floor(Math.random() * 100));
|
|
107
102
|
break;
|
|
108
103
|
}
|
|
109
104
|
case TypeEnum.Boolean: {
|
|
110
|
-
setValue(item, path, !item[path]);
|
|
105
|
+
SchemaEx.setValue(item, path, !item[path]);
|
|
111
106
|
break;
|
|
112
107
|
}
|
|
113
108
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { type Registry } from '@effect-atom/atom-react';
|
|
6
6
|
import { useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Type } from '@dxos/echo';
|
|
9
9
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
10
10
|
import { ProjectionModel, createEchoChangeCallback } from '@dxos/schema';
|
|
11
11
|
|
|
@@ -21,13 +21,12 @@ export const useProjectionModel = <S extends Type.AnyEntity>(
|
|
|
21
21
|
useAsyncEffect(async () => {
|
|
22
22
|
if (schema && table) {
|
|
23
23
|
const view = await table.view.load();
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
|
|
24
|
+
// Use the live jsonSchema reference for reactivity.
|
|
25
|
+
const jsonSchema = schema.jsonSchema;
|
|
27
26
|
|
|
28
27
|
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
29
|
-
// Pass
|
|
30
|
-
const change = createEchoChangeCallback(view, Type.
|
|
28
|
+
// Pass the type entity only when stored, to allow schema mutations.
|
|
29
|
+
const change = createEchoChangeCallback(view, Type.getDatabase(schema) != null ? schema : undefined);
|
|
31
30
|
|
|
32
31
|
const projection = new ProjectionModel({ registry, view, baseSchema: jsonSchema, change });
|
|
33
32
|
projection.normalizeView();
|
|
@@ -44,7 +44,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
|
|
|
44
44
|
...props
|
|
45
45
|
}: UseTableModelProps<T>): TableModel<T> | undefined => {
|
|
46
46
|
const registry = useContext(RegistryContext);
|
|
47
|
-
const selected = useSelected(object && Obj.
|
|
47
|
+
const selected = useSelected(object && Obj.getURI(object), 'multi');
|
|
48
48
|
const initialSelection = useMemo(() => selected, [object]);
|
|
49
49
|
|
|
50
50
|
const [model, setModel] = useState<TableModel<T>>();
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Registry } from '@effect-atom/atom-react';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import { Filter, JsonSchema, Query, Type } from '@dxos/echo';
|
|
9
|
+
import { DXN, Filter, JsonSchema, Query, Type } from '@dxos/echo';
|
|
10
10
|
import { createEchoSchema } from '@dxos/echo/testing';
|
|
11
11
|
import { ProjectionModel, ViewModel, createDirectChangeCallback } from '@dxos/schema';
|
|
12
12
|
|
|
@@ -118,15 +118,10 @@ describe('TableModel', () => {
|
|
|
118
118
|
const Test = Schema.Struct({
|
|
119
119
|
title: Schema.String,
|
|
120
120
|
completed: Schema.Boolean,
|
|
121
|
-
}).pipe(
|
|
122
|
-
Type.object({
|
|
123
|
-
typename: 'com.example.type.test',
|
|
124
|
-
version: '0.1.0',
|
|
125
|
-
}),
|
|
126
|
-
);
|
|
121
|
+
}).pipe(Type.makeObject(DXN.make('com.example.type.test', '0.1.0')));
|
|
127
122
|
|
|
128
123
|
const createTableModel = (registry: Registry.Registry, props: Partial<TableModelProps> = {}): TableModel => {
|
|
129
|
-
const schema = createEchoSchema(Test);
|
|
124
|
+
const schema = createEchoSchema(Type.getSchema(Test));
|
|
130
125
|
const view = ViewModel.make({
|
|
131
126
|
query: Query.select(Filter.type(schema)),
|
|
132
127
|
jsonSchema: schema.jsonSchema,
|
|
@@ -136,7 +131,7 @@ const createTableModel = (registry: Registry.Registry, props: Partial<TableModel
|
|
|
136
131
|
registry,
|
|
137
132
|
view,
|
|
138
133
|
baseSchema: schema.jsonSchema,
|
|
139
|
-
change: createDirectChangeCallback(view.projection, JsonSchema.toJsonSchema(schema)),
|
|
134
|
+
change: createDirectChangeCallback(view.projection, JsonSchema.toJsonSchema(Type.getSchema(schema))),
|
|
140
135
|
});
|
|
141
136
|
projection.normalizeView();
|
|
142
137
|
return new TableModel({
|