@dxos/react-ui-table 0.8.4-main.72ec0f3 → 0.8.4-main.7ace549

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 (88) hide show
  1. package/dist/lib/browser/chunk-5XWSB3JP.mjs +71 -0
  2. package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-GS2UNGJK.mjs → chunk-KNC7XYT6.mjs} +116 -108
  4. package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +25 -13
  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 +14 -27
  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-CWSGS3Y2.mjs} +116 -108
  12. package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-MKTMOY46.mjs +72 -0
  14. package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +25 -13
  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 +14 -27
  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/DynamicTable.d.ts.map +1 -1
  22. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +2 -2
  23. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  24. package/dist/types/src/components/Table/Relations.stories.d.ts +7 -6
  25. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  26. package/dist/types/src/components/Table/Table.stories.d.ts +67 -4
  27. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  29. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +2 -2
  30. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/testing.d.ts +0 -2
  32. package/dist/types/src/components/testing.d.ts.map +1 -1
  33. package/dist/types/src/hooks/index.d.ts +2 -1
  34. package/dist/types/src/hooks/index.d.ts.map +1 -1
  35. package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
  36. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  37. package/dist/types/src/hooks/useTableModel.d.ts +4 -5
  38. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  39. package/dist/types/src/model/table-model.d.ts +6 -8
  40. package/dist/types/src/model/table-model.d.ts.map +1 -1
  41. package/dist/types/src/model/table-sorting.d.ts +7 -3
  42. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  43. package/dist/types/src/playwright/TableManager.d.ts +1 -1
  44. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  45. package/dist/types/src/testing/useTestTableModel.d.ts +21 -15
  46. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  47. package/dist/types/src/translations.d.ts +2 -2
  48. package/dist/types/src/types/Table.d.ts +28 -0
  49. package/dist/types/src/types/Table.d.ts.map +1 -0
  50. package/dist/types/src/types/index.d.ts +1 -1
  51. package/dist/types/src/types/index.d.ts.map +1 -1
  52. package/dist/types/src/util/dynamic-table.d.ts +7 -6
  53. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  54. package/dist/types/tsconfig.tsbuildinfo +1 -1
  55. package/package.json +38 -38
  56. package/src/components/Table/DynamicTable.stories.tsx +7 -7
  57. package/src/components/Table/DynamicTable.tsx +12 -4
  58. package/src/components/Table/Relations.stories.tsx +9 -14
  59. package/src/components/Table/Table.stories.tsx +47 -42
  60. package/src/components/TableCellEditor/FormCellEditor.tsx +2 -2
  61. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +5 -4
  62. package/src/components/TableCellEditor/TableCellEditor.tsx +4 -4
  63. package/src/components/TableToolbar/TableToolbar.tsx +1 -1
  64. package/src/components/testing.ts +3 -3
  65. package/src/hooks/index.ts +2 -1
  66. package/src/hooks/useProjectionModel.ts +19 -0
  67. package/src/hooks/useTableModel.ts +10 -12
  68. package/src/model/table-model.test.ts +4 -4
  69. package/src/model/table-model.ts +27 -36
  70. package/src/model/table-presentation.test.ts +4 -4
  71. package/src/model/table-presentation.ts +9 -9
  72. package/src/model/table-sorting.ts +21 -12
  73. package/src/playwright/TableManager.ts +3 -3
  74. package/src/playwright/smoke.spec.ts +1 -1
  75. package/src/testing/useTestTableModel.ts +32 -24
  76. package/src/translations.ts +2 -2
  77. package/src/types/Table.ts +89 -0
  78. package/src/types/index.ts +1 -1
  79. package/src/util/dynamic-table.ts +9 -12
  80. package/dist/lib/browser/chunk-2PFAPQL4.mjs +0 -66
  81. package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +0 -7
  82. package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +0 -7
  83. package/dist/lib/node-esm/chunk-6POOFFYF.mjs +0 -67
  84. package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +0 -7
  85. package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +0 -7
  86. package/dist/types/src/types/schema.d.ts +0 -28
  87. package/dist/types/src/types/schema.d.ts.map +0 -1
  88. package/src/types/schema.ts +0 -73
@@ -7,17 +7,16 @@ import orderBy from 'lodash.orderby';
7
7
  import { useEffect, useMemo, useState } from 'react';
8
8
 
9
9
  import { Obj } from '@dxos/echo';
10
- import { type JsonSchemaType } from '@dxos/echo/internal';
11
10
  import { type Live } from '@dxos/live-object';
12
11
  import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
13
- import { type ProjectionModel, type View } from '@dxos/schema';
12
+ import { type ProjectionModel } from '@dxos/schema';
14
13
  import { isNonNullable } from '@dxos/util';
15
14
 
16
15
  import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
16
+ import { type Table } from '../types';
17
17
 
18
18
  export type UseTableModelParams<T extends TableRow = TableRow> = {
19
- view?: View.View;
20
- schema?: JsonSchemaType;
19
+ object?: Table.Table;
21
20
  projection?: ProjectionModel;
22
21
  rows?: Live<T>[];
23
22
  rowActions?: TableRowAction[];
@@ -29,8 +28,7 @@ export type UseTableModelParams<T extends TableRow = TableRow> = {
29
28
  >;
30
29
 
31
30
  export const useTableModel = <T extends TableRow = TableRow>({
32
- view,
33
- schema,
31
+ object,
34
32
  projection,
35
33
  rows,
36
34
  rowActions,
@@ -39,20 +37,19 @@ export const useTableModel = <T extends TableRow = TableRow>({
39
37
  onRowAction,
40
38
  ...props
41
39
  }: UseTableModelParams<T>): TableModel<T> | undefined => {
42
- const selected = useSelected(view && Obj.getDXN(view).toString(), 'multi');
43
- const initialSelection = useMemo(() => selected, [view]);
40
+ const selected = useSelected(object && Obj.getDXN(object).toString(), 'multi');
41
+ const initialSelection = useMemo(() => selected, [object]);
44
42
 
45
43
  const [model, setModel] = useState<TableModel<T>>();
46
44
  useEffect(() => {
47
- if (!view || !schema) {
45
+ if (!object || !projection) {
48
46
  return;
49
47
  }
50
48
 
51
49
  let model: TableModel<T> | undefined;
52
50
  const t = setTimeout(async () => {
53
51
  model = new TableModel<T>({
54
- view,
55
- schema,
52
+ object,
56
53
  projection,
57
54
  features,
58
55
  rowActions,
@@ -68,7 +65,8 @@ export const useTableModel = <T extends TableRow = TableRow>({
68
65
  clearTimeout(t);
69
66
  void model?.close();
70
67
  };
71
- }, [view, schema, projection, features, rowActions, initialSelection]); // TODO(burdon): Trigger if callbacks change?
68
+ // TODO(burdon): Trigger if callbacks change?
69
+ }, [object, projection, features, rowActions, initialSelection]);
72
70
 
73
71
  // Update data.
74
72
  useEffect(() => {
@@ -12,7 +12,7 @@ import { updateCounter } from '@dxos/echo/testing';
12
12
  import { createEchoSchema } from '@dxos/echo/testing';
13
13
  import { registerSignalsRuntime } from '@dxos/echo-signals';
14
14
  import { live } from '@dxos/live-object';
15
- import { View } from '@dxos/schema';
15
+ import { ProjectionModel, View } from '@dxos/schema';
16
16
 
17
17
  import { Table } from '../types';
18
18
 
@@ -112,11 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
112
112
 
113
113
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
114
114
  const schema = createEchoSchema(Test);
115
- const table = Table.make();
116
115
  const view = View.make({
117
116
  query: Query.select(Filter.type(schema)),
118
117
  jsonSchema: schema.jsonSchema,
119
- presentation: table,
120
118
  });
121
- return new TableModel({ view, schema: schema.jsonSchema, ...props });
119
+ const object = Table.make({ view });
120
+ const projection = new ProjectionModel(schema.jsonSchema, view.projection);
121
+ return new TableModel({ object, projection, ...props });
122
122
  };
@@ -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,7 +139,11 @@ 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
149
  const [sort] = sorting;
@@ -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();
59
+ await this.page.getByLabel(format).click();
60
60
  await this.page.getByPlaceholder('Property label.').click();
61
61
  await this.page.getByPlaceholder('Property label.').fill(label);
62
- await this.page.getByRole('button', { name: 'save button' }).click();
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,52 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { useCallback, useMemo, useRef } from 'react';
5
+ import type * as Schema from 'effect/Schema';
6
+ import { type RefObject, useCallback, useMemo, useRef } from 'react';
6
7
 
7
- import { isMutable, toJsonSchema } from '@dxos/echo/internal';
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 { type Client, useClient } from '@dxos/react-client';
12
+ import { Filter, type Space, useQuery, useSchema } from '@dxos/react-client/echo';
12
13
  import { useClientProvider } from '@dxos/react-client/testing';
13
- import { ProjectionModel, View, getTypenameFromQuery } from '@dxos/schema';
14
+ import { type ProjectionModel, getTypenameFromQuery } from '@dxos/schema';
14
15
 
15
16
  import { type TableController } from '../components';
16
- import { useAddRow, useTableModel } from '../hooks';
17
- import { TablePresentation } from '../model';
17
+ import { useAddRow, useProjectionModel, useTableModel } from '../hooks';
18
+ import { type TableModel, TablePresentation } from '../model';
19
+ import { Table } from '../types';
18
20
 
19
21
  faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
20
22
 
23
+ export type TestTableModel = {
24
+ schema: Schema.Schema.AnyNoContext | undefined;
25
+ table: Table.Table | undefined;
26
+ projection: ProjectionModel | undefined;
27
+ tableRef: RefObject<TableController | null>;
28
+ model: TableModel | undefined;
29
+ presentation: TablePresentation | undefined;
30
+ space: Space | undefined;
31
+ client: Client | undefined;
32
+ handleInsertRow: () => void;
33
+ handleSaveView: () => void;
34
+ handleDeleteRows: (rowIndex: number, objects: any[]) => void;
35
+ handleDeleteColumn: (fieldId: string) => void;
36
+ };
37
+
21
38
  /**
22
39
  * Custom hook to create and manage a test table model for storybook demonstrations.
23
40
  * Provides table data, schema, and handlers for table operations.
24
41
  */
25
- export const useTestTableModel = () => {
42
+ export const useTestTableModel = (): TestTableModel => {
26
43
  const client = useClient();
27
44
  const { space } = useClientProvider();
28
45
 
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;
46
+ const tables = useQuery(space, Filter.type(Table.Table));
47
+ const table = tables.at(0);
48
+ const typename = table?.view.target?.query ? getTypenameFromQuery(table.view.target.query.ast) : undefined;
32
49
  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]);
50
+ const projection = useProjectionModel(schema, table);
42
51
 
43
52
  const features = useMemo(
44
53
  () => ({
@@ -82,8 +91,7 @@ export const useTestTableModel = () => {
82
91
  );
83
92
 
84
93
  const model = useTableModel({
85
- view,
86
- schema: jsonSchema,
94
+ object: table,
87
95
  projection,
88
96
  features,
89
97
  rows: filteredObjects,
@@ -97,7 +105,7 @@ export const useTestTableModel = () => {
97
105
  const handleInsertRow = useCallback(() => {
98
106
  const insertResult = model?.insertRow();
99
107
  tableRef.current?.handleInsertRowResult?.(insertResult);
100
- }, [model, tableRef.current]);
108
+ }, [model]);
101
109
 
102
110
  const handleSaveView = useCallback(() => {
103
111
  model?.saveView();
@@ -111,7 +119,7 @@ export const useTestTableModel = () => {
111
119
 
112
120
  return {
113
121
  schema,
114
- view,
122
+ table,
115
123
  projection,
116
124
  tableRef,
117
125
  model,
@@ -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,89 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Match from 'effect/Match';
6
+ import * as Schema from 'effect/Schema';
7
+
8
+ import { Obj, Ref, Type } from '@dxos/echo';
9
+ import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
10
+ import { type SimpleType } from '@dxos/effect';
11
+ import { View, ViewAnnotation, getSchemaProperties } 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 = getSchemaProperties(schema.ast, {});
52
+ for (const property of properties) {
53
+ if (sizes?.[property.name]) {
54
+ table.sizes[property.name] = sizes[property.name];
55
+ continue;
56
+ }
57
+
58
+ Match.type<SimpleType>().pipe(
59
+ Match.when('boolean', () => {
60
+ table.sizes[property.name as JsonPath] = 100;
61
+ }),
62
+ Match.when('number', () => {
63
+ table.sizes[property.name as JsonPath] = 100;
64
+ }),
65
+ Match.orElse(() => {
66
+ // Noop.
67
+ }),
68
+ )(property.type);
69
+ }
70
+ }
71
+
72
+ return table;
73
+ };
74
+
75
+ //
76
+ // V1
77
+ //
78
+
79
+ export const TableV1 = Schema.Struct({
80
+ sizes: Schema.Record({
81
+ key: Schema.String,
82
+ value: Schema.Number,
83
+ }).pipe(Schema.mutable),
84
+ }).pipe(
85
+ Type.Obj({
86
+ typename: 'dxos.org/type/Table',
87
+ version: '0.1.0',
88
+ }),
89
+ );
@@ -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';