@dxos/react-ui-table 0.8.4-main.5ea62a8 → 0.8.4-main.ae835ea

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 (93) hide show
  1. package/dist/lib/browser/{chunk-63UUF4IG.mjs → chunk-4K7JPALT.mjs} +5 -5
  2. package/dist/lib/browser/chunk-4K7JPALT.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-RQYXNWWN.mjs → chunk-G7MGPBSR.mjs} +444 -490
  4. package/dist/lib/browser/chunk-G7MGPBSR.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +132 -283
  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 -6
  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-QNDUGIS7.mjs → chunk-LD5JEBA6.mjs} +444 -490
  12. package/dist/lib/node-esm/chunk-LD5JEBA6.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-K734DJB7.mjs → chunk-MK6OVUF6.mjs} +5 -5
  14. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +132 -283
  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 -6
  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 +71 -7
  26. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/Table.d.ts +5 -2
  28. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Table.stories.d.ts +5 -6
  30. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -5
  32. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  33. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -3
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -2
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  38. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  39. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  40. package/dist/types/src/components/testing.d.ts +3 -3
  41. package/dist/types/src/components/testing.d.ts.map +1 -1
  42. package/dist/types/src/hooks/useAddRow.d.ts +1 -1
  43. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useTableModel.d.ts +1 -1
  45. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  46. package/dist/types/src/model/modal-controller.d.ts +0 -7
  47. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  48. package/dist/types/src/model/table-model.d.ts +2 -2
  49. package/dist/types/src/model/table-model.d.ts.map +1 -1
  50. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  51. package/dist/types/src/model/table-sorting.d.ts +1 -2
  52. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  53. package/dist/types/src/testing/useTestTableModel.d.ts +3 -17
  54. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  55. package/dist/types/src/types/schema.d.ts +3 -3
  56. package/dist/types/src/types/schema.d.ts.map +1 -1
  57. package/dist/types/src/util/dynamic-table.d.ts +4 -5
  58. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  59. package/dist/types/src/util/schema.d.ts +1 -1
  60. package/dist/types/src/util/schema.d.ts.map +1 -1
  61. package/dist/types/tsconfig.tsbuildinfo +1 -1
  62. package/package.json +51 -51
  63. package/src/components/Table/DynamicTable.stories.tsx +8 -7
  64. package/src/components/Table/DynamicTable.tsx +4 -7
  65. package/src/components/Table/Relations.stories.tsx +119 -11
  66. package/src/components/Table/Table.stories.tsx +36 -35
  67. package/src/components/Table/Table.tsx +13 -9
  68. package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
  69. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +17 -45
  70. package/src/components/TableCellEditor/TableCellEditor.tsx +17 -112
  71. package/src/components/TableCellEditor/index.ts +1 -0
  72. package/src/components/TableToolbar/TableToolbar.tsx +3 -0
  73. package/src/components/testing.ts +2 -2
  74. package/src/hooks/useAddRow.ts +1 -1
  75. package/src/hooks/useTableModel.ts +1 -1
  76. package/src/model/modal-controller.ts +0 -28
  77. package/src/model/table-model.test.ts +16 -6
  78. package/src/model/table-model.ts +10 -3
  79. package/src/model/table-presentation.test.ts +19 -17
  80. package/src/model/table-presentation.ts +4 -5
  81. package/src/model/table-sorting.ts +15 -6
  82. package/src/playwright/smoke.spec.ts +3 -3
  83. package/src/testing/useTestTableModel.ts +7 -4
  84. package/src/types/schema.ts +4 -4
  85. package/src/util/dynamic-table.ts +8 -8
  86. package/src/util/schema.ts +4 -4
  87. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
  88. package/dist/lib/browser/chunk-RQYXNWWN.mjs.map +0 -7
  89. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
  90. package/dist/lib/node-esm/chunk-QNDUGIS7.mjs.map +0 -7
  91. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  92. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  93. package/src/components/Table/CreateRefPanel.tsx +0 -103
@@ -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 createSchema = useMemo(() => {
154
+ if (fieldProjection.props.format === FormatEnum.Ref && fieldProjection.props.referenceSchema) {
155
+ const { schema: refSchema } = getSchema({
156
+ typename: fieldProjection.props.referenceSchema,
157
+ });
158
+ if (!refSchema) {
159
+ return null;
160
+ }
161
+ const omit = Schema.omit<any, any, ['id']>('id');
162
+ return omit(refSchema);
163
+ }
164
+ return null;
165
+ }, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
166
+
167
+ const handleCreate = useCallback(
168
+ (values: any) => {
169
+ if (schema && onCreate) {
170
+ const objectWithId = onCreate(schema, 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, createSchema, 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,17 @@
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 } from '@dxos/schema';
18
- import { withLayout, withTheme } from '@dxos/storybook-utils';
13
+ import { DataType } from '@dxos/schema';
19
14
 
20
- import { useTableModel } from '../../hooks';
21
- import { type TableFeatures } from '../../model';
15
+ import { useTestTableModel } from '../../testing';
22
16
  import { translations } from '../../translations';
23
17
  import { Table } from '../../types';
24
18
 
@@ -29,39 +23,16 @@ type StoryProps = {
29
23
  };
30
24
 
31
25
  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(view.query.typename!));
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
- );
26
+ const { model, view } = useTestTableModel();
54
27
 
55
- const model = useTableModel({ view, schema: schema?.jsonSchema, features });
56
-
57
- if (!model || !schema || !view) {
28
+ if (!model || !view) {
58
29
  return <div />;
59
30
  }
60
31
 
61
32
  return (
62
33
  <div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
63
34
  <Grid.Root id='test' editing={editing}>
64
- <TableCellEditor model={model} schema={schema} />
35
+ <TableCellEditor model={model} schema={DataType.Task} />
65
36
  </Grid.Root>
66
37
  </div>
67
38
  );
@@ -71,19 +42,18 @@ const meta = {
71
42
  title: 'ui/react-ui-table/TableCellEditor',
72
43
  component: DefaultStory,
73
44
  render: DefaultStory,
74
- parameters: { translations, layout: 'centered' },
75
45
  decorators: [
46
+ withTheme,
76
47
  withClientProvider({
77
- types: [DataType.View, Table.Table],
48
+ types: [DataType.View, DataType.Task, Table.Table],
78
49
  createIdentity: true,
79
50
  createSpace: true,
80
- onSpaceCreated: async ({ client, space }) => {
81
- const schema = createDefaultSchema();
82
- const { view } = await Table.makeView({ client, space, typename: schema.typename });
51
+ onCreateSpace: async ({ client, space }) => {
52
+ const { view } = await Table.makeView({ client, space, typename: DataType.Task.typename });
83
53
  space.db.add(view);
84
54
  Array.from({ length: 10 }).forEach(() => {
85
55
  space.db.add(
86
- Obj.make(schema, {
56
+ Obj.make(DataType.Task, {
87
57
  title: faker.person.fullName(),
88
58
  status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
89
59
  description: faker.lorem.sentence(),
@@ -92,9 +62,11 @@ const meta = {
92
62
  });
93
63
  },
94
64
  }),
95
- withTheme,
96
- withLayout(),
97
65
  ],
66
+ parameters: {
67
+ translations,
68
+ layout: 'centered',
69
+ },
98
70
  } satisfies Meta<typeof DefaultStory>;
99
71
 
100
72
  export default meta;
@@ -4,16 +4,14 @@
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
6
  import { EditorView } from '@codemirror/view';
7
- import { type Schema } from 'effect/Schema';
7
+ import type * as Schema from 'effect/Schema';
8
8
  import React, { useCallback, useMemo, useRef, useState } from 'react';
9
9
 
10
10
  import { debounce } from '@dxos/async';
11
11
  import type { Client } from '@dxos/client';
12
- import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
12
+ import { FormatEnum, TypeEnum } from '@dxos/echo/internal';
13
13
  import { invariant } from '@dxos/invariant';
14
14
  import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
15
- import { useThemeContext } from '@dxos/react-ui';
16
- import { createMarkdownExtensions } from '@dxos/react-ui-editor';
17
15
  import {
18
16
  type EditorBlurHandler,
19
17
  type EditorKeyEvent,
@@ -25,13 +23,12 @@ import {
25
23
  parseCellIndex,
26
24
  useGridContext,
27
25
  } from '@dxos/react-ui-grid';
28
- import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
29
26
  import { type FieldProjection } from '@dxos/schema';
30
27
 
31
28
  import { type ModalController, type TableModel } from '../../model';
32
29
 
33
30
  import { CellValidationMessage } from './CellValidationMessage';
34
- import { FormCellEditor } from './FormCellEditor';
31
+ import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
35
32
 
36
33
  /**
37
34
  * Option to create new object/value.
@@ -41,8 +38,9 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
41
38
  export type TableCellEditorProps = {
42
39
  model?: TableModel;
43
40
  modals?: ModalController;
44
- schema?: Schema.AnyNoContext;
41
+ schema?: Schema.Schema.AnyNoContext;
45
42
  onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
43
+ onCreate?: OnCreateHandler;
46
44
  onSave?: () => void;
47
45
  client?: Client;
48
46
  };
@@ -56,6 +54,7 @@ export const TableValueEditor = ({
56
54
  schema,
57
55
  onFocus,
58
56
  onSave,
57
+ onCreate,
59
58
  client,
60
59
  __gridScope,
61
60
  }: GridScopedProps<TableCellEditorProps>) => {
@@ -86,6 +85,7 @@ export const TableValueEditor = ({
86
85
  schema={schema}
87
86
  __gridScope={__gridScope}
88
87
  onSave={onSave}
88
+ onCreate={onCreate}
89
89
  client={client}
90
90
  modals={modals}
91
91
  />
@@ -96,7 +96,7 @@ export const TableValueEditor = ({
96
96
  return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
97
97
  };
98
98
 
99
- const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
99
+ const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
100
100
 
101
101
  export const TableCellEditor = ({
102
102
  model,
@@ -105,9 +105,8 @@ export const TableCellEditor = ({
105
105
  onSave,
106
106
  __gridScope,
107
107
  }: GridScopedProps<TableCellEditorProps>) => {
108
- const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
108
+ const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
109
109
  const suppressNextBlur = useRef(false);
110
- const { themeMode } = useThemeContext();
111
110
  const [validationError, setValidationError] = useState<string | null>(null);
112
111
  const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
113
112
 
@@ -174,7 +173,6 @@ export const TableCellEditor = ({
174
173
  }
175
174
 
176
175
  const cell = parseCellIndex(editing.index);
177
-
178
176
  if (value !== undefined) {
179
177
  // Pre-commit validation check.
180
178
  const result = await model.validateCellData(cell, value);
@@ -205,71 +203,26 @@ export const TableCellEditor = ({
205
203
  [model, editing, onFocus, fieldProjection, setEditing, onSave],
206
204
  );
207
205
 
208
- const extension = useMemo(() => {
206
+ const extensions = useMemo(() => {
209
207
  if (!fieldProjection) {
210
208
  return [];
211
209
  }
212
210
 
213
- const extension = [
211
+ const extensions = [
214
212
  editorKeys({
215
213
  onClose: handleClose,
216
214
  ...(editing?.initialContent && { onNav: handleClose }),
217
215
  }),
218
216
  ];
219
-
220
- const format = fieldProjection.props.format;
221
-
222
- if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
223
- // TODO(ZaymonFC): Reconcile this with the TagPicker component?
224
- // Add markdown extensions needed by tag picker.
225
- extension.push(createMarkdownExtensions({ themeMode }));
226
-
227
- const options = fieldProjection.props.options || [];
228
-
229
- const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
230
-
231
- extension.push(
232
- tagPicker({
233
- mode,
234
- keymap: false,
235
- onSearch: (text, selectedIds) => {
236
- return options
237
- .filter(
238
- (option) =>
239
- selectedIds.indexOf(option.id) === -1 &&
240
- (text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase())),
241
- )
242
- .map((option) => ({
243
- id: option.id,
244
- label: option.title,
245
- hue: option.color as any,
246
- }));
247
- },
248
- onUpdate: (ids) => {
249
- if (model && editing) {
250
- if (ids.length === 0) {
251
- return;
252
- }
253
- if (mode === 'single-select') {
254
- void handleEnter(ids[0]);
255
- } else {
256
- void handleEnter(ids);
257
- }
258
- }
259
- },
260
- }),
261
- );
262
- }
263
-
264
- // Add validation extension to handle content changes
217
+ // Add validation extension to handle content changes.
265
218
  if (model && editing) {
266
- extension.push(
219
+ extensions.push(
267
220
  EditorView.updateListener.of(
268
221
  debounce((update) => {
269
222
  const content = update.state.doc.toString();
270
223
  const cell = parseCellIndex(editing.index);
271
224
 
272
- // Perform validation on content change
225
+ // Perform validation on content change.
273
226
  void model.validateCellData(cell, content).then((result) => {
274
227
  if (result.valid) {
275
228
  setValidationError(null);
@@ -283,60 +236,12 @@ export const TableCellEditor = ({
283
236
  );
284
237
  }
285
238
 
286
- return extension;
287
- }, [model, modals, editing, fieldProjection, handleClose, themeMode]);
239
+ return extensions;
240
+ }, [model, modals, editing, fieldProjection, handleClose]);
288
241
 
289
242
  const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
290
243
  if (model && editing) {
291
244
  const cell = parseCellIndex(editing.index);
292
- const { col } = cell;
293
- const field = model.projection.fields[col];
294
- const fieldProjection = model.projection.getFieldProjection(field.id);
295
-
296
- if (
297
- fieldProjection?.props.format === FormatEnum.SingleSelect ||
298
- fieldProjection?.props.format === FormatEnum.MultiSelect
299
- ) {
300
- const value = model.getCellData(cell);
301
-
302
- if (value !== undefined) {
303
- const options = fieldProjection.props.options || [];
304
-
305
- if (fieldProjection.props.format === FormatEnum.MultiSelect) {
306
- const tagItems = value
307
- .split(',')
308
- .map((id: string) => {
309
- const option = options.find((o) => o.id === id);
310
- if (option) {
311
- return {
312
- id,
313
- label: option.title,
314
- hue: option.color as any,
315
- };
316
- }
317
- return undefined;
318
- })
319
- .filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
320
-
321
- return createLinks(tagItems);
322
- } else {
323
- const option = options.find((o) => o.id === value);
324
-
325
- if (option) {
326
- const tagItem = {
327
- id: value,
328
- label: option.title,
329
- hue: option.color as any,
330
- };
331
-
332
- return createLinks([tagItem]);
333
- }
334
- }
335
- }
336
-
337
- return '';
338
- }
339
-
340
245
  const value = model.getCellData(cell);
341
246
  return value !== undefined ? String(value) : '';
342
247
  }
@@ -345,7 +250,7 @@ export const TableCellEditor = ({
345
250
  return (
346
251
  <>
347
252
  <CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
348
- <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
253
+ <GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
349
254
  </>
350
255
  );
351
256
  };
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './TableCellEditor';
6
+ export { type OnCreateHandler } from './FormCellEditor';
@@ -30,6 +30,8 @@ export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
30
30
 
31
31
  type TableToolbarState = Partial<{ viewDirty: boolean }>;
32
32
 
33
+ // TODO(burdon): Radix style layout.
34
+
33
35
  export type TableToolbarProps = ThemedClassName<
34
36
  TableToolbarState & {
35
37
  onAdd: () => void;
@@ -73,6 +75,7 @@ const createTableToolbarActions = ({
73
75
  nodes.push(...custom.nodes);
74
76
  edges.push(...custom.edges);
75
77
  }
78
+
76
79
  return {
77
80
  nodes,
78
81
  edges,
@@ -2,10 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
  import { useEffect } from 'react';
7
7
 
8
- import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo-schema';
8
+ import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
9
9
  import { live } from '@dxos/live-object';
10
10
  import { faker } from '@dxos/random';
11
11
  import { type ProjectionModel } from '@dxos/schema';
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect';
5
+ import type * as Schema from 'effect/Schema';
6
6
  import { useCallback } from 'react';
7
7
 
8
8
  import { type Space } from '@dxos/client/echo';
@@ -6,7 +6,7 @@ import { effect } from '@preact/signals-core';
6
6
  import orderBy from 'lodash.orderby';
7
7
  import { useEffect, useMemo, useState } from 'react';
8
8
 
9
- import { type JsonSchemaType } from '@dxos/echo-schema';
9
+ import { type JsonSchemaType } from '@dxos/echo/internal';
10
10
  import { type Live } from '@dxos/live-object';
11
11
  import { fullyQualifiedId } from '@dxos/react-client/echo';
12
12
  import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';