@dxos/react-ui-table 0.8.4-main.2c6827d → 0.8.4-main.3c1ae3b

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 (103) hide show
  1. package/dist/lib/browser/chunk-CJQ6T4LL.mjs +77 -0
  2. package/dist/lib/browser/chunk-CJQ6T4LL.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-GS2UNGJK.mjs → chunk-V7A7ZA5E.mjs} +119 -111
  4. package/dist/lib/browser/chunk-V7A7ZA5E.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +162 -147
  6. package/dist/lib/browser/index.mjs.map +3 -3
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +15 -31
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/browser/types/index.mjs +3 -3
  11. package/dist/lib/node-esm/{chunk-TRR3C5NX.mjs → chunk-P73ALX3N.mjs} +119 -111
  12. package/dist/lib/node-esm/chunk-P73ALX3N.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs +78 -0
  14. package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +162 -147
  16. package/dist/lib/node-esm/index.mjs.map +3 -3
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +15 -31
  19. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/types/index.mjs +3 -3
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.d.ts +4 -5
  26. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +3 -17
  28. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Relations.stories.d.ts +10 -6
  30. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/Table.d.ts +8 -8
  32. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/Table.stories.d.ts +70 -4
  34. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  35. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  36. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +10 -11
  37. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  38. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
  39. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  40. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +3 -3
  41. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  42. package/dist/types/src/components/testing.d.ts +0 -2
  43. package/dist/types/src/components/testing.d.ts.map +1 -1
  44. package/dist/types/src/hooks/index.d.ts +2 -1
  45. package/dist/types/src/hooks/index.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
  47. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  48. package/dist/types/src/hooks/useTableModel.d.ts +4 -5
  49. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  50. package/dist/types/src/model/table-model.d.ts +6 -8
  51. package/dist/types/src/model/table-model.d.ts.map +1 -1
  52. package/dist/types/src/model/table-sorting.d.ts +7 -3
  53. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  54. package/dist/types/src/playwright/TableManager.d.ts +1 -1
  55. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  56. package/dist/types/src/testing/useTestTableModel.d.ts +19 -15
  57. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  58. package/dist/types/src/translations.d.ts +2 -2
  59. package/dist/types/src/types/Table.d.ts +28 -0
  60. package/dist/types/src/types/Table.d.ts.map +1 -0
  61. package/dist/types/src/types/index.d.ts +1 -1
  62. package/dist/types/src/types/index.d.ts.map +1 -1
  63. package/dist/types/src/util/dynamic-table.d.ts +9 -9
  64. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  65. package/dist/types/tsconfig.tsbuildinfo +1 -1
  66. package/package.json +38 -38
  67. package/src/components/Table/ColumnActionsMenu.tsx +4 -1
  68. package/src/components/Table/ColumnSettings.tsx +2 -3
  69. package/src/components/Table/DynamicTable.stories.tsx +7 -9
  70. package/src/components/Table/DynamicTable.tsx +18 -10
  71. package/src/components/Table/Relations.stories.tsx +9 -18
  72. package/src/components/Table/Table.stories.tsx +52 -59
  73. package/src/components/Table/Table.tsx +317 -304
  74. package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
  75. package/src/components/TableCellEditor/FormCellEditor.tsx +101 -108
  76. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +11 -8
  77. package/src/components/TableCellEditor/TableCellEditor.tsx +29 -29
  78. package/src/components/TableToolbar/TableToolbar.tsx +1 -1
  79. package/src/components/testing.ts +3 -3
  80. package/src/hooks/index.ts +2 -1
  81. package/src/hooks/useProjectionModel.ts +19 -0
  82. package/src/hooks/useTableModel.ts +11 -13
  83. package/src/model/table-model.test.ts +4 -4
  84. package/src/model/table-model.ts +29 -38
  85. package/src/model/table-presentation.test.ts +4 -4
  86. package/src/model/table-presentation.ts +9 -9
  87. package/src/model/table-sorting.ts +21 -12
  88. package/src/playwright/TableManager.ts +5 -5
  89. package/src/playwright/smoke.spec.ts +1 -1
  90. package/src/testing/useTestTableModel.ts +31 -27
  91. package/src/translations.ts +2 -2
  92. package/src/types/Table.ts +90 -0
  93. package/src/types/index.ts +1 -1
  94. package/src/util/dynamic-table.ts +11 -16
  95. package/dist/lib/browser/chunk-2PFAPQL4.mjs +0 -66
  96. package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +0 -7
  97. package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +0 -7
  98. package/dist/lib/node-esm/chunk-6POOFFYF.mjs +0 -67
  99. package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +0 -7
  100. package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +0 -7
  101. package/dist/types/src/types/schema.d.ts +0 -28
  102. package/dist/types/src/types/schema.d.ts.map +0 -1
  103. package/src/types/schema.ts +0 -73
@@ -5,16 +5,8 @@
5
5
  import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-core';
6
6
 
7
7
  import { Resource } from '@dxos/context';
8
- import { Obj, Ref } from '@dxos/echo';
9
- import {
10
- FormatEnum,
11
- type JsonProp,
12
- type JsonSchemaType,
13
- getSchema,
14
- getValue,
15
- setValue,
16
- toEffectSchema,
17
- } from '@dxos/echo/internal';
8
+ import { Format, Obj, Ref } from '@dxos/echo';
9
+ import { type JsonProp, type JsonSchemaType, getValue, setValue, toEffectSchema } from '@dxos/echo/internal';
18
10
  import { invariant } from '@dxos/invariant';
19
11
  import { ObjectId } from '@dxos/keys';
20
12
  import { getSnapshot, isLiveObject } from '@dxos/live-object';
@@ -28,14 +20,14 @@ import {
28
20
  } from '@dxos/react-ui-grid';
29
21
  import {
30
22
  type FieldSortType,
31
- ProjectionModel,
23
+ type ProjectionModel,
32
24
  type PropertyType,
33
25
  type ValidationError,
34
26
  type View,
35
27
  validateSchema,
36
28
  } from '@dxos/schema';
37
29
 
38
- import { Table } from '../types';
30
+ import { type Table } from '../types';
39
31
  import { touch } from '../util';
40
32
  import { extractTagIds } from '../util/tag';
41
33
 
@@ -77,9 +69,8 @@ const defaultFeatures: TableFeatures = {
77
69
  export type InsertRowResult = 'draft' | 'final';
78
70
 
79
71
  export type TableModelProps<T extends TableRow = TableRow> = {
80
- view: View.View;
81
- schema: JsonSchemaType;
82
- projection?: ProjectionModel;
72
+ object: Table.Table;
73
+ projection: ProjectionModel;
83
74
  features?: Partial<TableFeatures>;
84
75
  sorting?: FieldSortType[];
85
76
  initialSelection?: string[];
@@ -95,9 +86,8 @@ export type TableModelProps<T extends TableRow = TableRow> = {
95
86
  };
96
87
 
97
88
  export class TableModel<T extends TableRow = TableRow> extends Resource {
98
- private readonly _view: View.View;
89
+ private readonly _object: Table.Table;
99
90
  private readonly _projection: ProjectionModel;
100
- private _table?: Table.Table;
101
91
 
102
92
  private readonly _visibleRange = signal<DxGridPlaneRange>({
103
93
  start: { row: 0, col: 0 },
@@ -122,8 +112,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
122
112
  private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
123
113
 
124
114
  constructor({
125
- view,
126
- schema,
115
+ object,
127
116
  projection,
128
117
  features = {},
129
118
  sorting = [],
@@ -138,8 +127,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
138
127
  onRowAction,
139
128
  }: TableModelProps<T>) {
140
129
  super();
141
- this._view = view;
142
- this._projection = projection ?? new ProjectionModel(schema, view.projection);
130
+ this._object = object;
131
+ this._projection = projection;
143
132
  this._projection.normalizeView();
144
133
 
145
134
  // TODO(ZaymonFC): Use our more robust config merging module?
@@ -150,11 +139,15 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
150
139
  'Single selection is not compatible with editable tables.',
151
140
  );
152
141
 
153
- this._sorting = new TableSorting(this._rows, this._view, this._projection);
142
+ this._sorting = new TableSorting({
143
+ rows: this._rows,
144
+ getView: () => this.view,
145
+ projection: this._projection,
146
+ });
154
147
 
155
148
  if (sorting.length > 0) {
156
- const [sort] = sorting;
157
- this._sorting.setSort(sort.fieldId, sort.direction);
149
+ const { fieldId, direction } = sorting[0];
150
+ this._sorting.setSort(fieldId, direction);
158
151
  }
159
152
 
160
153
  this._selection = new SelectionModel(
@@ -175,16 +168,17 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
175
168
  }
176
169
 
177
170
  public get id(): string {
178
- return Obj.getDXN(this._view).toString();
171
+ return Obj.getDXN(this._object).toString();
179
172
  }
180
173
 
181
174
  public get view(): View.View {
182
- return this._view;
175
+ const view = this._object.view.target;
176
+ invariant(view, 'Table model not initialized');
177
+ return view;
183
178
  }
184
179
 
185
180
  public get table(): Table.Table {
186
- invariant(this._table, 'Model not initialized');
187
- return this._table;
181
+ return this._object;
188
182
  }
189
183
 
190
184
  public get projection(): ProjectionModel {
@@ -244,10 +238,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
244
238
  //
245
239
 
246
240
  protected override async _open(): Promise<void> {
247
- const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
248
- invariant(Obj.instanceOf(Table.Table, presentation));
249
- this._table = presentation;
250
-
241
+ await this._object.view.load();
251
242
  this.initializeColumnMeta();
252
243
  this.initializeEffects();
253
244
  await this._selection.open(this._ctx);
@@ -470,7 +461,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
470
461
 
471
462
  const { props } = this._projection.getFieldProjection(field.id);
472
463
  switch (props.format) {
473
- case FormatEnum.Ref: {
464
+ case Format.TypeFormat.Ref: {
474
465
  if (!field.referencePath) {
475
466
  return ''; // TODO(burdon): Show error.
476
467
  }
@@ -527,7 +518,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
527
518
  const snapshot = getSnapshot(currentRow);
528
519
  setValue(snapshot, field.path, transformedValue);
529
520
 
530
- const schema = getSchema(currentRow);
521
+ const schema = Obj.getSchema(currentRow);
531
522
  invariant(schema);
532
523
 
533
524
  const validationResult = validateSchema(schema, snapshot);
@@ -553,7 +544,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
553
544
  const transformedValue = editorTextToCellValue(props, value);
554
545
 
555
546
  // Special handling for Ref format to preserve existing behavior
556
- if (props.format === FormatEnum.Ref && !isLiveObject(value)) {
547
+ if (props.format === Format.TypeFormat.Ref && !isLiveObject(value)) {
557
548
  return;
558
549
  }
559
550
 
@@ -668,7 +659,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
668
659
 
669
660
  const editorTextToCellValue = (props: PropertyType, value: any): any => {
670
661
  switch (props.format) {
671
- case FormatEnum.Ref: {
662
+ case Format.TypeFormat.Ref: {
672
663
  if (isLiveObject(value)) {
673
664
  return Ref.make(value);
674
665
  } else {
@@ -676,7 +667,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
676
667
  }
677
668
  }
678
669
 
679
- case FormatEnum.SingleSelect: {
670
+ case Format.TypeFormat.SingleSelect: {
680
671
  const ids = extractTagIds(value);
681
672
  if (ids && ids.length > 0) {
682
673
  return ids[0];
@@ -685,7 +676,7 @@ const editorTextToCellValue = (props: PropertyType, value: any): any => {
685
676
  }
686
677
  }
687
678
 
688
- case FormatEnum.MultiSelect: {
679
+ case Format.TypeFormat.MultiSelect: {
689
680
  const ids = extractTagIds(value);
690
681
  return ids || value;
691
682
  }
@@ -9,7 +9,7 @@ import { Filter, Query } from '@dxos/echo';
9
9
  import { TypedObject } from '@dxos/echo/internal';
10
10
  import { createEchoSchema } from '@dxos/echo/testing';
11
11
  import { live } from '@dxos/live-object';
12
- import { View } from '@dxos/schema';
12
+ import { ProjectionModel, View } from '@dxos/schema';
13
13
 
14
14
  import { Table } from '../types';
15
15
 
@@ -95,11 +95,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
95
95
 
96
96
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
97
97
  const schema = createEchoSchema(Test);
98
- const table = Table.make();
99
98
  const view = View.make({
100
99
  query: Query.select(Filter.type(schema)),
101
100
  jsonSchema: schema.jsonSchema,
102
- presentation: table,
103
101
  });
104
- return new TableModel({ view, schema: schema.jsonSchema, ...props });
102
+ const object = Table.make({ view });
103
+ const projection = new ProjectionModel(schema.jsonSchema, view.projection);
104
+ return new TableModel({ object, projection, ...props });
105
105
  };
@@ -6,7 +6,7 @@ import { signal } from '@preact/signals-core';
6
6
  import * as Predicate from 'effect/Predicate';
7
7
 
8
8
  import { Obj } from '@dxos/echo';
9
- import { FormatEnum, TypeEnum, getValue } from '@dxos/echo/internal';
9
+ import { Format, TypeEnum, getValue } from '@dxos/echo/internal';
10
10
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
11
11
  import {
12
12
  type DxGridCellValue,
@@ -120,12 +120,12 @@ export class TablePresentation<T extends TableRow = TableRow> {
120
120
  }
121
121
 
122
122
  switch (props.format) {
123
- case FormatEnum.Boolean:
124
- case FormatEnum.SingleSelect:
125
- case FormatEnum.MultiSelect: {
123
+ case Format.TypeFormat.Boolean:
124
+ case Format.TypeFormat.SingleSelect:
125
+ case Format.TypeFormat.MultiSelect: {
126
126
  return '';
127
127
  }
128
- case FormatEnum.Ref: {
128
+ case Format.TypeFormat.Ref: {
129
129
  if (!field.referencePath) {
130
130
  return ''; // TODO(burdon): Show error.
131
131
  }
@@ -201,7 +201,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
201
201
  }
202
202
 
203
203
  // References.
204
- if (props.format === FormatEnum.Ref && props.referenceSchema) {
204
+ if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
205
205
  const targetObj = getValue(obj, field.path)?.target;
206
206
  if (targetObj) {
207
207
  const dxn = Obj.getDXN(targetObj)?.toString();
@@ -210,7 +210,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
210
210
  }
211
211
 
212
212
  // Booleans.
213
- if (props.format === FormatEnum.Boolean) {
213
+ if (props.format === Format.TypeFormat.Boolean) {
214
214
  const value = getValue(obj, field.path);
215
215
  cell.accessoryHtml = tableControls.switch.render({
216
216
  colIndex,
@@ -221,7 +221,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
221
221
  }
222
222
 
223
223
  // Single-Selects.
224
- if (props.format === FormatEnum.SingleSelect) {
224
+ if (props.format === Format.TypeFormat.SingleSelect) {
225
225
  const value = getValue(obj, field.path);
226
226
  const options = this.model.projection.getFieldProjection(field.id).props.options;
227
227
  if (options) {
@@ -233,7 +233,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
233
233
  }
234
234
 
235
235
  // Multi-Selects.
236
- if (props.format === FormatEnum.MultiSelect) {
236
+ if (props.format === Format.TypeFormat.MultiSelect) {
237
237
  const values = getValue(obj, field.path) as string[] | undefined;
238
238
  const options = this.model.projection.getFieldProjection(field.id).props.options;
239
239
  if (options && values && values.length > 0) {
@@ -5,7 +5,7 @@
5
5
  import { type ReadonlySignal, type Signal, computed, signal } from '@preact/signals-core';
6
6
  import orderBy from 'lodash.orderby';
7
7
 
8
- import { FormatEnum, TypeEnum, getValue } from '@dxos/echo/internal';
8
+ import { Format, TypeEnum, getValue } from '@dxos/echo/internal';
9
9
  import { formatForDisplay } from '@dxos/react-ui-form';
10
10
  import {
11
11
  type FieldSortType,
@@ -52,19 +52,27 @@ type LocalSort = { type: 'cleared' } | { type: 'active'; sort: FieldSortType } |
52
52
  export class TableSorting<T extends TableRow> {
53
53
  private readonly _displayToDataIndex = new Map<number, number>();
54
54
  private readonly _rows: Signal<T[]>;
55
+ private readonly _getView: () => View.View;
56
+ private readonly _projection: ProjectionModel;
55
57
  private readonly _localSort = signal<LocalSort>(undefined);
56
58
  private readonly _isDirty: ReadonlySignal<boolean>;
57
59
  public readonly sortedRows: ReadonlySignal<T[]>;
58
60
 
59
- constructor(
60
- rows: Signal<T[]>,
61
- private readonly _view: View.View,
62
- private readonly _projection: ProjectionModel,
63
- ) {
61
+ constructor({
62
+ rows,
63
+ projection,
64
+ getView,
65
+ }: {
66
+ rows: Signal<T[]>;
67
+ getView: () => View.View;
68
+ projection: ProjectionModel;
69
+ }) {
64
70
  this._rows = rows;
71
+ this._getView = getView;
72
+ this._projection = projection;
65
73
  this._isDirty = computed(() => {
66
74
  const local = this._localSort.value;
67
- const viewSort = this._view.sort?.[0];
75
+ const viewSort = this._getView().sort?.[0];
68
76
  if (local?.type === 'cleared') {
69
77
  return viewSort !== undefined;
70
78
  }
@@ -92,7 +100,7 @@ export class TableSorting<T extends TableRow> {
92
100
  return local.sort;
93
101
  }
94
102
 
95
- return this._view.sort?.[0];
103
+ return this._getView().sort?.[0];
96
104
  }
97
105
 
98
106
  /**
@@ -137,10 +145,11 @@ export class TableSorting<T extends TableRow> {
137
145
 
138
146
  public save(): void {
139
147
  if (this._localSort.value !== undefined) {
148
+ const view = this._getView();
140
149
  if (this._localSort.value.type === 'active') {
141
- this._view.sort = [this._localSort.value.sort];
150
+ view.sort = [this._localSort.value.sort];
142
151
  } else {
143
- this._view.sort = [];
152
+ view.sort = [];
144
153
  }
145
154
  this._localSort.value = undefined;
146
155
  }
@@ -202,7 +211,7 @@ export class TableSorting<T extends TableRow> {
202
211
  * For single select fields, the sort value is determined by the index of the option in the
203
212
  * options array. This ensures sort order matches the configured option order.
204
213
  */
205
- if (format === FormatEnum.SingleSelect) {
214
+ if (format === Format.TypeFormat.SingleSelect) {
206
215
  const options = props.options;
207
216
  if (!options) {
208
217
  return undefined;
@@ -215,7 +224,7 @@ export class TableSorting<T extends TableRow> {
215
224
  // TODO(ZaymonFC): Should we format this returned value for display based on the referenced prop format?
216
225
  // Right now we're going to be sorting based on the raw values.
217
226
  // Maybe we need to recurse this function with the referenced prop format and field?
218
- if (format === FormatEnum.Ref && field.referencePath) {
227
+ if (format === Format.TypeFormat.Ref && field.referencePath) {
219
228
  const refValue = getValue(value.target, field.referencePath);
220
229
  return refValue;
221
230
  }
@@ -53,12 +53,12 @@ export class TableManager {
53
53
  await this.page.getByTestId(TABLE_SELECTORS.columnSort[direction]).click();
54
54
  }
55
55
 
56
- public async addColumn({ label, format }: { label: string; format: 'number' }): Promise<void> {
56
+ public async addColumn({ label, format }: { label: string; format: string }): Promise<void> {
57
57
  await this.page.getByTestId(TABLE_SELECTORS.newColumnButton).click();
58
58
  await this.page.getByRole('combobox').click();
59
- await this.page.getByLabel(`format ${format}`).click();
60
- await this.page.getByPlaceholder('Property label.').click();
61
- await this.page.getByPlaceholder('Property label.').fill(label);
62
- await this.page.getByRole('button', { name: 'save button' }).click();
59
+ await this.page.getByLabel(format).click();
60
+ await this.page.getByPlaceholder('Property label').click();
61
+ await this.page.getByPlaceholder('Property label').fill(label);
62
+ await this.page.getByRole('button', { name: 'Save' }).click();
63
63
  }
64
64
  }
@@ -130,7 +130,7 @@ test.describe('Table', () => {
130
130
  await table.grid.ready();
131
131
  const newColumnLabel = 'TEST LABEL';
132
132
 
133
- await table.addColumn({ label: newColumnLabel, format: 'number' });
133
+ await table.addColumn({ label: newColumnLabel, format: 'Number' });
134
134
 
135
135
  await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
136
136
  await page.close();
@@ -2,43 +2,49 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { useCallback, useMemo, useRef } from 'react';
5
+ import { type RefObject, useCallback, useMemo, useRef } from 'react';
6
6
 
7
- import { isMutable, toJsonSchema } from '@dxos/echo/internal';
7
+ import { type Type } from '@dxos/echo';
8
+ import { isMutable } from '@dxos/echo/internal';
8
9
  import { useGlobalFilteredObjects } from '@dxos/plugin-search';
9
10
  import { faker } from '@dxos/random';
10
- import { useClient } from '@dxos/react-client';
11
- import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
11
+ import { Filter, type Space, useQuery, useSchema } from '@dxos/react-client/echo';
12
12
  import { useClientProvider } from '@dxos/react-client/testing';
13
- import { ProjectionModel, View, getTypenameFromQuery } from '@dxos/schema';
13
+ import { type ProjectionModel, getTypenameFromQuery } from '@dxos/schema';
14
14
 
15
15
  import { type TableController } from '../components';
16
- import { useAddRow, useTableModel } from '../hooks';
17
- import { TablePresentation } from '../model';
16
+ import { useAddRow, useProjectionModel, useTableModel } from '../hooks';
17
+ import { type TableModel, TablePresentation } from '../model';
18
+ import { Table } from '../types';
18
19
 
19
20
  faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
20
21
 
22
+ export type TestTableModel<T extends Type.Entity.Any = Type.Entity.Any> = {
23
+ schema: T | undefined;
24
+ table: Table.Table | undefined;
25
+ projection: ProjectionModel | undefined;
26
+ tableRef: RefObject<TableController | null>;
27
+ model: TableModel | undefined;
28
+ presentation: TablePresentation | undefined;
29
+ space: Space | undefined;
30
+ handleInsertRow: () => void;
31
+ handleSaveView: () => void;
32
+ handleDeleteRows: (rowIndex: number, objects: any[]) => void;
33
+ handleDeleteColumn: (fieldId: string) => void;
34
+ };
35
+
21
36
  /**
22
37
  * Custom hook to create and manage a test table model for storybook demonstrations.
23
38
  * Provides table data, schema, and handlers for table operations.
24
39
  */
25
- export const useTestTableModel = () => {
26
- const client = useClient();
40
+ export const useTestTableModel = <T extends Type.Entity.Any = Type.Entity.Any>(): TestTableModel<T> => {
27
41
  const { space } = useClientProvider();
28
42
 
29
- const views = useQuery(space, Filter.type(View.View));
30
- const view = useMemo(() => views.at(0), [views]);
31
- const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
32
- const schema = useSchema(client, space, typename);
33
- const jsonSchema = useMemo(() => (schema ? toJsonSchema(schema) : undefined), [schema]);
34
-
35
- const projection = useMemo(() => {
36
- if (schema && view?.projection) {
37
- const projection = new ProjectionModel(toJsonSchema(schema), view.projection);
38
- projection.normalizeView();
39
- return projection;
40
- }
41
- }, [schema, view?.projection]);
43
+ const tables = useQuery(space, Filter.type(Table.Table));
44
+ const table = tables.at(0);
45
+ const typename = table?.view.target?.query ? getTypenameFromQuery(table.view.target.query.ast) : undefined;
46
+ const schema = useSchema<T>(space, typename);
47
+ const projection = useProjectionModel(schema, table);
42
48
 
43
49
  const features = useMemo(
44
50
  () => ({
@@ -82,8 +88,7 @@ export const useTestTableModel = () => {
82
88
  );
83
89
 
84
90
  const model = useTableModel({
85
- view,
86
- schema: jsonSchema,
91
+ object: table,
87
92
  projection,
88
93
  features,
89
94
  rows: filteredObjects,
@@ -97,7 +102,7 @@ export const useTestTableModel = () => {
97
102
  const handleInsertRow = useCallback(() => {
98
103
  const insertResult = model?.insertRow();
99
104
  tableRef.current?.handleInsertRowResult?.(insertResult);
100
- }, [model, tableRef.current]);
105
+ }, [model]);
101
106
 
102
107
  const handleSaveView = useCallback(() => {
103
108
  model?.saveView();
@@ -111,13 +116,12 @@ export const useTestTableModel = () => {
111
116
 
112
117
  return {
113
118
  schema,
114
- view,
119
+ table,
115
120
  projection,
116
121
  tableRef,
117
122
  model,
118
123
  presentation,
119
124
  space,
120
- client,
121
125
  handleInsertRow,
122
126
  handleSaveView,
123
127
  handleDeleteRows,
@@ -14,8 +14,8 @@ export const translations = [
14
14
  'create object label': 'Create table',
15
15
  'table name placeholder': 'Table name',
16
16
  'settings title': 'Table settings',
17
- 'table schema label': 'Record type',
18
- 'new schema': 'New record type',
17
+ 'table schema label': 'Type',
18
+ 'new schema': 'New type',
19
19
  'continue label': 'Continue',
20
20
  'create stack section label': 'Create table',
21
21
  'add row label': 'Add row',
@@ -0,0 +1,90 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Match from 'effect/Match';
6
+ import * as Schema from 'effect/Schema';
7
+ import * as SchemaAST from 'effect/SchemaAST';
8
+
9
+ import { Obj, Ref, Type } from '@dxos/echo';
10
+ import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
11
+ import { View, ViewAnnotation } from '@dxos/schema';
12
+
13
+ const TableSchema = Schema.Struct({
14
+ name: Schema.String.pipe(Schema.optional),
15
+
16
+ view: Type.Ref(View.View).pipe(FormInputAnnotation.set(false)),
17
+
18
+ sizes: Schema.Record({
19
+ // TODO(wittjosiah): Should be JsonPath.
20
+ key: Schema.String,
21
+ value: Schema.Number,
22
+ }).pipe(Schema.mutable, FormInputAnnotation.set(false)),
23
+ }).pipe(
24
+ Type.Obj({
25
+ typename: 'dxos.org/type/Table',
26
+ version: '0.2.0',
27
+ }),
28
+ LabelAnnotation.set(['name']),
29
+ ViewAnnotation.set(true),
30
+ );
31
+
32
+ export interface Table extends Schema.Schema.Type<typeof TableSchema> {}
33
+ export interface TableEncoded extends Schema.Schema.Encoded<typeof TableSchema> {}
34
+ export const Table: Schema.Schema<Table, TableEncoded> = TableSchema;
35
+
36
+ type MakeProps = Omit<Partial<Obj.MakeProps<typeof Table>>, 'view'> & {
37
+ view: View.View;
38
+ /** Required to auto-size columns. */
39
+ jsonSchema?: JsonSchemaType;
40
+ };
41
+
42
+ /**
43
+ * Make a table as a view of a data set.
44
+ */
45
+ export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table => {
46
+ const table = Obj.make(Table, { name, view: Ref.make(view), sizes });
47
+
48
+ // Preset sizes.
49
+ if (jsonSchema) {
50
+ const schema = Type.toEffectSchema(jsonSchema);
51
+ const properties = SchemaAST.getPropertySignatures(schema.ast);
52
+ for (const property of properties) {
53
+ const name = property.name.toString() as JsonPath;
54
+ if (sizes?.[name]) {
55
+ table.sizes[name] = sizes[name];
56
+ continue;
57
+ }
58
+
59
+ Match.type<SchemaAST.AST>().pipe(
60
+ Match.when({ _tag: 'BooleanKeyword' }, () => {
61
+ table.sizes[name] = 100;
62
+ }),
63
+ Match.when({ _tag: 'NumberKeyword' }, () => {
64
+ table.sizes[name] = 100;
65
+ }),
66
+ Match.orElse(() => {
67
+ // Noop.
68
+ }),
69
+ )(property.type);
70
+ }
71
+ }
72
+
73
+ return table;
74
+ };
75
+
76
+ //
77
+ // V1
78
+ //
79
+
80
+ export const TableV1 = Schema.Struct({
81
+ sizes: Schema.Record({
82
+ key: Schema.String,
83
+ value: Schema.Number,
84
+ }).pipe(Schema.mutable),
85
+ }).pipe(
86
+ Type.Obj({
87
+ typename: 'dxos.org/type/Table',
88
+ version: '0.1.0',
89
+ }),
90
+ );
@@ -2,4 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * as Table from './schema';
5
+ export * as Table from './Table';
@@ -2,11 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import type * as Schema from 'effect/Schema';
6
-
7
- import { Filter, Obj, Query } from '@dxos/echo';
8
- import { getTypename, toJsonSchema } from '@dxos/echo/internal';
9
- import type { JsonSchemaType } from '@dxos/echo/internal';
5
+ import { Filter, type JsonSchema, Obj, Query, Ref, Type } from '@dxos/echo';
10
6
  import {
11
7
  ProjectionModel,
12
8
  type SchemaPropertyDefinition,
@@ -31,21 +27,21 @@ export type TablePropertyDefinition = SchemaPropertyDefinition & Partial<Propert
31
27
  */
32
28
  // TODO(burdon): Remove variance.
33
29
  export const getBaseSchema = ({
30
+ schema,
34
31
  typename,
35
32
  properties,
36
33
  jsonSchema,
37
- schema,
38
34
  }: {
35
+ schema?: Type.Entity.Any;
39
36
  typename?: string;
40
37
  properties?: TablePropertyDefinition[];
41
- jsonSchema?: JsonSchemaType;
42
- schema?: Schema.Schema.AnyNoContext;
43
- }): { typename: string; jsonSchema: JsonSchemaType } => {
38
+ jsonSchema?: JsonSchema.JsonSchema;
39
+ }): { typename: string; jsonSchema: JsonSchema.JsonSchema } => {
44
40
  if (typename && properties) {
45
41
  const schema = getSchemaFromPropertyDefinitions(typename, properties);
46
42
  return { typename: schema.typename, jsonSchema: schema.jsonSchema };
47
43
  } else if (schema) {
48
- return { typename: getTypename(schema)!, jsonSchema: toJsonSchema(schema) };
44
+ return { typename: Type.getTypename(schema)!, jsonSchema: Type.toJsonSchema(schema) };
49
45
  } else if (typename && jsonSchema) {
50
46
  return { typename, jsonSchema };
51
47
  } else {
@@ -57,24 +53,23 @@ export const makeDynamicTable = ({
57
53
  jsonSchema,
58
54
  properties,
59
55
  }: {
60
- jsonSchema: JsonSchemaType;
56
+ jsonSchema: JsonSchema.JsonSchema;
61
57
  properties?: TablePropertyDefinition[];
62
- }): { projection: ProjectionModel; view: View.View } => {
63
- const table = Obj.make(Table.Table, { sizes: {} });
58
+ }): { projection: ProjectionModel; object: Table.Table } => {
64
59
  const view = View.make({
65
60
  query: Query.select(Filter.everything()),
66
61
  jsonSchema,
67
- presentation: table,
68
62
  ...(properties && { fields: properties.map((property) => property.name) }),
69
63
  });
64
+ const object = Obj.make(Table.Table, { view: Ref.make(view), sizes: {} });
70
65
 
71
66
  const projection = new ProjectionModel(jsonSchema, view.projection);
72
67
  projection.normalizeView();
73
68
  if (properties && projection.fields) {
74
- setProperties(view, projection, table, properties);
69
+ setProperties(view, projection, object, properties);
75
70
  }
76
71
 
77
- return { projection, view };
72
+ return { projection, object };
78
73
  };
79
74
 
80
75
  const setProperties = (