@dxos/react-ui-table 0.8.4-main.e098934 → 0.8.4-main.e8ec1fe
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{chunk-63UUF4IG.mjs → chunk-2PFAPQL4.mjs} +7 -7
- package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +7 -0
- package/dist/lib/browser/{chunk-BBXDTWCX.mjs → chunk-GS2UNGJK.mjs} +443 -490
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +161 -303
- 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 -7
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node-esm/{chunk-K734DJB7.mjs → chunk-6POOFFYF.mjs} +7 -7
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-RNQDU7IQ.mjs → chunk-TRR3C5NX.mjs} +443 -490
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +161 -303
- 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 -7
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +2 -2
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +3 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +70 -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.d.ts +5 -2
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +5 -6
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -5
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -3
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -2
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/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 -3
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +3 -3
- 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 +4 -4
- 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 +2 -2
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +3 -3
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +6 -6
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +5 -6
- 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 +52 -51
- package/src/components/Table/DynamicTable.stories.tsx +11 -10
- package/src/components/Table/DynamicTable.tsx +4 -7
- package/src/components/Table/Relations.stories.tsx +127 -18
- package/src/components/Table/RowActionsMenu.tsx +21 -13
- package/src/components/Table/Table.stories.tsx +44 -43
- package/src/components/Table/Table.tsx +13 -9
- package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +19 -46
- package/src/components/TableCellEditor/TableCellEditor.tsx +7 -94
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +32 -22
- package/src/components/testing.ts +2 -2
- package/src/hooks/useAddRow.ts +1 -1
- package/src/hooks/useTableModel.ts +5 -5
- package/src/model/modal-controller.ts +0 -28
- package/src/model/table-model.test.ts +12 -7
- package/src/model/table-model.ts +12 -8
- package/src/model/table-presentation.test.ts +15 -18
- package/src/model/table-presentation.ts +4 -5
- package/src/model/table-sorting.ts +9 -9
- package/src/playwright/TableManager.ts +1 -1
- package/src/playwright/smoke.spec.ts +7 -5
- package/src/testing/useTestTableModel.ts +7 -5
- package/src/types/schema.ts +8 -8
- package/src/util/dynamic-table.ts +9 -11
- package/src/util/schema.ts +4 -4
- package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
- package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +0 -7
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -103
|
@@ -2,13 +2,11 @@
|
|
|
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 {
|
|
9
|
+
import { Obj, type QueryAST, Type } from '@dxos/echo';
|
|
12
10
|
import {
|
|
13
11
|
EchoObject,
|
|
14
12
|
FormatAnnotation,
|
|
@@ -16,17 +14,18 @@ import {
|
|
|
16
14
|
GeneratorAnnotation,
|
|
17
15
|
LabelAnnotation,
|
|
18
16
|
PropertyMetaAnnotationId,
|
|
19
|
-
} from '@dxos/echo
|
|
17
|
+
} from '@dxos/echo/internal';
|
|
18
|
+
import { live } from '@dxos/echo/internal';
|
|
20
19
|
import { invariant } from '@dxos/invariant';
|
|
21
20
|
import { faker } from '@dxos/random';
|
|
22
21
|
import { PublicKey } from '@dxos/react-client';
|
|
23
|
-
import { type Space
|
|
22
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
24
23
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
24
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
25
25
|
import { ViewEditor } from '@dxos/react-ui-form';
|
|
26
26
|
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
27
|
-
import {
|
|
27
|
+
import { View, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
|
|
28
28
|
import { Testing, createObjectFactory } from '@dxos/schema/testing';
|
|
29
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
30
29
|
|
|
31
30
|
import { useTestTableModel } from '../../testing';
|
|
32
31
|
import { translations } from '../../translations';
|
|
@@ -71,18 +70,18 @@ const StoryViewEditor = ({
|
|
|
71
70
|
space,
|
|
72
71
|
handleDeleteColumn,
|
|
73
72
|
}: {
|
|
74
|
-
view?:
|
|
73
|
+
view?: View.View;
|
|
75
74
|
schema?: Schema.Schema.AnyNoContext;
|
|
76
75
|
space?: Space;
|
|
77
76
|
handleDeleteColumn: (fieldId: string) => void;
|
|
78
77
|
}) => {
|
|
79
78
|
const handleQueryChanged = useCallback(
|
|
80
|
-
(
|
|
79
|
+
(newQuery: QueryAST.Query) => {
|
|
81
80
|
invariant(schema);
|
|
82
81
|
invariant(Type.isMutable(schema));
|
|
83
|
-
schema.updateTypename(
|
|
82
|
+
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
84
83
|
invariant(view);
|
|
85
|
-
view.query =
|
|
84
|
+
view.query.ast = newQuery;
|
|
86
85
|
},
|
|
87
86
|
[schema, view],
|
|
88
87
|
);
|
|
@@ -139,7 +138,7 @@ const DefaultStory = () => {
|
|
|
139
138
|
/>
|
|
140
139
|
</TableComponent.Root>
|
|
141
140
|
</div>
|
|
142
|
-
<div className='flex flex-col
|
|
141
|
+
<div className='flex flex-col bs-full border-l border-separator overflow-y-auto'>
|
|
143
142
|
<StoryViewEditor view={view} schema={schema} space={space} handleDeleteColumn={handleDeleteColumn} />
|
|
144
143
|
<SyntaxHighlighter language='json' className='text-xs'>
|
|
145
144
|
{JSON.stringify({ view, schema }, null, 2)}
|
|
@@ -160,26 +159,18 @@ type StoryProps = { rows?: number };
|
|
|
160
159
|
const meta = {
|
|
161
160
|
title: 'ui/react-ui-table/Table',
|
|
162
161
|
render: DefaultStory,
|
|
163
|
-
parameters: {
|
|
164
|
-
translations,
|
|
165
|
-
layout: 'fullscreen',
|
|
166
|
-
controls: {
|
|
167
|
-
disable: true,
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
162
|
decorators: [
|
|
171
163
|
withTheme,
|
|
172
|
-
withLayout({ fullscreen: true }),
|
|
173
164
|
withClientProvider({
|
|
174
|
-
types: [
|
|
165
|
+
types: [View.View, Table.Table],
|
|
175
166
|
createIdentity: true,
|
|
176
167
|
createSpace: true,
|
|
177
|
-
|
|
168
|
+
onCreateSpace: async ({ client, space }) => {
|
|
178
169
|
const [schema] = await space.db.schemaRegistry.register([TestSchema]);
|
|
179
170
|
const { view } = await Table.makeView({ client, space, typename: schema.typename });
|
|
180
171
|
view.projection.fields = [
|
|
181
|
-
view.projection.fields.find((field) => field.path === 'name')!,
|
|
182
|
-
...view.projection.fields.filter((field) => field.path !== 'name'),
|
|
172
|
+
view.projection.fields.find((field: any) => field.path === 'name')!,
|
|
173
|
+
...view.projection.fields.filter((field: any) => field.path !== 'name'),
|
|
183
174
|
];
|
|
184
175
|
|
|
185
176
|
space.db.add(view);
|
|
@@ -196,6 +187,13 @@ const meta = {
|
|
|
196
187
|
},
|
|
197
188
|
}),
|
|
198
189
|
],
|
|
190
|
+
parameters: {
|
|
191
|
+
layout: 'fullscreen',
|
|
192
|
+
controls: {
|
|
193
|
+
disable: true,
|
|
194
|
+
},
|
|
195
|
+
translations,
|
|
196
|
+
},
|
|
199
197
|
} satisfies Meta<typeof TableComponent>;
|
|
200
198
|
|
|
201
199
|
export default meta;
|
|
@@ -206,26 +204,27 @@ export const Default: Story = {};
|
|
|
206
204
|
|
|
207
205
|
export const StaticSchema: StoryObj = {
|
|
208
206
|
render: DefaultStory,
|
|
209
|
-
parameters: { translations },
|
|
210
207
|
decorators: [
|
|
211
208
|
withClientProvider({
|
|
212
|
-
types: [
|
|
209
|
+
types: [View.View, Table.Table],
|
|
213
210
|
createIdentity: true,
|
|
214
211
|
createSpace: true,
|
|
215
|
-
|
|
216
|
-
const { view } = await Table.makeView({ client, space, typename: Testing.
|
|
212
|
+
onCreateSpace: async ({ client, space }) => {
|
|
213
|
+
const { view } = await Table.makeView({ client, space, typename: Testing.Person.typename });
|
|
217
214
|
space.db.add(view);
|
|
218
215
|
|
|
219
216
|
const factory = createObjectFactory(space.db, faker as any);
|
|
220
217
|
await factory([
|
|
221
|
-
{ type: Testing.
|
|
218
|
+
{ type: Testing.Person, count: 10 },
|
|
222
219
|
// { type: Testing.Organization, count: 1 },
|
|
223
220
|
]);
|
|
224
221
|
},
|
|
225
222
|
}),
|
|
226
|
-
withLayout({ fullscreen: true }),
|
|
227
|
-
withTheme,
|
|
228
223
|
],
|
|
224
|
+
parameters: {
|
|
225
|
+
layout: 'fullscreen',
|
|
226
|
+
translations,
|
|
227
|
+
},
|
|
229
228
|
};
|
|
230
229
|
|
|
231
230
|
const ContactWithArrayOfEmails = Schema.Struct({
|
|
@@ -247,39 +246,39 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
247
246
|
|
|
248
247
|
export const ArrayOfObjects: StoryObj = {
|
|
249
248
|
render: DefaultStory,
|
|
250
|
-
parameters: { translations },
|
|
251
249
|
decorators: [
|
|
252
250
|
withClientProvider({
|
|
253
|
-
types: [
|
|
251
|
+
types: [View.View, Table.Table, Testing.Person, Testing.Organization, ContactWithArrayOfEmails],
|
|
254
252
|
createIdentity: true,
|
|
255
253
|
createSpace: true,
|
|
256
|
-
|
|
254
|
+
onCreateSpace: async ({ client, space }) => {
|
|
257
255
|
const { view } = await Table.makeView({ client, space, typename: ContactWithArrayOfEmails.typename });
|
|
258
256
|
space.db.add(view);
|
|
259
257
|
|
|
260
258
|
const factory = createObjectFactory(space.db, faker as any);
|
|
261
259
|
await factory([
|
|
262
|
-
// { type: Testing.
|
|
260
|
+
// { type: Testing.Person, count: 10 },
|
|
263
261
|
// { type: Testing.Organization, count: 1 },
|
|
264
262
|
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
265
263
|
]);
|
|
266
264
|
},
|
|
267
265
|
}),
|
|
268
|
-
withLayout({ fullscreen: true }),
|
|
269
|
-
withTheme,
|
|
270
266
|
],
|
|
267
|
+
parameters: {
|
|
268
|
+
layout: 'fullscreen',
|
|
269
|
+
translations,
|
|
270
|
+
},
|
|
271
271
|
};
|
|
272
272
|
|
|
273
273
|
export const Tags: Meta<StoryProps> = {
|
|
274
274
|
title: 'ui/react-ui-table/Table',
|
|
275
275
|
render: DefaultStory,
|
|
276
|
-
parameters: { translations },
|
|
277
276
|
decorators: [
|
|
278
277
|
withClientProvider({
|
|
279
|
-
types: [
|
|
278
|
+
types: [View.View, Table.Table],
|
|
280
279
|
createIdentity: true,
|
|
281
280
|
createSpace: true,
|
|
282
|
-
|
|
281
|
+
onCreateSpace: async ({ client, space }) => {
|
|
283
282
|
// Configure schema.
|
|
284
283
|
const typename = 'example.com/SingleSelect';
|
|
285
284
|
const selectOptions = [
|
|
@@ -321,7 +320,9 @@ export const Tags: Meta<StoryProps> = {
|
|
|
321
320
|
});
|
|
322
321
|
},
|
|
323
322
|
}),
|
|
324
|
-
withLayout({ fullscreen: true }),
|
|
325
|
-
withTheme,
|
|
326
323
|
],
|
|
324
|
+
parameters: {
|
|
325
|
+
layout: 'fullscreen',
|
|
326
|
+
translations,
|
|
327
|
+
},
|
|
327
328
|
};
|
|
@@ -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 { FormatEnum, Ref, type TypeAnnotation, getValue } from '@dxos/echo/internal';
|
|
10
10
|
import { invariant } from '@dxos/invariant';
|
|
11
11
|
import { getSnapshot } from '@dxos/live-object';
|
|
12
12
|
import { type Client } from '@dxos/react-client';
|
|
13
13
|
import { getSpace } from '@dxos/react-client/echo';
|
|
14
14
|
import { Popover } from '@dxos/react-ui';
|
|
15
15
|
import { Form, type FormProps } from '@dxos/react-ui-form';
|
|
16
|
-
import {
|
|
16
|
+
import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
17
17
|
import { type FieldProjection } from '@dxos/schema';
|
|
18
|
-
import { getDeep,
|
|
18
|
+
import { getDeep, isTruthy, setDeep } from '@dxos/util';
|
|
19
19
|
|
|
20
20
|
import { type ModalController, type TableModel } from '../../model';
|
|
21
21
|
import { translationKey } from '../../translations';
|
|
22
22
|
import { narrowSchema } from '../../util';
|
|
23
23
|
|
|
24
|
-
type
|
|
24
|
+
export type OnCreateHandler = (schema: Schema.Schema.AnyNoContext, values: any) => Parameters<typeof Ref.make>[0];
|
|
25
|
+
|
|
26
|
+
export type FormCellEditorProps = {
|
|
25
27
|
fieldProjection: FieldProjection;
|
|
26
28
|
model?: TableModel;
|
|
27
29
|
schema?: Schema.Schema.AnyNoContext;
|
|
28
30
|
onSave?: () => void;
|
|
31
|
+
onCreate?: OnCreateHandler;
|
|
29
32
|
client?: Client;
|
|
30
33
|
modals?: ModalController;
|
|
31
34
|
__gridScope: any;
|
|
32
|
-
} & Omit<FormProps<any>, 'values' | 'schema'>;
|
|
35
|
+
} & Omit<FormProps<any>, 'values' | 'schema' | 'onCreate'>;
|
|
33
36
|
|
|
34
37
|
const createOptionLabel = ['create new object label', { ns: translationKey }] as [string, { ns: string }];
|
|
35
38
|
|
|
@@ -40,6 +43,7 @@ export const FormCellEditor = ({
|
|
|
40
43
|
onSave,
|
|
41
44
|
client,
|
|
42
45
|
modals,
|
|
46
|
+
onCreate,
|
|
43
47
|
__gridScope,
|
|
44
48
|
...formProps
|
|
45
49
|
}: FormCellEditorProps) => {
|
|
@@ -48,7 +52,7 @@ export const FormCellEditor = ({
|
|
|
48
52
|
const anchorRef = useRef<HTMLButtonElement | null>(null);
|
|
49
53
|
|
|
50
54
|
const getSchema = useCallback(
|
|
51
|
-
(typeAnnotation: TypeAnnotation) => {
|
|
55
|
+
(typeAnnotation: Pick<TypeAnnotation, 'typename'>) => {
|
|
52
56
|
const space = getSpace(model!.view);
|
|
53
57
|
invariant(space);
|
|
54
58
|
|
|
@@ -76,7 +80,7 @@ export const FormCellEditor = ({
|
|
|
76
80
|
label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
|
|
77
81
|
};
|
|
78
82
|
})
|
|
79
|
-
.filter(
|
|
83
|
+
.filter(isTruthy);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
return [];
|
|
@@ -116,36 +120,6 @@ export const FormCellEditor = ({
|
|
|
116
120
|
[fieldProjection.field.path, onSave, contextEditing, originalRow],
|
|
117
121
|
);
|
|
118
122
|
|
|
119
|
-
const handleCreate = useCallback(
|
|
120
|
-
(object: any) => {
|
|
121
|
-
const ref = Ref.make(object);
|
|
122
|
-
const path = fieldProjection.field.path;
|
|
123
|
-
setDeep(originalRow, [path], ref);
|
|
124
|
-
contextEditing?.cellElement?.focus();
|
|
125
|
-
setEditing(null);
|
|
126
|
-
setLocalEditing(false);
|
|
127
|
-
onSave?.();
|
|
128
|
-
},
|
|
129
|
-
[fieldProjection.field.path, onSave, contextEditing, originalRow],
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
const handleCreateFromQuery = useCallback(
|
|
133
|
-
async (typeAnnotation: TypeAnnotation, query: string) => {
|
|
134
|
-
if (model && modals && contextEditing?.index) {
|
|
135
|
-
setLocalEditing(false);
|
|
136
|
-
modals.openCreateRef(
|
|
137
|
-
typeAnnotation.typename,
|
|
138
|
-
document.querySelector(cellQuery(contextEditing.index, gridId)),
|
|
139
|
-
{
|
|
140
|
-
[fieldProjection.field.referencePath!]: query,
|
|
141
|
-
},
|
|
142
|
-
handleCreate,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
[model, modals, client, contextEditing?.index, gridId, fieldProjection, handleSave],
|
|
147
|
-
);
|
|
148
|
-
|
|
149
123
|
useEffect(() => {
|
|
150
124
|
if (contextEditing && contextEditing.cellElement) {
|
|
151
125
|
anchorRef.current = (contextEditing.cellElement as HTMLElement).querySelector(
|
|
@@ -176,6 +150,38 @@ export const FormCellEditor = ({
|
|
|
176
150
|
setLocalEditing(nextOpen);
|
|
177
151
|
}, []);
|
|
178
152
|
|
|
153
|
+
const refSchema = useMemo(() => {
|
|
154
|
+
if (fieldProjection.props.format === FormatEnum.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>
|
|
@@ -2,23 +2,18 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import React
|
|
6
|
+
import React from 'react';
|
|
9
7
|
|
|
10
8
|
import { Obj } from '@dxos/echo';
|
|
11
|
-
import { type EchoSchema, isMutable } from '@dxos/echo-schema';
|
|
12
|
-
import { invariant } from '@dxos/invariant';
|
|
13
9
|
import { faker } from '@dxos/random';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
10
|
+
import { withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
16
12
|
import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
13
|
+
import { View } from '@dxos/schema';
|
|
14
|
+
import { Task } from '@dxos/types';
|
|
19
15
|
|
|
20
|
-
import {
|
|
21
|
-
import { type TableFeatures } from '../../model';
|
|
16
|
+
import { useTestTableModel } from '../../testing';
|
|
22
17
|
import { translations } from '../../translations';
|
|
23
18
|
import { Table } from '../../types';
|
|
24
19
|
|
|
@@ -29,39 +24,16 @@ type StoryProps = {
|
|
|
29
24
|
};
|
|
30
25
|
|
|
31
26
|
const DefaultStory = ({ editing }: StoryProps) => {
|
|
32
|
-
const {
|
|
33
|
-
invariant(space);
|
|
34
|
-
|
|
35
|
-
const views = useQuery(space, Filter.type(DataType.View));
|
|
36
|
-
const [view, setView] = useState<DataType.View>();
|
|
37
|
-
const [schema, setSchema] = useState<EchoSchema>();
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (space && views.length && !view) {
|
|
40
|
-
const view = views[0];
|
|
41
|
-
setView(view);
|
|
42
|
-
setSchema(space.db.schemaRegistry.getSchema(typenameFromQuery(view.query)!));
|
|
43
|
-
}
|
|
44
|
-
}, [space, views, view]);
|
|
45
|
-
|
|
46
|
-
const features: Partial<TableFeatures> = useMemo(
|
|
47
|
-
() => ({
|
|
48
|
-
selection: { enabled: true, mode: 'multiple' },
|
|
49
|
-
dataEditable: true,
|
|
50
|
-
schemaEditable: schema && isMutable(schema),
|
|
51
|
-
}),
|
|
52
|
-
[schema],
|
|
53
|
-
);
|
|
27
|
+
const { model, view } = useTestTableModel();
|
|
54
28
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!model || !schema || !view) {
|
|
29
|
+
if (!model || !view) {
|
|
58
30
|
return <div />;
|
|
59
31
|
}
|
|
60
32
|
|
|
61
33
|
return (
|
|
62
|
-
<div className='flex
|
|
34
|
+
<div className='flex is-[300px] border border-separator' style={{ height: defaultRowSize }}>
|
|
63
35
|
<Grid.Root id='test' editing={editing}>
|
|
64
|
-
<TableCellEditor model={model} schema={
|
|
36
|
+
<TableCellEditor model={model} schema={Task.Task} />
|
|
65
37
|
</Grid.Root>
|
|
66
38
|
</div>
|
|
67
39
|
);
|
|
@@ -71,19 +43,18 @@ const meta = {
|
|
|
71
43
|
title: 'ui/react-ui-table/TableCellEditor',
|
|
72
44
|
component: DefaultStory,
|
|
73
45
|
render: DefaultStory,
|
|
74
|
-
parameters: { translations, layout: 'centered' },
|
|
75
46
|
decorators: [
|
|
47
|
+
withTheme,
|
|
76
48
|
withClientProvider({
|
|
77
|
-
types: [
|
|
49
|
+
types: [View.View, Task.Task, Table.Table],
|
|
78
50
|
createIdentity: true,
|
|
79
51
|
createSpace: true,
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const { view } = await Table.makeView({ client, space, typename: schema.typename });
|
|
52
|
+
onCreateSpace: async ({ client, space }) => {
|
|
53
|
+
const { view } = await Table.makeView({ client, space, typename: Task.Task.typename });
|
|
83
54
|
space.db.add(view);
|
|
84
55
|
Array.from({ length: 10 }).forEach(() => {
|
|
85
56
|
space.db.add(
|
|
86
|
-
Obj.make(
|
|
57
|
+
Obj.make(Task.Task, {
|
|
87
58
|
title: faker.person.fullName(),
|
|
88
59
|
status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
89
60
|
description: faker.lorem.sentence(),
|
|
@@ -92,9 +63,11 @@ const meta = {
|
|
|
92
63
|
});
|
|
93
64
|
},
|
|
94
65
|
}),
|
|
95
|
-
withTheme,
|
|
96
|
-
withLayout(),
|
|
97
66
|
],
|
|
67
|
+
parameters: {
|
|
68
|
+
translations,
|
|
69
|
+
layout: 'centered',
|
|
70
|
+
},
|
|
98
71
|
} satisfies Meta<typeof DefaultStory>;
|
|
99
72
|
|
|
100
73
|
export default meta;
|