@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.
Files changed (97) hide show
  1. package/dist/lib/browser/{chunk-63UUF4IG.mjs → chunk-2PFAPQL4.mjs} +7 -7
  2. package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-BBXDTWCX.mjs → chunk-GS2UNGJK.mjs} +443 -490
  4. package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +161 -303
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +9 -7
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/browser/types/index.mjs +1 -1
  11. package/dist/lib/node-esm/{chunk-K734DJB7.mjs → chunk-6POOFFYF.mjs} +7 -7
  12. package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-RNQDU7IQ.mjs → chunk-TRR3C5NX.mjs} +443 -490
  14. package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +161 -303
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +9 -7
  19. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/types/index.mjs +1 -1
  21. package/dist/types/src/components/Table/DynamicTable.d.ts +2 -2
  22. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +3 -3
  24. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/Relations.stories.d.ts +70 -7
  26. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  28. package/dist/types/src/components/Table/Table.d.ts +5 -2
  29. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  30. package/dist/types/src/components/Table/Table.stories.d.ts +5 -6
  31. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -5
  33. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -3
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -2
  37. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  39. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  40. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
  41. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  42. package/dist/types/src/components/testing.d.ts +3 -3
  43. package/dist/types/src/components/testing.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useAddRow.d.ts +1 -1
  45. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useTableModel.d.ts +3 -3
  47. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  48. package/dist/types/src/model/modal-controller.d.ts +0 -7
  49. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  50. package/dist/types/src/model/table-model.d.ts +4 -4
  51. package/dist/types/src/model/table-model.d.ts.map +1 -1
  52. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  53. package/dist/types/src/model/table-sorting.d.ts +2 -2
  54. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  55. package/dist/types/src/testing/useTestTableModel.d.ts +3 -3
  56. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  57. package/dist/types/src/types/schema.d.ts +6 -6
  58. package/dist/types/src/types/schema.d.ts.map +1 -1
  59. package/dist/types/src/util/dynamic-table.d.ts +5 -6
  60. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  61. package/dist/types/src/util/schema.d.ts +1 -1
  62. package/dist/types/src/util/schema.d.ts.map +1 -1
  63. package/dist/types/tsconfig.tsbuildinfo +1 -1
  64. package/package.json +52 -51
  65. package/src/components/Table/DynamicTable.stories.tsx +11 -10
  66. package/src/components/Table/DynamicTable.tsx +4 -7
  67. package/src/components/Table/Relations.stories.tsx +127 -18
  68. package/src/components/Table/RowActionsMenu.tsx +21 -13
  69. package/src/components/Table/Table.stories.tsx +44 -43
  70. package/src/components/Table/Table.tsx +13 -9
  71. package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
  72. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +19 -46
  73. package/src/components/TableCellEditor/TableCellEditor.tsx +7 -94
  74. package/src/components/TableCellEditor/index.ts +1 -0
  75. package/src/components/TableToolbar/TableToolbar.tsx +32 -22
  76. package/src/components/testing.ts +2 -2
  77. package/src/hooks/useAddRow.ts +1 -1
  78. package/src/hooks/useTableModel.ts +5 -5
  79. package/src/model/modal-controller.ts +0 -28
  80. package/src/model/table-model.test.ts +12 -7
  81. package/src/model/table-model.ts +12 -8
  82. package/src/model/table-presentation.test.ts +15 -18
  83. package/src/model/table-presentation.ts +4 -5
  84. package/src/model/table-sorting.ts +9 -9
  85. package/src/playwright/TableManager.ts +1 -1
  86. package/src/playwright/smoke.spec.ts +7 -5
  87. package/src/testing/useTestTableModel.ts +7 -5
  88. package/src/types/schema.ts +8 -8
  89. package/src/util/dynamic-table.ts +9 -11
  90. package/src/util/schema.ts +4 -4
  91. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
  92. package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +0 -7
  93. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
  94. package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +0 -7
  95. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  96. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  97. 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 { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
9
7
  import React, { useCallback } from 'react';
10
8
 
11
- import { Filter, Obj, Query, Type } from '@dxos/echo';
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-schema';
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, live } from '@dxos/react-client/echo';
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 { DataType, getSchemaFromPropertyDefinitions } from '@dxos/schema';
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?: DataType.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
- (typename: string) => {
79
+ (newQuery: QueryAST.Query) => {
81
80
  invariant(schema);
82
81
  invariant(Type.isMutable(schema));
83
- schema.updateTypename(typename);
82
+ schema.updateTypename(getTypenameFromQuery(newQuery));
84
83
  invariant(view);
85
- view.query = Query.select(Filter.typename(typename)).ast;
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 h-full border-l border-separator overflow-y-auto'>
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: [DataType.View, Table.Table],
165
+ types: [View.View, Table.Table],
175
166
  createIdentity: true,
176
167
  createSpace: true,
177
- onSpaceCreated: async ({ client, space }) => {
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: [DataType.View, Table.Table, Testing.Contact, Testing.Organization],
209
+ types: [View.View, Table.Table],
213
210
  createIdentity: true,
214
211
  createSpace: true,
215
- onSpaceCreated: async ({ client, space }) => {
216
- const { view } = await Table.makeView({ client, space, typename: Testing.Contact.typename });
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.Contact, count: 10 },
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: [DataType.View, Table.Table, Testing.Contact, Testing.Organization, ContactWithArrayOfEmails],
251
+ types: [View.View, Table.Table, Testing.Person, Testing.Organization, ContactWithArrayOfEmails],
254
252
  createIdentity: true,
255
253
  createSpace: true,
256
- onSpaceCreated: async ({ client, space }) => {
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.Contact, count: 10 },
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: [DataType.View, Table.Table],
278
+ types: [View.View, Table.Table],
280
279
  createIdentity: true,
281
280
  createSpace: true,
282
- onSpaceCreated: async ({ client, space }) => {
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 { type Schema } from 'effect/Schema';
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 { type Schema } from 'effect';
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-schema';
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 { cellQuery, parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
16
+ import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
17
17
  import { type FieldProjection } from '@dxos/schema';
18
- import { getDeep, isNotFalsy, setDeep } from '@dxos/util';
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 FormCellEditorProps = {
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(isNotFalsy);
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
- onCreateFromQuery={handleCreateFromQuery}
198
- createOptionIcon='ph--plus--regular'
199
- createOptionLabel={createOptionLabel}
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, { useEffect, useMemo, useState } from '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 { Filter, useQuery } from '@dxos/react-client/echo';
15
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
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 { DataType, createDefaultSchema, typenameFromQuery } from '@dxos/schema';
18
- import { withLayout, withTheme } from '@dxos/storybook-utils';
13
+ import { View } from '@dxos/schema';
14
+ import { Task } from '@dxos/types';
19
15
 
20
- import { useTableModel } from '../../hooks';
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 { space } = useClientProvider();
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
- const model = useTableModel({ view, schema: schema?.jsonSchema, features });
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 w-[300px] border border-separator' style={{ height: defaultRowSize }}>
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={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: [DataType.View, Table.Table],
49
+ types: [View.View, Task.Task, Table.Table],
78
50
  createIdentity: true,
79
51
  createSpace: true,
80
- onSpaceCreated: async ({ client, space }) => {
81
- const schema = createDefaultSchema();
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(schema, {
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;