@dxos/react-ui-table 0.8.4-main.fcfe5033a5 → 0.8.4-staging.60fe92afc8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/{chunk-F7Y374KL.mjs → chunk-4KOQTPFC.mjs} +62 -111
  3. package/dist/lib/browser/chunk-4KOQTPFC.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-BF54SSNL.mjs → chunk-62PYOJPY.mjs} +13 -16
  5. package/dist/lib/browser/chunk-62PYOJPY.mjs.map +7 -0
  6. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  7. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  8. package/dist/lib/browser/index.mjs +57 -131
  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 +9 -8
  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 +2 -1
  16. package/dist/lib/node-esm/{chunk-SIRDIPHZ.mjs → chunk-7LMOZUTS.mjs} +13 -16
  17. package/dist/lib/node-esm/chunk-7LMOZUTS.mjs.map +7 -0
  18. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  19. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  20. package/dist/lib/node-esm/{chunk-CCJ4LOVM.mjs → chunk-KOSV2J5N.mjs} +62 -111
  21. package/dist/lib/node-esm/chunk-KOSV2J5N.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +57 -131
  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 +9 -8
  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 +2 -1
  30. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  32. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/DynamicTable.d.ts +3 -4
  34. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +21 -20
  36. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Relations.stories.d.ts +87 -81
  38. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  40. package/dist/types/src/components/Table/Table.d.ts +2 -2
  41. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  42. package/dist/types/src/components/Table/Table.stories.d.ts +88 -81
  43. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/Table/TableContent.d.ts +2 -1
  45. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -1
  46. package/dist/types/src/components/Table/TableToolbar.d.ts +1 -1
  47. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -1
  48. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  49. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +1 -2
  50. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  51. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  52. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +19 -19
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  54. package/dist/types/src/components/testing.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  56. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
  57. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  58. package/dist/types/src/index.d.ts +0 -2
  59. package/dist/types/src/index.d.ts.map +1 -1
  60. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  61. package/dist/types/src/model/selection-model.d.ts +1 -1
  62. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  63. package/dist/types/src/model/table-model.d.ts +7 -7
  64. package/dist/types/src/model/table-model.d.ts.map +1 -1
  65. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  66. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  67. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  68. package/dist/types/src/testing/useTestTableModel.d.ts +1 -1
  69. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  70. package/dist/types/src/translations.d.ts +19 -19
  71. package/dist/types/src/translations.d.ts.map +1 -1
  72. package/dist/types/src/types/Table.d.ts +12 -6
  73. package/dist/types/src/types/Table.d.ts.map +1 -1
  74. package/dist/types/src/util/dynamic-table.d.ts +6 -6
  75. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  76. package/dist/types/src/util/query-ast.d.ts.map +1 -1
  77. package/dist/types/src/util/schema.d.ts.map +1 -1
  78. package/dist/types/src/util/signals.d.ts.map +1 -1
  79. package/dist/types/src/util/sort.d.ts.map +1 -1
  80. package/dist/types/src/util/table-buttons.d.ts +20 -20
  81. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  82. package/dist/types/src/util/table-controls.d.ts +4 -4
  83. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  84. package/dist/types/src/util/tag.d.ts.map +1 -1
  85. package/dist/types/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +53 -63
  87. package/src/components/Table/ColumnActionsMenu.tsx +2 -1
  88. package/src/components/Table/ColumnSettings.tsx +2 -2
  89. package/src/components/Table/DynamicTable.stories.tsx +9 -8
  90. package/src/components/Table/DynamicTable.tsx +11 -12
  91. package/src/components/Table/Relations.stories.tsx +46 -9
  92. package/src/components/Table/RowActionsMenu.tsx +3 -2
  93. package/src/components/Table/Table.stories.tsx +86 -33
  94. package/src/components/Table/TableContent.tsx +9 -4
  95. package/src/components/Table/TableToolbar.tsx +2 -2
  96. package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -2
  97. package/src/components/TableCellEditor/FormCellEditor.tsx +8 -7
  98. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +7 -4
  99. package/src/components/TableCellEditor/TableCellEditor.tsx +8 -6
  100. package/src/components/testing.ts +7 -12
  101. package/src/hooks/useProjectionModel.ts +5 -6
  102. package/src/hooks/useTableModel.ts +1 -1
  103. package/src/index.ts +0 -4
  104. package/src/model/table-model.test.ts +4 -9
  105. package/src/model/table-model.ts +43 -36
  106. package/src/model/table-presentation.test.ts +4 -9
  107. package/src/model/table-presentation.ts +15 -15
  108. package/src/testing/useTestTableModel.ts +6 -7
  109. package/src/types/Table.ts +16 -15
  110. package/src/util/dynamic-table.test.ts +13 -20
  111. package/src/util/dynamic-table.ts +19 -18
  112. package/dist/lib/browser/chunk-BF54SSNL.mjs.map +0 -7
  113. package/dist/lib/browser/chunk-F7Y374KL.mjs.map +0 -7
  114. package/dist/lib/node-esm/chunk-CCJ4LOVM.mjs.map +0 -7
  115. package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs.map +0 -7
@@ -5,13 +5,12 @@
5
5
  import { Atom, type Registry } from '@effect-atom/atom-react';
6
6
 
7
7
  import { Resource } from '@dxos/context';
8
- import { type Database, Format, Obj, Order, Query, type QueryAST, Ref } from '@dxos/echo';
9
- import { type View } from '@dxos/echo';
10
- import { type JsonProp, type JsonSchemaType, type Mutable, toEffectSchema } from '@dxos/echo/internal';
11
- import { getSnapshot } from '@dxos/echo/internal';
12
- import { getValue, setValue } from '@dxos/effect';
8
+ import { type Database, Format, Obj, Order, Query, type QueryAST, Ref, Type, type View } from '@dxos/echo';
9
+ import { type JsonSchema as JsonSchemaType, toEffectSchema } from '@dxos/echo/JsonSchema';
10
+ import { getSnapshot, type Mutable } from '@dxos/echo/Obj';
11
+ import { SchemaEx } from '@dxos/effect';
13
12
  import { invariant } from '@dxos/invariant';
14
- import { ObjectId } from '@dxos/keys';
13
+ import { EntityId } from '@dxos/keys';
15
14
  import { type Label } from '@dxos/react-ui';
16
15
  import { formatForEditing, parseValue } from '@dxos/react-ui-form';
17
16
  import {
@@ -38,7 +37,7 @@ export type FieldSortType = {
38
37
  import { type SelectionMode, SelectionModel } from './selection-model';
39
38
 
40
39
  /**
41
- * Callback type for wrapping mutations in Obj.change().
40
+ * Callback type for wrapping mutations in Obj.update().
42
41
  * Contains separate callbacks for table object and row mutations.
43
42
  */
44
43
  export type TableChangeCallback<T extends TableRow> = {
@@ -53,10 +52,10 @@ export type TableChangeCallback<T extends TableRow> = {
53
52
  * Use this when the table and rows are stored in the ECHO database.
54
53
  */
55
54
  export const createEchoChangeCallback = <T extends TableRow>(table: Table.Table): TableChangeCallback<T> => ({
56
- table: (mutate) => Obj.change(table, (table) => mutate(table as unknown as Table.Table)),
55
+ table: (mutate) => Obj.update(table, (table) => mutate(table as unknown as Table.Table)),
57
56
  row: (row, mutate) => {
58
57
  if (Obj.isObject(row)) {
59
- Obj.change(row, (row) => mutate(row as T));
58
+ Obj.update(row, (row) => mutate(row as T));
60
59
  } else {
61
60
  mutate(row);
62
61
  }
@@ -85,7 +84,7 @@ export type DraftRow<T extends TableRow = TableRow> = {
85
84
  };
86
85
 
87
86
  // TODO(burdon): Use schema types.
88
- export type TableRow = Record<JsonProp, any> & { id: string };
87
+ export type TableRow = Record<SchemaEx.JsonProp, any> & { id: string };
89
88
 
90
89
  export type TableRowAction = {
91
90
  id: string;
@@ -112,7 +111,7 @@ export type TableModelProps<T extends TableRow = TableRow> = {
112
111
  projection: ProjectionModel;
113
112
  db?: Database.Database;
114
113
  /**
115
- * Callbacks to wrap mutations in Obj.change().
114
+ * Callbacks to wrap mutations in Obj.update().
116
115
  * Use createEchoChangeCallback() for ECHO-backed objects or createDirectChangeCallback() for plain objects.
117
116
  */
118
117
  change?: TableChangeCallback<T>;
@@ -210,10 +209,15 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
210
209
  this._cellUpdateCounter = Atom.make<number>(0);
211
210
 
212
211
  // Create derived atom for persisted sort from view.query.ast.
213
- this._persistedSort = Atom.make((_get) => {
214
- // Note: This depends on the view being loaded and the projection being set.
215
- // The view is loaded in _open().
216
- const viewSnapshot = Obj.getSnapshot(this.view);
212
+ this._persistedSort = Atom.make((get) => {
213
+ // Subscribe to the view ref's atom so this recomputes once the reference resolves; the view
214
+ // is loaded asynchronously in _open() and is absent before then (and after disposal).
215
+ const view = get(this._object.view.atom);
216
+ if (!view) {
217
+ return undefined;
218
+ }
219
+
220
+ const viewSnapshot = Obj.getSnapshot(view);
217
221
  const ast = viewSnapshot.query.ast;
218
222
  const orders = extractOrder(ast);
219
223
 
@@ -258,8 +262,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
258
262
  const fieldProjection = this._projection.getFieldProjection(field.id);
259
263
 
260
264
  const sorted = [...rows].sort((a, b) => {
261
- const aValue = getValue(a, field.path);
262
- const bValue = getValue(b, field.path);
265
+ const aValue = SchemaEx.getValue(a, field.path);
266
+ const bValue = SchemaEx.getValue(b, field.path);
263
267
  return compareValues(aValue, bValue, fieldProjection.props.format, sort.direction);
264
268
  });
265
269
 
@@ -300,7 +304,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
300
304
  }
301
305
 
302
306
  public get id(): string {
303
- return Obj.getDXN(this._object).toString();
307
+ return Obj.getURI(this._object);
304
308
  }
305
309
 
306
310
  public get view(): View.View {
@@ -524,7 +528,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
524
528
  }
525
529
 
526
530
  // NOTE(ZaymonFC): This is initialized with id because it's required in all schemata?
527
- const draftData = { id: ObjectId.random() } as any as T;
531
+ const draftData = { id: EntityId.random() } as any as T;
528
532
  const validationErrors = this.validateDraftRowData(draftData);
529
533
 
530
534
  const draftRow: DraftRow<T> = {
@@ -623,14 +627,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
623
627
  if (plane === 'frozenRowsEnd') {
624
628
  // Get data from draft row
625
629
  if (row >= 0 && row < this._registry.get(this._draftRows).length) {
626
- value = getValue(this._registry.get(this._draftRows)[row].data, field.path);
630
+ value = SchemaEx.getValue(this._registry.get(this._draftRows)[row].data, field.path);
627
631
  } else {
628
632
  return undefined;
629
633
  }
630
634
  } else {
631
635
  // Get data from regular row (use sorted rows for display index)
632
636
  const sortedRows = this._registry.get(this._sortedRows);
633
- value = getValue(sortedRows[row], field.path);
637
+ value = SchemaEx.getValue(sortedRows[row], field.path);
634
638
  }
635
639
 
636
640
  if (value == null) {
@@ -644,7 +648,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
644
648
  return ''; // TODO(burdon): Show error.
645
649
  }
646
650
 
647
- return getValue(value.target, field.referencePath);
651
+ return SchemaEx.getValue(value.target, field.referencePath);
648
652
  }
649
653
 
650
654
  default: {
@@ -677,7 +681,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
677
681
 
678
682
  const draftRow = this._registry.get(this._draftRows)[row];
679
683
  const snapshot = { ...draftRow.data };
680
- setValue(snapshot, field.path, transformedValue);
684
+ SchemaEx.setValue(snapshot, field.path, transformedValue);
681
685
 
682
686
  const validationErrors = this.validateDraftRowData(snapshot);
683
687
  // TODO(thure): These errors sometimes result in a useless message like “is missing” (what is missing?)
@@ -692,17 +696,20 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
692
696
  const currentRow = this._registry.get(this._rows)[row];
693
697
  invariant(currentRow, 'Invalid row index');
694
698
 
695
- const snapshot = { ...getSnapshot(currentRow) };
696
- setValue(snapshot, field.path, transformedValue);
699
+ // TableRow is a generic type; cast to Obj.Unknown for Echo introspection APIs.
700
+ const snapshot = { ...getSnapshot(currentRow as unknown as Obj.Unknown) };
701
+ SchemaEx.setValue(snapshot, field.path, transformedValue);
697
702
 
698
- const schema = Obj.getSchema(currentRow);
699
- invariant(schema);
703
+ const type = Obj.getType(currentRow as unknown as Obj.Unknown);
704
+ invariant(type);
705
+ const schema = Type.getSchema(type);
700
706
 
701
707
  const validationResult = validateSchema(schema, snapshot);
702
708
  if (validationResult && validationResult.length > 0) {
703
- const error = validationResult[0];
704
- invariant(error.path === field.path);
705
- return { valid: false, error: error.message };
709
+ const error = validationResult.find((err) => err.path === field.path);
710
+ if (error) {
711
+ return { valid: false, error: error.message };
712
+ }
706
713
  }
707
714
 
708
715
  return { valid: true };
@@ -728,7 +735,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
728
735
  if (plane === 'frozenRowsEnd') {
729
736
  // Update draft row data (draft rows are plain objects, not ECHO objects).
730
737
  if (row >= 0 && row < this._registry.get(this._draftRows).length) {
731
- setValue(this._registry.get(this._draftRows)[row].data, field.path, transformedValue);
738
+ SchemaEx.setValue(this._registry.get(this._draftRows)[row].data, field.path, transformedValue);
732
739
  // Re-validate the draft row after the update
733
740
  this.validateDraftRow(row);
734
741
  }
@@ -737,7 +744,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
737
744
  const sortedRows = this._registry.get(this._sortedRows);
738
745
  const rowData = sortedRows[row];
739
746
  this._change.row(rowData, (mutableRow) => {
740
- setValue(mutableRow, field.path, transformedValue);
747
+ SchemaEx.setValue(mutableRow, field.path, transformedValue);
741
748
  });
742
749
  }
743
750
  };
@@ -753,10 +760,10 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
753
760
  const sortedRows = this._registry.get(this._sortedRows);
754
761
  const rowData = sortedRows[row];
755
762
 
756
- const value = getValue(rowData, field.path);
763
+ const value = SchemaEx.getValue(rowData, field.path);
757
764
  const updatedValue = update(value);
758
765
  this._change.row(rowData, (mutableRow) => {
759
- setValue(mutableRow, field.path, updatedValue);
766
+ SchemaEx.setValue(mutableRow, field.path, updatedValue);
760
767
  });
761
768
  }
762
769
 
@@ -894,13 +901,13 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
894
901
  if (field) {
895
902
  // Persist sort to view.query.ast
896
903
  const newQuery = baseQuery.orderBy(Order.property<any>(field.path as string, inMemorySort.direction));
897
- Obj.change(view, (view) => {
904
+ Obj.update(view, (view) => {
898
905
  view.query.ast = newQuery.ast as Mutable<typeof newQuery.ast>;
899
906
  });
900
907
  }
901
908
  } else {
902
909
  // Clear sort from view.query.ast
903
- Obj.change(view, (view) => {
910
+ Obj.update(view, (view) => {
904
911
  view.query.ast = baseQuery.ast as Mutable<typeof baseQuery.ast>;
905
912
  });
906
913
  }
@@ -6,7 +6,7 @@ import { Registry } from '@effect-atom/atom-react';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import { beforeEach, describe, expect, it } from 'vitest';
8
8
 
9
- import { Filter, JsonSchema, Query, Type } from '@dxos/echo';
9
+ import { DXN, Filter, JsonSchema, Query, Type } from '@dxos/echo';
10
10
  import { createEchoSchema } from '@dxos/echo/testing';
11
11
  import { ProjectionModel, ViewModel, createDirectChangeCallback } from '@dxos/schema';
12
12
 
@@ -89,15 +89,10 @@ describe('TablePresentation', () => {
89
89
  const Test = Schema.Struct({
90
90
  title: Schema.String,
91
91
  count: Schema.Number,
92
- }).pipe(
93
- Type.object({
94
- typename: 'com.example.type.test',
95
- version: '0.1.0',
96
- }),
97
- );
92
+ }).pipe(Type.makeObject(DXN.make('com.example.type.test', '0.1.0')));
98
93
 
99
94
  const createTableModel = (registry: Registry.Registry, props: Partial<TableModelProps> = {}): TableModel => {
100
- const schema = createEchoSchema(Test);
95
+ const schema = createEchoSchema(Type.getSchema(Test));
101
96
  const view = ViewModel.make({
102
97
  query: Query.select(Filter.type(schema)),
103
98
  jsonSchema: schema.jsonSchema,
@@ -107,7 +102,7 @@ const createTableModel = (registry: Registry.Registry, props: Partial<TableModel
107
102
  registry,
108
103
  view,
109
104
  baseSchema: schema.jsonSchema,
110
- change: createDirectChangeCallback(view.projection, JsonSchema.toJsonSchema(schema)),
105
+ change: createDirectChangeCallback(view.projection, JsonSchema.toJsonSchema(Type.getSchema(schema))),
111
106
  });
112
107
  projection.normalizeView();
113
108
  return new TableModel({ registry, object, projection, ...props });
@@ -5,9 +5,9 @@
5
5
  import { Atom, type Registry } from '@effect-atom/atom-react';
6
6
  import * as Predicate from 'effect/Predicate';
7
7
 
8
- import { Obj } from '@dxos/echo';
9
- import { type View } from '@dxos/echo';
10
- import { Format, TypeEnum, getValue } from '@dxos/echo/internal';
8
+ import { Obj, type View } from '@dxos/echo';
9
+ import { Format, TypeEnum } from '@dxos/echo/Format';
10
+ import { SchemaEx } from '@dxos/effect';
11
11
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
12
12
  import {
13
13
  type DxGridCellValue,
@@ -115,7 +115,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
115
115
 
116
116
  const cell: DxGridCellValue = {
117
117
  get value() {
118
- const value = getValue(obj, field.path);
118
+ const value = SchemaEx.getValue(obj, field.path);
119
119
  if (value == null) {
120
120
  return '';
121
121
  }
@@ -135,7 +135,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
135
135
  return ''; // TODO(burdon): Show error.
136
136
  }
137
137
 
138
- return getValue(value.target, field.referencePath);
138
+ return SchemaEx.getValue(value.target, field.referencePath);
139
139
  }
140
140
 
141
141
  default: {
@@ -165,7 +165,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
165
165
 
166
166
  // Arrays.
167
167
  if (props.type === TypeEnum.Array) {
168
- const targetArray = getValue(obj, field.path);
168
+ const targetArray = SchemaEx.getValue(obj, field.path);
169
169
  if (targetArray && Array.isArray(targetArray)) {
170
170
  // TODO(ZaymonFC): For arrays of objects, objects should have a 'label' annotation on the field
171
171
  // that can be used to render tags.
@@ -201,22 +201,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
201
201
  })
202
202
  .join('');
203
203
 
204
- cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
204
+ cell.accessoryHtml = `<div class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
205
205
  }
206
206
  }
207
207
 
208
208
  // References.
209
209
  if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
210
- const targetObj = getValue(obj, field.path)?.target;
210
+ const targetObj = SchemaEx.getValue(obj, field.path)?.target;
211
211
  if (targetObj) {
212
- const dxn = Obj.getDXN(targetObj)?.toString();
213
- cell.accessoryHtml = `<div role="none" class="absolute end-0 inset-y-0 p-(--dx-grid-cell-content-padding-block)"><dx-anchor dxn=${dxn} class="dx-button w-6 aspect-square min-h-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
212
+ const uri = Obj.getURI(targetObj);
213
+ cell.accessoryHtml = `<div role="none" class="absolute end-0 inset-y-0 p-(--dx-grid-cell-content-padding-block)"><dx-anchor uri=${uri} class="dx-button w-6 aspect-square min-h-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
214
214
  }
215
215
  }
216
216
 
217
217
  // Booleans.
218
218
  if (props.format === Format.TypeFormat.Boolean) {
219
- const value = getValue(obj, field.path);
219
+ const value = SchemaEx.getValue(obj, field.path);
220
220
  cell.accessoryHtml = tableControls.switch.render({
221
221
  colIndex,
222
222
  rowIndex: displayIndex,
@@ -227,7 +227,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
227
227
 
228
228
  // Single-Selects.
229
229
  if (props.format === Format.TypeFormat.SingleSelect) {
230
- const value = getValue(obj, field.path);
230
+ const value = SchemaEx.getValue(obj, field.path);
231
231
  const options = this.model.projection.getFieldProjection(field.id).props.options;
232
232
  if (options) {
233
233
  const option = options.find((o) => o.id === value);
@@ -239,7 +239,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
239
239
 
240
240
  // Multi-Selects.
241
241
  if (props.format === Format.TypeFormat.MultiSelect) {
242
- const values = getValue(obj, field.path) as string[] | undefined;
242
+ const values = SchemaEx.getValue(obj, field.path) as string[] | undefined;
243
243
  const options = this.model.projection.getFieldProjection(field.id).props.options;
244
244
  if (options && values && values.length > 0) {
245
245
  const tags = values
@@ -473,8 +473,8 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
473
473
 
474
474
  switch (selectionMode) {
475
475
  case 'single':
476
- return ['dx-grid__cell--no-focus-unfurl !bg-current-surface hover:bg-hover-surface cursor-pointer!'];
476
+ return ['dx-grid__cell--no-focus-unfurl bg-current-surface! hover:bg-hover-surface cursor-pointer!'];
477
477
  case 'multiple':
478
- return ['!bg-grid-cell-selected'];
478
+ return ['bg-grid-cell-selected!'];
479
479
  }
480
480
  };
@@ -5,13 +5,12 @@
5
5
  import { RegistryContext } from '@effect-atom/atom-react';
6
6
  import { type RefObject, useCallback, useContext, useMemo, useRef } from 'react';
7
7
 
8
- import { type Database, type Type } from '@dxos/echo';
9
- import { isMutable } from '@dxos/echo/internal';
8
+ import { type Database, Filter, Type } from '@dxos/echo';
10
9
  import { random } from '@dxos/random';
11
- import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
10
+ import { useQuery, useType } from '@dxos/react-client/echo';
12
11
  import { useClientStory } from '@dxos/react-client/testing';
13
12
  import { useGlobalFilteredObjects } from '@dxos/react-ui-search';
14
- import { type ProjectionModel, getTypenameFromQuery } from '@dxos/schema';
13
+ import { type ProjectionModel, getTypeURIFromQuery } from '@dxos/schema';
15
14
 
16
15
  import { type TableController } from '../components';
17
16
  import { useAddRow, useProjectionModel, useTableModel } from '../hooks';
@@ -45,15 +44,15 @@ export const useTestTableModel = <T extends Type.AnyObj = Type.AnyObj>(): TestTa
45
44
 
46
45
  const tables = useQuery(space?.db, Filter.type(Table.Table));
47
46
  const table = tables.at(0);
48
- const typename = table?.view.target?.query ? getTypenameFromQuery(table.view.target.query.ast) : undefined;
49
- const schema = useSchema<T>(space?.db, typename);
47
+ const typeUri = table?.view.target?.query ? getTypeURIFromQuery(table.view.target.query.ast) : undefined;
48
+ const schema = useType<T>(space?.db, typeUri);
50
49
  const projection = useProjectionModel(schema, table, registry);
51
50
 
52
51
  const features = useMemo(
53
52
  () => ({
54
53
  selection: { enabled: true, mode: 'multiple' as const },
55
54
  dataEditable: true,
56
- schemaEditable: schema && isMutable(schema),
55
+ schemaEditable: schema != null && Type.getDatabase(schema) != null,
57
56
  }),
58
57
  [schema],
59
58
  );
@@ -6,12 +6,17 @@ import * as Match from 'effect/Match';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import * as SchemaAST from 'effect/SchemaAST';
8
8
 
9
- import { Annotation, JsonSchema, Obj, Ref, Type } from '@dxos/echo';
10
- import { View } from '@dxos/echo';
11
- import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
9
+ // QueryAST is referenced indirectly through `Type.InstanceType<typeof TableSchema>`
10
+ // (Ref.Ref(View.View) → View.View → QueryAST.Query) in the emitted .d.ts; the
11
+ // namespace import keeps the inferred types portable.
12
+ // eslint-disable-next-line unused-imports/no-unused-imports
13
+ import { DXN, Annotation, JsonSchema, Obj, QueryAST, Ref, Type, View } from '@dxos/echo';
14
+ import { FormInputAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';
15
+ import { type JsonSchema as JsonSchemaType } from '@dxos/echo/JsonSchema';
16
+ import { SchemaEx } from '@dxos/effect';
12
17
  import { ViewAnnotation } from '@dxos/schema';
13
18
 
14
- export const Table = Schema.Struct({
19
+ const TableSchema = Schema.Struct({
15
20
  name: Schema.String.pipe(Schema.optional),
16
21
 
17
22
  view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false)),
@@ -22,19 +27,15 @@ export const Table = Schema.Struct({
22
27
  value: Schema.Number,
23
28
  }).pipe(Schema.mutable, FormInputAnnotation.set(false)),
24
29
  }).pipe(
25
- Type.object({
26
- typename: 'org.dxos.type.table',
27
- version: '0.1.0',
28
- }),
29
30
  LabelAnnotation.set(['name']),
30
- ViewAnnotation.set(true),
31
- Annotation.IconAnnotation.set({
32
- icon: 'ph--table--regular',
33
- hue: 'green',
34
- }),
31
+ ViewAnnotation.set(['view']),
32
+ Annotation.IconAnnotation.set({ icon: 'ph--table--regular', hue: 'green' }),
33
+ Type.makeObject(DXN.make('org.dxos.type.table', '0.1.0')),
35
34
  );
36
35
 
37
- export interface Table extends Schema.Schema.Type<typeof Table> {}
36
+ // TODO(wittjosiah): Try to clean up this type inference.
37
+ export interface Table extends Type.InstanceType<typeof TableSchema> {}
38
+ export const Table = TableSchema;
38
39
 
39
40
  type MakeProps = {
40
41
  name?: string;
@@ -55,7 +56,7 @@ export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table =
55
56
  const schema = JsonSchema.toEffectSchema(jsonSchema);
56
57
  const properties = SchemaAST.getPropertySignatures(schema.ast);
57
58
  for (const property of properties) {
58
- const name = property.name.toString() as JsonPath;
59
+ const name = property.name.toString() as SchemaEx.JsonPath;
59
60
  if (sizes?.[name]) {
60
61
  table.sizes[name] = sizes[name];
61
62
  continue;
@@ -11,13 +11,14 @@ import { type TablePropertyDefinition, getBaseSchema, makeDynamicTable } from '.
11
11
 
12
12
  describe('makeDynamicTable', () => {
13
13
  /**
14
- * Base case: plain jsonSchema (not from Echo / JsonSchema.toJsonSchema). Does not exercise the path
15
- * where projection or schema are reactive, so this does not reproduce the Obj.change regression.
16
- * Confirms makeDynamicTable + setProperties (title) works with plain objects.
14
+ * Builds the Type entity from a JSON schema, then makes the table.
15
+ * Confirms makeDynamicTable + setProperties (title) works for the jsonSchema path.
17
16
  */
18
- test('makeDynamicTable with plain jsonSchema and properties with title does not throw', () => {
17
+ test('makeDynamicTable from a jsonSchema with properties with title does not throw', () => {
19
18
  const registry = Registry.make();
20
19
  const jsonSchema = {
20
+ typename: 'com.example.type.dynamicTablePlain',
21
+ version: '0.1.0',
21
22
  type: 'object' as const,
22
23
  properties: {
23
24
  id: { type: 'string' as const },
@@ -31,11 +32,8 @@ describe('makeDynamicTable', () => {
31
32
  ];
32
33
 
33
34
  expect(() => {
34
- const { projection, object } = makeDynamicTable({
35
- registry,
36
- jsonSchema,
37
- properties,
38
- });
35
+ const { type } = getBaseSchema({ jsonSchema });
36
+ const { projection, object } = makeDynamicTable({ registry, type, properties });
39
37
  expect(projection).toBeDefined();
40
38
  expect(object).toBeDefined();
41
39
  expect(projection.getFields().length).toBeGreaterThan(0);
@@ -43,25 +41,20 @@ describe('makeDynamicTable', () => {
43
41
  });
44
42
 
45
43
  /**
46
- * Regression test for the path where jsonSchema comes from getBaseSchema(typename, properties).
47
- * That path uses Echo (ViewModel.make, JsonSchema.toJsonSchema); mutations must run inside Obj.change and on a
48
- * cloned schema. This test ensures that flow does not throw.
44
+ * Builds the Type entity from property definitions, then makes the table.
45
+ * Confirms makeDynamicTable + setProperties (title) works for the property-definitions path.
49
46
  */
50
- test('makeDynamicTable with jsonSchema from getBaseSchema(typename, properties) and properties with title does not throw', () => {
47
+ test('makeDynamicTable from getBaseSchema(typename, properties) with properties with title does not throw', () => {
51
48
  const registry = Registry.make();
52
- const typename = 'com.example.type.dynamic-table-test';
49
+ const typename = 'com.example.type.dynamicTableTest';
53
50
  const properties: TablePropertyDefinition[] = [
54
51
  { name: 'id', format: Format.TypeFormat.String },
55
52
  { name: 'name', format: Format.TypeFormat.String, title: 'Name' },
56
53
  ];
57
- const { jsonSchema } = getBaseSchema({ typename, properties });
58
54
 
59
55
  expect(() => {
60
- const { projection, object } = makeDynamicTable({
61
- registry,
62
- jsonSchema,
63
- properties,
64
- });
56
+ const { type } = getBaseSchema({ typename, properties });
57
+ const { projection, object } = makeDynamicTable({ registry, type, properties });
65
58
  expect(projection).toBeDefined();
66
59
  expect(object).toBeDefined();
67
60
  expect(projection.getFields().length).toBeGreaterThan(0);
@@ -5,13 +5,13 @@
5
5
  import { type Registry } from '@effect-atom/atom-react';
6
6
  import type * as Types from 'effect/Types';
7
7
 
8
- import { Filter, JsonSchema, Obj, Order, Query, type QueryAST, Ref, Type } from '@dxos/echo';
9
- import { type View } from '@dxos/echo';
8
+ import { Filter, type JsonSchema, Obj, Order, Query, type QueryAST, Ref, Type, type View } from '@dxos/echo';
10
9
  import {
11
10
  ProjectionModel,
12
11
  type SchemaPropertyDefinition,
13
12
  ViewModel,
14
13
  createEchoChangeCallback,
14
+ getSchemaFromJsonSchema,
15
15
  getSchemaFromPropertyDefinitions,
16
16
  } from '@dxos/schema';
17
17
 
@@ -31,40 +31,41 @@ export type TablePropertyDefinition = SchemaPropertyDefinition & Partial<Propert
31
31
  */
32
32
  // TODO(burdon): Remove variance.
33
33
  export const getBaseSchema = ({
34
- schema,
34
+ type,
35
35
  typename,
36
36
  properties,
37
37
  jsonSchema,
38
38
  }: {
39
- schema?: Type.AnyEntity;
39
+ type?: Type.AnyEntity;
40
40
  typename?: string;
41
41
  properties?: TablePropertyDefinition[];
42
42
  jsonSchema?: Types.DeepMutable<JsonSchema.JsonSchema>;
43
- }): { typename: string; jsonSchema: Types.DeepMutable<JsonSchema.JsonSchema> } => {
44
- if (typename && properties) {
45
- const schema = getSchemaFromPropertyDefinitions(typename, properties);
46
- return { typename: schema.typename, jsonSchema: JsonSchema.toJsonSchema(schema) };
47
- } else if (schema) {
48
- return { typename: Type.getTypename(schema)!, jsonSchema: JsonSchema.toJsonSchema(schema) };
49
- } else if (typename && jsonSchema) {
50
- return { typename, jsonSchema };
51
- } else {
43
+ }): { typename: string; type: Type.AnyEntity } => {
44
+ const resolved =
45
+ typename && properties
46
+ ? getSchemaFromPropertyDefinitions(typename, properties)
47
+ : (type ?? (jsonSchema ? getSchemaFromJsonSchema(jsonSchema, typename) : undefined));
48
+ if (!resolved) {
52
49
  throw new Error('invalid properties');
53
50
  }
51
+
52
+ return { typename: Type.getTypename(resolved)!, type: resolved };
54
53
  };
55
54
 
56
55
  export const makeDynamicTable = ({
57
56
  registry,
58
- jsonSchema,
57
+ type,
59
58
  properties,
60
59
  }: {
61
60
  registry: Registry.Registry;
62
- jsonSchema: Types.DeepMutable<JsonSchema.JsonSchema>;
61
+ type: Type.AnyEntity;
63
62
  properties?: TablePropertyDefinition[];
64
63
  }): { projection: ProjectionModel; object: Table.Table } => {
64
+ const jsonSchema = type.jsonSchema;
65
65
  const view = ViewModel.make({
66
66
  query: Query.select(Filter.everything()),
67
67
  jsonSchema,
68
+ type,
68
69
  ...(properties && { fields: properties.map((property) => property.name) }),
69
70
  });
70
71
  const object = Obj.make(Table.Table, { view: Ref.make(view), sizes: {} });
@@ -73,7 +74,7 @@ export const makeDynamicTable = ({
73
74
  registry,
74
75
  view,
75
76
  baseSchema: jsonSchema,
76
- change: createEchoChangeCallback(view, jsonSchema),
77
+ change: createEchoChangeCallback(view, type),
77
78
  });
78
79
  projection.normalizeView();
79
80
  if (properties && projection.getFields()) {
@@ -93,7 +94,7 @@ const setProperties = (
93
94
  const field = projection.getFields().find((field) => field.path === property.name);
94
95
  if (field) {
95
96
  if (property.size !== undefined) {
96
- Obj.change(table, (table) => {
97
+ Obj.update(table, (table) => {
97
98
  table.sizes[field.path] = property.size!;
98
99
  });
99
100
  }
@@ -111,7 +112,7 @@ const setProperties = (
111
112
  const currentQuery = Query.fromAst(Obj.getSnapshot(view).query.ast);
112
113
  // Use any type parameter since we're working with dynamic field paths
113
114
  const newQuery = currentQuery.orderBy(Order.property<any>(field.path as string, property.sort));
114
- Obj.change(view, (view) => {
115
+ Obj.update(view, (view) => {
115
116
  // Type assertion needed because Query AST types have some variance issues.
116
117
  view.query.ast = newQuery.ast as typeof view.query.ast;
117
118
  });
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types/Table.ts"],
4
- "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Match from 'effect/Match';\nimport * as Schema from 'effect/Schema';\nimport * as SchemaAST from 'effect/SchemaAST';\n\nimport { Annotation, JsonSchema, Obj, Ref, Type } from '@dxos/echo';\nimport { View } from '@dxos/echo';\nimport { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';\nimport { ViewAnnotation } from '@dxos/schema';\n\nexport const Table = Schema.Struct({\n name: Schema.String.pipe(Schema.optional),\n\n view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false)),\n\n sizes: Schema.Record({\n // TODO(wittjosiah): Should be JsonPath.\n key: Schema.String,\n value: Schema.Number,\n }).pipe(Schema.mutable, FormInputAnnotation.set(false)),\n}).pipe(\n Type.object({\n typename: 'org.dxos.type.table',\n version: '0.1.0',\n }),\n LabelAnnotation.set(['name']),\n ViewAnnotation.set(true),\n Annotation.IconAnnotation.set({\n icon: 'ph--table--regular',\n hue: 'green',\n }),\n);\n\nexport interface Table extends Schema.Schema.Type<typeof Table> {}\n\ntype MakeProps = {\n name?: string;\n sizes?: Record<string, number>;\n view: View.View;\n /** Required to auto-size columns. */\n jsonSchema?: JsonSchemaType;\n};\n\n/**\n * Make a table as a view of a data set.\n */\nexport const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table => {\n const table = Obj.make(Table, { name, view: Ref.make(view), sizes } as Obj.MakeProps<typeof Table>);\n\n // Preset sizes.\n if (jsonSchema) {\n const schema = JsonSchema.toEffectSchema(jsonSchema);\n const properties = SchemaAST.getPropertySignatures(schema.ast);\n for (const property of properties) {\n const name = property.name.toString() as JsonPath;\n if (sizes?.[name]) {\n table.sizes[name] = sizes[name];\n continue;\n }\n\n Match.type<SchemaAST.AST>().pipe(\n Match.when({ _tag: 'BooleanKeyword' }, () => {\n table.sizes[name] = 100;\n }),\n Match.when({ _tag: 'NumberKeyword' }, () => {\n table.sizes[name] = 100;\n }),\n Match.orElse(() => {\n // Noop.\n }),\n )(property.type);\n }\n }\n\n return table;\n};\n"],
5
- "mappings": ";;;;;;;AAAA;;;;;AAIA,YAAYA,WAAW;AACvB,YAAYC,YAAY;AACxB,YAAYC,eAAe;AAE3B,SAASC,YAAYC,YAAYC,KAAKC,KAAKC,YAAY;AACvD,SAASC,YAAY;AACrB,SAASC,qBAAyDC,uBAAuB;AACzF,SAASC,sBAAsB;AAExB,IAAMC,QAAeC,cAAO;EACjCC,MAAaC,cAAOC,KAAYC,eAAQ;EAExCC,MAAMC,IAAIA,IAAIC,KAAKA,IAAI,EAAEJ,KAAKK,oBAAoBC,IAAI,KAAA,CAAA;EAEtDC,OAAcC,cAAO;;IAEnBC,KAAYV;IACZW,OAAcC;EAChB,CAAA,EAAGX,KAAYY,gBAASP,oBAAoBC,IAAI,KAAA,CAAA;AAClD,CAAA,EAAGN,KACDa,KAAKC,OAAO;EACVC,UAAU;EACVC,SAAS;AACX,CAAA,GACAC,gBAAgBX,IAAI;EAAC;CAAO,GAC5BY,eAAeZ,IAAI,IAAA,GACnBa,WAAWC,eAAed,IAAI;EAC5Be,MAAM;EACNC,KAAK;AACP,CAAA,CAAA;AAgBK,IAAMC,OAAO,CAAC,EAAEzB,MAAMS,QAAQ,CAAC,GAAGL,MAAMsB,WAAU,MAAa;AACpE,QAAMC,QAAQC,IAAIH,KAAK3B,OAAO;IAAEE;IAAMI,MAAMC,IAAIoB,KAAKrB,IAAAA;IAAOK;EAAM,CAAA;AAGlE,MAAIiB,YAAY;AACd,UAAMG,SAASC,WAAWC,eAAeL,UAAAA;AACzC,UAAMM,aAAuBC,gCAAsBJ,OAAOK,GAAG;AAC7D,eAAWC,YAAYH,YAAY;AACjC,YAAMhC,QAAOmC,SAASnC,KAAKoC,SAAQ;AACnC,UAAI3B,QAAQT,KAAAA,GAAO;AACjB2B,cAAMlB,MAAMT,KAAAA,IAAQS,MAAMT,KAAAA;AAC1B;MACF;AAEAqC,MAAMC,WAAI,EAAkBpC,KACpBqC,WAAK;QAAEC,MAAM;MAAiB,GAAG,MAAA;AACrCb,cAAMlB,MAAMT,KAAAA,IAAQ;MACtB,CAAA,GACMuC,WAAK;QAAEC,MAAM;MAAgB,GAAG,MAAA;AACpCb,cAAMlB,MAAMT,KAAAA,IAAQ;MACtB,CAAA,GACMyC,aAAO,MAAA;MAEb,CAAA,CAAA,EACAN,SAASG,IAAI;IACjB;EACF;AAEA,SAAOX;AACT;",
6
- "names": ["Match", "Schema", "SchemaAST", "Annotation", "JsonSchema", "Obj", "Ref", "Type", "View", "FormInputAnnotation", "LabelAnnotation", "ViewAnnotation", "Table", "Struct", "name", "String", "pipe", "optional", "view", "Ref", "View", "FormInputAnnotation", "set", "sizes", "Record", "key", "value", "Number", "mutable", "Type", "object", "typename", "version", "LabelAnnotation", "ViewAnnotation", "Annotation", "IconAnnotation", "icon", "hue", "make", "jsonSchema", "table", "Obj", "schema", "JsonSchema", "toEffectSchema", "properties", "getPropertySignatures", "ast", "property", "toString", "Match", "type", "when", "_tag", "orElse"]
7
- }