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

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 (177) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/chunk-4URYOFDL.mjs +69 -0
  3. package/dist/lib/browser/chunk-4URYOFDL.mjs.map +7 -0
  4. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  5. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  6. package/dist/lib/browser/chunk-YDXPC7HA.mjs +1695 -0
  7. package/dist/lib/browser/chunk-YDXPC7HA.mjs.map +7 -0
  8. package/dist/lib/browser/index.mjs +1139 -1198
  9. package/dist/lib/browser/index.mjs.map +4 -4
  10. package/dist/lib/browser/meta.json +1 -1
  11. package/dist/lib/browser/testing/index.mjs +35 -42
  12. package/dist/lib/browser/testing/index.mjs.map +3 -3
  13. package/dist/lib/browser/translations.mjs +36 -0
  14. package/dist/lib/browser/translations.mjs.map +7 -0
  15. package/dist/lib/browser/types/index.mjs +4 -3
  16. package/dist/lib/node-esm/chunk-2AN3QQ3D.mjs +1697 -0
  17. package/dist/lib/node-esm/chunk-2AN3QQ3D.mjs.map +7 -0
  18. package/dist/lib/node-esm/chunk-B2SZYAEC.mjs +70 -0
  19. package/dist/lib/node-esm/chunk-B2SZYAEC.mjs.map +7 -0
  20. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  21. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +1139 -1198
  23. package/dist/lib/node-esm/index.mjs.map +4 -4
  24. package/dist/lib/node-esm/meta.json +1 -1
  25. package/dist/lib/node-esm/testing/index.mjs +35 -42
  26. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  27. package/dist/lib/node-esm/translations.mjs +37 -0
  28. package/dist/lib/node-esm/translations.mjs.map +7 -0
  29. package/dist/lib/node-esm/types/index.mjs +4 -3
  30. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  31. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  32. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  33. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  34. package/dist/types/src/components/Table/DynamicTable.d.ts +6 -7
  35. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  36. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +23 -39
  37. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/Table/Relations.stories.d.ts +96 -29
  39. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  41. package/dist/types/src/components/Table/Table.d.ts +20 -18
  42. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  43. package/dist/types/src/components/Table/Table.stories.d.ts +96 -31
  44. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/Table/TableContent.d.ts +18 -0
  46. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
  47. package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
  48. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
  49. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  50. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  51. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -12
  52. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +7 -7
  54. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  55. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +25 -28
  56. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  58. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  59. package/dist/types/src/components/index.d.ts +0 -1
  60. package/dist/types/src/components/index.d.ts.map +1 -1
  61. package/dist/types/src/components/testing.d.ts +23 -16
  62. package/dist/types/src/components/testing.d.ts.map +1 -1
  63. package/dist/types/src/hooks/index.d.ts +2 -1
  64. package/dist/types/src/hooks/index.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  66. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  67. package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
  68. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  69. package/dist/types/src/hooks/useTableModel.d.ts +9 -9
  70. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  71. package/dist/types/src/index.d.ts +0 -2
  72. package/dist/types/src/index.d.ts.map +1 -1
  73. package/dist/types/src/model/modal-controller.d.ts +5 -8
  74. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  75. package/dist/types/src/model/selection-model.d.ts +18 -9
  76. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  77. package/dist/types/src/model/table-model.d.ts +105 -26
  78. package/dist/types/src/model/table-model.d.ts.map +1 -1
  79. package/dist/types/src/model/table-presentation.d.ts +3 -1
  80. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  81. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  82. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  83. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  84. package/dist/types/src/testing/useTestTableModel.d.ts +18 -29
  85. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  86. package/dist/types/src/translations.d.ts +21 -23
  87. package/dist/types/src/translations.d.ts.map +1 -1
  88. package/dist/types/src/types/Table.d.ts +25 -0
  89. package/dist/types/src/types/Table.d.ts.map +1 -0
  90. package/dist/types/src/types/index.d.ts +1 -1
  91. package/dist/types/src/types/index.d.ts.map +1 -1
  92. package/dist/types/src/util/dynamic-table.d.ts +14 -12
  93. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  94. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  95. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  96. package/dist/types/src/util/index.d.ts +2 -0
  97. package/dist/types/src/util/index.d.ts.map +1 -1
  98. package/dist/types/src/util/query-ast.d.ts +7 -0
  99. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  100. package/dist/types/src/util/schema.d.ts +1 -1
  101. package/dist/types/src/util/schema.d.ts.map +1 -1
  102. package/dist/types/src/util/signals.d.ts.map +1 -1
  103. package/dist/types/src/util/sort.d.ts +12 -0
  104. package/dist/types/src/util/sort.d.ts.map +1 -0
  105. package/dist/types/src/util/table-buttons.d.ts +20 -20
  106. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  107. package/dist/types/src/util/table-controls.d.ts +4 -4
  108. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  109. package/dist/types/src/util/tag.d.ts.map +1 -1
  110. package/dist/types/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +69 -78
  112. package/src/components/Table/ColumnActionsMenu.tsx +17 -12
  113. package/src/components/Table/ColumnSettings.tsx +8 -9
  114. package/src/components/Table/DynamicTable.stories.tsx +35 -34
  115. package/src/components/Table/DynamicTable.tsx +32 -36
  116. package/src/components/Table/Relations.stories.tsx +195 -48
  117. package/src/components/Table/RowActionsMenu.tsx +27 -17
  118. package/src/components/Table/Table.stories.tsx +145 -129
  119. package/src/components/Table/Table.tsx +43 -403
  120. package/src/components/Table/TableContent.tsx +425 -0
  121. package/src/components/Table/TableToolbar.tsx +111 -0
  122. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
  123. package/src/components/TableCellEditor/FormCellEditor.tsx +125 -131
  124. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +32 -56
  125. package/src/components/TableCellEditor/TableCellEditor.tsx +47 -140
  126. package/src/components/TableCellEditor/index.ts +1 -0
  127. package/src/components/index.ts +0 -1
  128. package/src/components/testing.ts +44 -18
  129. package/src/hooks/index.ts +2 -1
  130. package/src/hooks/useAddRow.ts +8 -10
  131. package/src/hooks/useProjectionModel.ts +39 -0
  132. package/src/hooks/useTableModel.ts +36 -34
  133. package/src/index.ts +0 -4
  134. package/src/model/modal-controller.ts +23 -40
  135. package/src/model/selection-model.ts +75 -44
  136. package/src/model/table-model.test.ts +93 -56
  137. package/src/model/table-model.ts +435 -163
  138. package/src/model/table-presentation.test.ts +67 -56
  139. package/src/model/table-presentation.ts +45 -42
  140. package/src/playwright/TableManager.ts +13 -6
  141. package/src/playwright/playwright.config.ts +1 -1
  142. package/src/playwright/smoke.spec.ts +59 -13
  143. package/src/testing/useTestTableModel.ts +49 -39
  144. package/src/translations.ts +20 -22
  145. package/src/types/Table.ts +78 -0
  146. package/src/types/index.ts +1 -1
  147. package/src/util/dynamic-table.test.ts +70 -0
  148. package/src/util/dynamic-table.ts +42 -30
  149. package/src/util/index.ts +2 -0
  150. package/src/util/query-ast.ts +22 -0
  151. package/src/util/schema.ts +4 -4
  152. package/src/util/sort.ts +90 -0
  153. package/src/util/table-buttons.ts +1 -1
  154. package/src/util/table-controls.ts +2 -2
  155. package/dist/lib/browser/chunk-63UUF4IG.mjs +0 -66
  156. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
  157. package/dist/lib/browser/chunk-RQYXNWWN.mjs +0 -1822
  158. package/dist/lib/browser/chunk-RQYXNWWN.mjs.map +0 -7
  159. package/dist/lib/node-esm/chunk-K734DJB7.mjs +0 -67
  160. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
  161. package/dist/lib/node-esm/chunk-QNDUGIS7.mjs +0 -1823
  162. package/dist/lib/node-esm/chunk-QNDUGIS7.mjs.map +0 -7
  163. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  164. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  165. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  166. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  167. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  168. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  169. package/dist/types/src/model/table-sorting.d.ts +0 -53
  170. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  171. package/dist/types/src/types/schema.d.ts +0 -28
  172. package/dist/types/src/types/schema.d.ts.map +0 -1
  173. package/src/components/Table/CreateRefPanel.tsx +0 -103
  174. package/src/components/TableToolbar/TableToolbar.tsx +0 -102
  175. package/src/components/TableToolbar/index.ts +0 -5
  176. package/src/model/table-sorting.ts +0 -216
  177. package/src/types/schema.ts +0 -73
@@ -4,16 +4,13 @@
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
6
  import { EditorView } from '@codemirror/view';
7
- import { type Schema } from 'effect/Schema';
8
7
  import React, { useCallback, useMemo, useRef, useState } from 'react';
9
8
 
10
9
  import { debounce } from '@dxos/async';
11
- import type { Client } from '@dxos/client';
12
- import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
10
+ import { type Type } from '@dxos/echo';
11
+ import { Format, TypeEnum } from '@dxos/echo/internal';
13
12
  import { invariant } from '@dxos/invariant';
14
13
  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
14
  import {
18
15
  type EditorBlurHandler,
19
16
  type EditorKeyEvent,
@@ -25,40 +22,37 @@ import {
25
22
  parseCellIndex,
26
23
  useGridContext,
27
24
  } from '@dxos/react-ui-grid';
28
- import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
29
25
  import { type FieldProjection } from '@dxos/schema';
30
26
 
31
27
  import { type ModalController, type TableModel } from '../../model';
32
-
33
28
  import { CellValidationMessage } from './CellValidationMessage';
34
- import { FormCellEditor } from './FormCellEditor';
29
+ import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
35
30
 
36
31
  /**
37
32
  * Option to create new object/value.
38
33
  */
39
- export type QueryResult = Pick<Completion, 'label'> & { data: any };
34
+ export type QueryResult = Pick<Completion, 'label'> & {
35
+ data: any;
36
+ };
40
37
 
41
- export type TableCellEditorProps = {
38
+ export type TableCellEditorProps<T extends Type.AnyEntity = Type.AnyEntity> = {
39
+ schema?: T;
42
40
  model?: TableModel;
43
41
  modals?: ModalController;
44
- schema?: Schema.AnyNoContext;
45
42
  onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
43
+ onCreate?: OnCreateHandler;
46
44
  onSave?: () => void;
47
- client?: Client;
48
45
  };
49
46
 
50
- const adaptValidationMessage = (message: string | null) =>
51
- message ? (message.endsWith('is missing') ? 'Can’t be blank' : message) : null;
52
-
53
- export const TableValueEditor = ({
47
+ export const TableValueEditor = <T extends Type.AnyEntity = Type.AnyEntity>({
48
+ __gridScope,
49
+ schema,
54
50
  model,
55
51
  modals,
56
- schema,
57
52
  onFocus,
58
53
  onSave,
59
- client,
60
- __gridScope,
61
- }: GridScopedProps<TableCellEditorProps>) => {
54
+ onCreate,
55
+ }: GridScopedProps<TableCellEditorProps<T>>) => {
62
56
  const { editing } = useGridContext('TableValueEditor', __gridScope);
63
57
 
64
58
  const fieldProjection = useMemo<FieldProjection | undefined>(() => {
@@ -67,7 +61,7 @@ export const TableValueEditor = ({
67
61
  }
68
62
 
69
63
  const { col } = parseCellIndex(editing.index);
70
- const field = model.projection.fields[col];
64
+ const field = model.projection.getFields()[col];
71
65
  const fieldProjection = model.projection.getFieldProjection(field.id);
72
66
  invariant(fieldProjection);
73
67
  return fieldProjection;
@@ -75,39 +69,36 @@ export const TableValueEditor = ({
75
69
 
76
70
  if (
77
71
  fieldProjection?.props.type === TypeEnum.Array ||
78
- fieldProjection?.props.format === FormatEnum.SingleSelect ||
79
- fieldProjection?.props.format === FormatEnum.Ref
80
- // TODO(thure): Support `FormatEnum.MultiSelect`
72
+ fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
73
+ fieldProjection?.props.format === Format.TypeFormat.Ref
74
+ // TODO(thure): Support `Format.TypeFormat.MultiSelect`
81
75
  ) {
82
76
  return (
83
- <FormCellEditor
84
- fieldProjection={fieldProjection}
85
- model={model}
86
- schema={schema}
77
+ <FormCellEditor<T>
87
78
  __gridScope={__gridScope}
88
- onSave={onSave}
89
- client={client}
79
+ schema={schema}
80
+ model={model}
81
+ fieldProjection={fieldProjection}
90
82
  modals={modals}
83
+ onSave={onSave}
84
+ onCreate={onCreate}
91
85
  />
92
86
  );
93
87
  }
94
88
 
95
- // For all other types, use the existing cell editor
89
+ // For all other types, use the existing cell editor.
96
90
  return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
97
91
  };
98
92
 
99
- const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
100
-
101
93
  export const TableCellEditor = ({
94
+ __gridScope,
102
95
  model,
103
96
  modals,
104
97
  onFocus,
105
98
  onSave,
106
- __gridScope,
107
99
  }: GridScopedProps<TableCellEditorProps>) => {
108
- const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
100
+ const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
109
101
  const suppressNextBlur = useRef(false);
110
- const { themeMode } = useThemeContext();
111
102
  const [validationError, setValidationError] = useState<string | null>(null);
112
103
  const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
113
104
 
@@ -117,12 +108,13 @@ export const TableCellEditor = ({
117
108
  }
118
109
 
119
110
  const { col } = parseCellIndex(editing.index);
120
- const field = model.projection.fields[col];
111
+ const field = model.projection.getFields()[col];
121
112
  const fieldProjection = model.projection.getFieldProjection(field.id);
122
113
  invariant(fieldProjection);
123
114
  return fieldProjection;
124
115
  }, [model, editing]);
125
116
 
117
+ // TOOD(burdon): Attach to event handler?
126
118
  const handleEnter = useCallback(
127
119
  async (value: any) => {
128
120
  if (!model || !editing) {
@@ -139,7 +131,7 @@ export const TableCellEditor = ({
139
131
  setEditing(null);
140
132
  onSave?.();
141
133
  } else {
142
- setValidationError(adaptValidationMessage(validationResult.error));
134
+ setValidationError(validationResult.error);
143
135
  setValidationVariant('error');
144
136
  }
145
137
  },
@@ -174,7 +166,6 @@ export const TableCellEditor = ({
174
166
  }
175
167
 
176
168
  const cell = parseCellIndex(editing.index);
177
-
178
169
  if (value !== undefined) {
179
170
  // Pre-commit validation check.
180
171
  const result = await model.validateCellData(cell, value);
@@ -205,71 +196,26 @@ export const TableCellEditor = ({
205
196
  [model, editing, onFocus, fieldProjection, setEditing, onSave],
206
197
  );
207
198
 
208
- const extension = useMemo(() => {
199
+ const extensions = useMemo(() => {
209
200
  if (!fieldProjection) {
210
201
  return [];
211
202
  }
212
203
 
213
- const extension = [
204
+ const extensions = [
214
205
  editorKeys({
215
206
  onClose: handleClose,
216
207
  ...(editing?.initialContent && { onNav: handleClose }),
217
208
  }),
218
209
  ];
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
210
+ // Add validation extension to handle content changes.
265
211
  if (model && editing) {
266
- extension.push(
212
+ extensions.push(
267
213
  EditorView.updateListener.of(
268
214
  debounce((update) => {
269
215
  const content = update.state.doc.toString();
270
216
  const cell = parseCellIndex(editing.index);
271
217
 
272
- // Perform validation on content change
218
+ // Perform validation on content change.
273
219
  void model.validateCellData(cell, content).then((result) => {
274
220
  if (result.valid) {
275
221
  setValidationError(null);
@@ -283,60 +229,12 @@ export const TableCellEditor = ({
283
229
  );
284
230
  }
285
231
 
286
- return extension;
287
- }, [model, modals, editing, fieldProjection, handleClose, themeMode]);
232
+ return extensions;
233
+ }, [model, modals, editing, fieldProjection, handleClose]);
288
234
 
289
235
  const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
290
236
  if (model && editing) {
291
237
  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
238
  const value = model.getCellData(cell);
341
239
  return value !== undefined ? String(value) : '';
342
240
  }
@@ -345,7 +243,16 @@ export const TableCellEditor = ({
345
243
  return (
346
244
  <>
347
245
  <CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
348
- <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
246
+ <GridCellEditor
247
+ extensions={extensions}
248
+ slots={{
249
+ content: {
250
+ className: '!py-(--dx-grid-cell-editor-padding-block)',
251
+ },
252
+ }}
253
+ getCellContent={getCellContent}
254
+ onBlur={handleBlur}
255
+ />
349
256
  </>
350
257
  );
351
258
  };
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './TableCellEditor';
6
+ export { type OnCreateHandler } from './FormCellEditor';
@@ -3,4 +3,3 @@
3
3
  //
4
4
 
5
5
  export * from './Table';
6
- export * from './TableToolbar';
@@ -2,33 +2,59 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import * as Schema from 'effect/Schema';
6
7
  import { useEffect } from 'react';
7
8
 
8
- import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo-schema';
9
- import { live } from '@dxos/live-object';
10
- import { faker } from '@dxos/random';
9
+ import { Format, Type } from '@dxos/echo';
10
+ import { TypeEnum } from '@dxos/echo/internal';
11
+ import { setValue } from '@dxos/effect';
12
+ import { random } from '@dxos/random';
11
13
  import { type ProjectionModel } from '@dxos/schema';
12
14
 
13
- export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
14
- id: ObjectId,
15
+ export const TestSchema = Schema.Struct({
15
16
  name: Schema.optional(Schema.String),
16
17
  age: Schema.optional(Schema.Number),
17
18
  active: Schema.optional(Schema.Boolean),
18
19
  netWorth: Schema.optional(Schema.Number),
19
- });
20
+ }).pipe(
21
+ Type.object({
22
+ typename: 'com.example.type.test',
23
+ version: '0.1.0',
24
+ }),
25
+ );
20
26
 
21
- export const createItems = (n: number) => {
22
- const { data } = live({
27
+ export type TestItem = {
28
+ name: string;
29
+ age: number;
30
+ active: boolean;
31
+ netWorth: number;
32
+ };
33
+
34
+ /**
35
+ * Creates an Atom containing test items data.
36
+ */
37
+ export const createItemsAtom = (n: number) => {
38
+ return Atom.make<{ data: TestItem[] }>({
23
39
  data: Array.from({ length: n }, () => ({
24
- name: faker.person.fullName(),
25
- age: faker.number.int({ min: 20, max: 70 }),
26
- active: faker.datatype.boolean(),
27
- netWorth: faker.number.int(),
40
+ name: random.person.fullName(),
41
+ age: random.number.int({ min: 20, max: 70 }),
42
+ active: random.datatype.boolean(),
43
+ netWorth: random.number.int(),
28
44
  })),
29
- });
45
+ }).pipe(Atom.keepAlive);
46
+ };
30
47
 
31
- return data;
48
+ /**
49
+ * Creates plain test items (non-reactive).
50
+ */
51
+ export const createItems = (n: number): TestItem[] => {
52
+ return Array.from({ length: n }, () => ({
53
+ name: random.person.fullName(),
54
+ age: random.number.int({ min: 20, max: 70 }),
55
+ active: random.datatype.boolean(),
56
+ netWorth: random.number.int(),
57
+ }));
32
58
  };
33
59
 
34
60
  export type SimulatorProps = {
@@ -59,7 +85,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
59
85
 
60
86
  const i = setInterval(() => {
61
87
  const rowIdx = Math.floor(Math.random() * items.length);
62
- const fields = projection.fields ?? [];
88
+ const fields = projection.getFields() ?? [];
63
89
  const columnIdx = Math.floor(Math.random() * fields.length);
64
90
  const field = fields[columnIdx];
65
91
  const item = items[rowIdx];
@@ -88,7 +114,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
88
114
 
89
115
  if (format) {
90
116
  switch (format) {
91
- case FormatEnum.Currency: {
117
+ case Format.TypeFormat.Currency: {
92
118
  item[path] = Math.floor(Math.random() * 1000);
93
119
  break;
94
120
  }
@@ -98,5 +124,5 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
98
124
  }, updateInterval);
99
125
 
100
126
  return () => clearInterval(i);
101
- }, [items, projection.fields, updateInterval]);
127
+ }, [items, projection.getFields(), updateInterval]);
102
128
  };
@@ -2,5 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './useTableModel';
6
5
  export * from './useAddRow';
6
+ export * from './useProjectionModel';
7
+ export * from './useTableModel';
@@ -2,17 +2,15 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect';
6
5
  import { useCallback } from 'react';
7
6
 
8
- import { type Space } from '@dxos/client/echo';
9
- import { Obj } from '@dxos/echo';
7
+ import { type Database, Obj, type Type } from '@dxos/echo';
10
8
 
11
9
  import { type InsertRowResult } from '../model';
12
10
 
13
- export type UseAddRowParams = {
14
- space?: Space;
15
- schema?: Schema.Schema.AnyNoContext;
11
+ export type UseAddRowProps = {
12
+ db?: Database.Database;
13
+ schema?: Type.AnyObj;
16
14
  };
17
15
 
18
16
  /**
@@ -20,12 +18,12 @@ export type UseAddRowParams = {
20
18
  * Returns a callback that attempts to create an object and returns boolean success/failure.
21
19
  * Can accept optional data to create objects with specific content (used for committing draft rows).
22
20
  */
23
- export const useAddRow = ({ space, schema }: UseAddRowParams) => {
21
+ export const useAddRow = ({ db, schema }: UseAddRowProps) => {
24
22
  return useCallback(
25
23
  (data?: any): InsertRowResult => {
26
- if (space && schema) {
24
+ if (db && schema) {
27
25
  try {
28
- space.db.add(Obj.make(schema, data ?? {}));
26
+ db.add(Obj.make(schema, data ?? {}));
29
27
  return 'final';
30
28
  } catch (error) {
31
29
  return 'draft';
@@ -33,6 +31,6 @@ export const useAddRow = ({ space, schema }: UseAddRowParams) => {
33
31
  }
34
32
  return 'final';
35
33
  },
36
- [space, schema],
34
+ [db, schema],
37
35
  );
38
36
  };
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Registry } from '@effect-atom/atom-react';
6
+ import { useState } from 'react';
7
+
8
+ import { JsonSchema, Type } from '@dxos/echo';
9
+ import { useAsyncEffect } from '@dxos/react-ui';
10
+ import { ProjectionModel, createEchoChangeCallback } from '@dxos/schema';
11
+
12
+ import { type Table } from '../types';
13
+
14
+ export const useProjectionModel = <S extends Type.AnyEntity>(
15
+ schema: S | undefined,
16
+ table: Table.Table | undefined,
17
+ registry: Registry.Registry,
18
+ ) => {
19
+ const [projection, setProjection] = useState<ProjectionModel | undefined>();
20
+
21
+ useAsyncEffect(async () => {
22
+ if (schema && table) {
23
+ const view = await table.view.load();
24
+ // For mutable schemas (EchoSchema), use the live jsonSchema reference for reactivity.
25
+ // For immutable schemas, create a snapshot.
26
+ const jsonSchema = Type.isMutable(schema) ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
27
+
28
+ // Always use createEchoChangeCallback since the view is ECHO-backed.
29
+ // Pass schema only when mutable to allow schema mutations.
30
+ const change = createEchoChangeCallback(view, Type.isMutable(schema) ? schema : undefined);
31
+
32
+ const projection = new ProjectionModel({ registry, view, baseSchema: jsonSchema, change });
33
+ projection.normalizeView();
34
+ setProjection(projection);
35
+ }
36
+ }, [schema, table, registry]);
37
+
38
+ return projection;
39
+ };
@@ -2,58 +2,65 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { effect } from '@preact/signals-core';
6
- import orderBy from 'lodash.orderby';
7
- import { useEffect, useMemo, useState } from 'react';
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import { useContext, useEffect, useMemo, useState } from 'react';
8
7
 
9
- import { type JsonSchemaType } from '@dxos/echo-schema';
10
- import { type Live } from '@dxos/live-object';
11
- import { fullyQualifiedId } from '@dxos/react-client/echo';
8
+ import { type Database, Obj } from '@dxos/echo';
12
9
  import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
13
- import { type DataType, type ProjectionModel } from '@dxos/schema';
10
+ import { type ProjectionModel } from '@dxos/schema';
14
11
  import { isNonNullable } from '@dxos/util';
15
12
 
16
- import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
13
+ import {
14
+ TableModel,
15
+ type TableModelProps,
16
+ type TableRow,
17
+ type TableRowAction,
18
+ createEchoChangeCallback,
19
+ } from '../model';
20
+ import { type Table } from '../types';
17
21
 
18
- export type UseTableModelParams<T extends TableRow = TableRow> = {
19
- view?: DataType.View;
20
- schema?: JsonSchemaType;
22
+ export type UseTableModelProps<T extends TableRow = TableRow> = {
23
+ object?: Table.Table;
21
24
  projection?: ProjectionModel;
22
- rows?: Live<T>[];
25
+ db?: Database.Database;
26
+ rows?: T[];
23
27
  rowActions?: TableRowAction[];
24
28
  onSelectionChanged?: (selection: string[]) => void;
25
29
  onRowAction?: (actionId: string, data: T) => void;
26
30
  } & Pick<
27
31
  TableModelProps<T>,
28
- 'features' | 'onInsertRow' | 'onDeleteRows' | 'onDeleteColumn' | 'onCellUpdate' | 'onRowOrderChange'
32
+ 'features' | 'onInsertRow' | 'onDeleteRows' | 'onColumnDelete' | 'onCellUpdate' | 'onRowOrderChange'
29
33
  >;
30
34
 
31
35
  export const useTableModel = <T extends TableRow = TableRow>({
32
- view,
33
- schema,
36
+ object,
34
37
  projection,
38
+ db,
35
39
  rows,
36
40
  rowActions,
37
41
  features,
38
42
  onSelectionChanged,
39
43
  onRowAction,
40
44
  ...props
41
- }: UseTableModelParams<T>): TableModel<T> | undefined => {
42
- const selected = useSelected(view && fullyQualifiedId(view), 'multi');
43
- const initialSelection = useMemo(() => selected, [view]);
45
+ }: UseTableModelProps<T>): TableModel<T> | undefined => {
46
+ const registry = useContext(RegistryContext);
47
+ const selected = useSelected(object && Obj.getDXN(object).toString(), 'multi');
48
+ const initialSelection = useMemo(() => selected, [object]);
44
49
 
45
50
  const [model, setModel] = useState<TableModel<T>>();
46
51
  useEffect(() => {
47
- if (!view || !schema) {
52
+ if (!object || !projection) {
48
53
  return;
49
54
  }
50
55
 
51
56
  let model: TableModel<T> | undefined;
52
57
  const t = setTimeout(async () => {
53
58
  model = new TableModel<T>({
54
- view,
55
- schema,
59
+ registry,
60
+ object,
56
61
  projection,
62
+ db,
63
+ change: createEchoChangeCallback<T>(object),
57
64
  features,
58
65
  rowActions,
59
66
  initialSelection,
@@ -68,18 +75,13 @@ export const useTableModel = <T extends TableRow = TableRow>({
68
75
  clearTimeout(t);
69
76
  void model?.close();
70
77
  };
71
- }, [view, schema, projection, features, rowActions, initialSelection]); // TODO(burdon): Trigger if callbacks change?
78
+ // TODO(burdon): Trigger if callbacks change?
79
+ }, [registry, object, projection, features, rowActions, initialSelection]);
72
80
 
73
- // Update data.
81
+ // Update data when rows change.
74
82
  useEffect(() => {
75
- if (rows) {
76
- // TODO(ZaymonFC): Remove this workaround once unstable query ordering issue is resolved
77
- /*
78
- * Sort all objects by string id field as a temporary workaround for query ordering issues
79
- * Reference: https://github.com/dxos/dxos/pull/9409
80
- */
81
- const sortedRows = orderBy(rows, [(row) => String(row.id)], ['asc']);
82
- model?.setRows(sortedRows);
83
+ if (rows && model) {
84
+ model.setRows(rows);
83
85
  }
84
86
  }, [model, rows]);
85
87
 
@@ -90,8 +92,8 @@ export const useTableModel = <T extends TableRow = TableRow>({
90
92
  return;
91
93
  }
92
94
 
93
- const unsubscribe = effect(() => {
94
- const selectedItems = [...model.selection.selection.value];
95
+ const unsubscribe = registry.subscribe(model.selection.selectionAtom, () => {
96
+ const selectedItems = [...model.selection.selection];
95
97
  multiSelect(selectedItems);
96
98
  onSelectionChanged?.(selectedItems);
97
99
  });
@@ -101,7 +103,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
101
103
  clear();
102
104
  unsubscribe();
103
105
  };
104
- }, [model, onSelectionChanged]);
106
+ }, [registry, model, onSelectionChanged]);
105
107
 
106
108
  return model;
107
109
  };
package/src/index.ts CHANGED
@@ -2,11 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { translations } from './translations';
6
-
7
5
  export * from './components';
8
6
  export * from './hooks';
9
7
  export * from './model';
10
8
  export * from './util';
11
-
12
- export { translations };