@dxos/react-ui-table 0.8.4-main.f9ba587 → 0.8.4-main.fd6878d

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 (122) hide show
  1. package/dist/lib/browser/chunk-L2LSB6KV.mjs +18 -0
  2. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-ZAOSIG3L.mjs +1814 -0
  4. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +658 -2359
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +123 -0
  9. package/dist/lib/browser/testing/index.mjs.map +7 -0
  10. package/dist/lib/browser/types/index.mjs +3 -5
  11. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
  12. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +1815 -0
  14. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +658 -2359
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +124 -0
  19. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  20. package/dist/lib/node-esm/types/index.mjs +3 -5
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
  26. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
  28. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  30. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/{relations.stories.d.ts → Relations.stories.d.ts} +2 -2
  32. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  34. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Table.d.ts +1 -0
  36. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
  38. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  40. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
  41. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -7
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
  46. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  47. package/dist/types/src/components/testing.d.ts +3 -40
  48. package/dist/types/src/components/testing.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useTableModel.d.ts +8 -7
  50. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  51. package/dist/types/src/index.d.ts +0 -1
  52. package/dist/types/src/index.d.ts.map +1 -1
  53. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  54. package/dist/types/src/model/selection-model.d.ts +2 -1
  55. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  56. package/dist/types/src/model/table-model.d.ts +17 -16
  57. package/dist/types/src/model/table-model.d.ts.map +1 -1
  58. package/dist/types/src/model/table-presentation.d.ts +2 -1
  59. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  60. package/dist/types/src/model/table-sorting.d.ts +4 -4
  61. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  62. package/dist/types/src/testing/index.d.ts +2 -0
  63. package/dist/types/src/testing/index.d.ts.map +1 -0
  64. package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
  65. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
  66. package/dist/types/src/translations.d.ts +2 -2
  67. package/dist/types/src/types/index.d.ts +1 -1
  68. package/dist/types/src/types/index.d.ts.map +1 -1
  69. package/dist/types/src/types/schema.d.ts +8 -0
  70. package/dist/types/src/types/schema.d.ts.map +1 -0
  71. package/dist/types/src/util/dynamic-table.d.ts +3 -4
  72. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  73. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  74. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  75. package/dist/types/src/util/table.d.ts +9 -11
  76. package/dist/types/src/util/table.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +50 -36
  79. package/src/components/Table/ColumnActionsMenu.tsx +1 -1
  80. package/src/components/Table/ColumnSettings.tsx +15 -11
  81. package/src/components/Table/CreateRefPanel.tsx +6 -4
  82. package/src/components/Table/DynamicTable.stories.tsx +3 -2
  83. package/src/components/Table/DynamicTable.tsx +17 -10
  84. package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +36 -35
  85. package/src/components/Table/RowActionsMenu.tsx +3 -3
  86. package/src/components/Table/Table.stories.tsx +99 -144
  87. package/src/components/Table/Table.tsx +87 -71
  88. package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
  89. package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
  90. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -27
  91. package/src/components/TableCellEditor/TableCellEditor.tsx +42 -27
  92. package/src/components/TableCellEditor/extension.ts +2 -2
  93. package/src/components/TableToolbar/TableToolbar.tsx +2 -2
  94. package/src/components/testing.ts +7 -24
  95. package/src/hooks/useTableModel.ts +21 -17
  96. package/src/index.ts +0 -1
  97. package/src/model/modal-controller.ts +1 -1
  98. package/src/model/selection-model.ts +6 -3
  99. package/src/model/table-model.test.ts +8 -8
  100. package/src/model/table-model.ts +68 -62
  101. package/src/model/table-presentation.test.ts +8 -8
  102. package/src/model/table-presentation.ts +35 -13
  103. package/src/model/table-sorting.ts +11 -11
  104. package/src/playwright/smoke.spec.ts +105 -14
  105. package/src/testing/index.ts +5 -0
  106. package/src/testing/useTestTableModel.ts +123 -0
  107. package/src/translations.ts +2 -2
  108. package/src/types/index.ts +2 -2
  109. package/src/types/schema.ts +16 -0
  110. package/src/util/dynamic-table.ts +20 -18
  111. package/src/util/schema.ts +1 -1
  112. package/src/util/table-buttons.ts +2 -1
  113. package/src/util/table-controls.ts +4 -1
  114. package/src/util/table.ts +37 -111
  115. package/dist/lib/browser/chunk-Q5QQXJUO.mjs +0 -27
  116. package/dist/lib/browser/chunk-Q5QQXJUO.mjs.map +0 -7
  117. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs +0 -29
  118. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs.map +0 -7
  119. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  120. package/dist/types/src/types/table.d.ts +0 -56
  121. package/dist/types/src/types/table.d.ts.map +0 -1
  122. package/src/types/table.ts +0 -27
@@ -2,44 +2,42 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { computed, effect, signal, type ReadonlySignal } from '@preact/signals-core';
5
+ import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-core';
6
6
 
7
- import { type Space } from '@dxos/client/echo';
8
7
  import { Resource } from '@dxos/context';
9
- import { Ref } from '@dxos/echo';
8
+ import { Obj, Ref } from '@dxos/echo';
10
9
  import {
11
10
  type FieldSortType,
12
11
  FormatEnum,
13
- getValue,
14
- setValue,
15
12
  type JsonProp,
16
- getSnapshot,
13
+ type JsonSchemaType,
17
14
  getSchema,
15
+ getSnapshot,
16
+ getValue,
17
+ setValue,
18
18
  toEffectSchema,
19
19
  } from '@dxos/echo-schema';
20
20
  import { invariant } from '@dxos/invariant';
21
21
  import { ObjectId } from '@dxos/keys';
22
22
  import { isLiveObject } from '@dxos/live-object';
23
+ import { fullyQualifiedId } from '@dxos/react-client/echo';
24
+ import { type Label } from '@dxos/react-ui';
23
25
  import { formatForEditing, parseValue } from '@dxos/react-ui-form';
24
26
  import {
25
27
  type DxGridAxisMeta,
26
- type DxGridPlaneRange,
27
28
  type DxGridPlanePosition,
29
+ type DxGridPlaneRange,
28
30
  type DxGridPosition,
29
31
  } from '@dxos/react-ui-grid';
30
- import {
31
- type ViewType,
32
- type ViewProjection,
33
- type PropertyType,
34
- validateSchema,
35
- type ValidationError,
36
- } from '@dxos/schema';
32
+ import { type DataType, ProjectionModel, type PropertyType, type ValidationError, validateSchema } from '@dxos/schema';
37
33
 
38
- import { type SelectionMode, SelectionModel } from './selection-model';
39
- import { TableSorting } from './table-sorting';
34
+ import { TableView } from '../types';
40
35
  import { touch } from '../util';
41
36
  import { extractTagIds } from '../util/tag';
42
37
 
38
+ import { type SelectionMode, SelectionModel } from './selection-model';
39
+ import { TableSorting } from './table-sorting';
40
+
43
41
  // Domain types for cell classification
44
42
  export type TableCellType = 'standard' | 'draft' | 'header';
45
43
 
@@ -57,7 +55,7 @@ export type TableRow = Record<JsonProp, any> & { id: string };
57
55
 
58
56
  export type TableRowAction = {
59
57
  id: string;
60
- translationKey: string;
58
+ label: Label;
61
59
  };
62
60
 
63
61
  export type TableFeatures = {
@@ -73,14 +71,15 @@ const defaultFeatures: TableFeatures = {
73
71
  };
74
72
 
75
73
  export type TableModelProps<T extends TableRow = TableRow> = {
76
- id?: string;
77
- space?: Space;
78
- view?: ViewType;
79
- projection: ViewProjection;
74
+ view: DataType.View;
75
+ schema: JsonSchemaType;
76
+ projection?: ProjectionModel;
80
77
  features?: Partial<TableFeatures>;
81
78
  sorting?: FieldSortType[];
79
+ initialSelection?: string[];
82
80
  pinnedRows?: { top: number[]; bottom: number[] };
83
81
  rowActions?: TableRowAction[];
82
+ onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
84
83
  onInsertRow?: (data?: any) => boolean;
85
84
  onDeleteRows?: (index: number, obj: T[]) => void;
86
85
  onDeleteColumn?: (fieldId: string) => void;
@@ -90,10 +89,9 @@ export type TableModelProps<T extends TableRow = TableRow> = {
90
89
  };
91
90
 
92
91
  export class TableModel<T extends TableRow = TableRow> extends Resource {
93
- private readonly _id: string | undefined;
94
- private readonly _space: Space | undefined;
95
- private readonly _view: ViewType | undefined;
96
- private readonly _projection: ViewProjection;
92
+ private readonly _view: DataType.View;
93
+ private readonly _projection: ProjectionModel;
94
+ private _table?: TableView;
97
95
 
98
96
  private readonly _visibleRange = signal<DxGridPlaneRange>({
99
97
  start: { row: 0, col: 0 },
@@ -118,12 +116,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
118
116
  private _columnMeta?: ReadonlySignal<DxGridAxisMeta>;
119
117
 
120
118
  constructor({
121
- id,
122
- space,
123
119
  view,
120
+ schema,
124
121
  projection,
125
122
  features = {},
126
123
  sorting = [],
124
+ initialSelection = [],
127
125
  pinnedRows = { top: [], bottom: [] },
128
126
  rowActions = [],
129
127
  onCellUpdate,
@@ -134,10 +132,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
134
132
  onRowAction,
135
133
  }: TableModelProps<T>) {
136
134
  super();
137
- this._id = id;
138
- this._space = space;
139
135
  this._view = view;
140
- this._projection = projection;
136
+ this._projection = projection ?? new ProjectionModel(schema, view.projection);
141
137
 
142
138
  // TODO(ZaymonFC): Use our more robust config merging module?
143
139
  this._features = { ...defaultFeatures, ...features };
@@ -147,13 +143,20 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
147
143
  'Single selection is not compatible with editable tables.',
148
144
  );
149
145
 
150
- this._sorting = new TableSorting(this._rows, this._view, projection);
146
+ this._sorting = new TableSorting(this._rows, this._view, this._projection);
151
147
 
152
148
  if (sorting.length > 0) {
153
149
  const [sort] = sorting;
154
150
  this._sorting.setSort(sort.fieldId, sort.direction);
155
151
  }
156
152
 
153
+ this._selection = new SelectionModel(
154
+ this._sorting.sortedRows,
155
+ this._features.selection.mode ?? 'multiple',
156
+ initialSelection,
157
+ () => this._onRowOrderChange?.(),
158
+ );
159
+
157
160
  this._pinnedRows = pinnedRows;
158
161
  this._rowActions = rowActions;
159
162
  this._onInsertRow = onInsertRow;
@@ -164,24 +167,25 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
164
167
  this._onRowAction = onRowAction;
165
168
  }
166
169
 
167
- public get id() {
168
- return this._id;
170
+ public get id(): string {
171
+ return fullyQualifiedId(this._view);
169
172
  }
170
173
 
171
- public get space() {
172
- return this._space;
174
+ public get view(): DataType.View {
175
+ return this._view;
173
176
  }
174
177
 
175
- public get view() {
176
- return this._view;
178
+ public get table(): TableView {
179
+ invariant(this._table, 'Model not initialized');
180
+ return this._table;
177
181
  }
178
182
 
179
- public get projection() {
183
+ public get projection(): ProjectionModel {
180
184
  return this._projection;
181
185
  }
182
186
 
183
187
  public get rows(): ReadonlySignal<T[]> {
184
- return this._sorting.sortedRows;
188
+ return this.sorting.sortedRows;
185
189
  }
186
190
 
187
191
  public get features(): TableFeatures {
@@ -191,7 +195,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
191
195
  /**
192
196
  * @reactive
193
197
  */
194
- public get sorting(): TableSorting<T> | undefined {
198
+ public get sorting(): TableSorting<T> {
195
199
  return this._sorting;
196
200
  }
197
201
 
@@ -199,11 +203,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
199
203
  * Gets the row data at the specified display index.
200
204
  */
201
205
  public getRowAt(displayIndex: number): T | undefined {
202
- if (displayIndex < 0 || displayIndex >= this._sorting.sortedRows.value.length) {
206
+ if (displayIndex < 0 || displayIndex >= this.sorting.sortedRows.value.length) {
203
207
  return undefined;
204
208
  }
205
209
 
206
- return this._sorting.sortedRows.value[displayIndex];
210
+ return this.sorting.sortedRows.value[displayIndex];
207
211
  }
208
212
 
209
213
  public get pinnedRows(): NonNullable<TableModelProps<T>['pinnedRows']> {
@@ -221,7 +225,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
221
225
 
222
226
  /** @reactive */
223
227
  public get isViewDirty(): boolean {
224
- return this._sorting.isDirty;
228
+ return this.sorting.isDirty;
225
229
  }
226
230
 
227
231
  public get rowActions(): TableRowAction[] {
@@ -233,19 +237,20 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
233
237
  //
234
238
 
235
239
  protected override async _open(): Promise<void> {
240
+ const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
241
+ invariant(Obj.instanceOf(TableView, presentation));
242
+ this._table = presentation;
243
+
236
244
  this.initializeColumnMeta();
237
245
  this.initializeEffects();
238
- this._selection = new SelectionModel(this._sorting.sortedRows, this._features.selection.mode ?? 'multiple', () =>
239
- this._onRowOrderChange?.(),
240
- );
241
246
  await this._selection.open(this._ctx);
242
247
  }
243
248
 
244
249
  private initializeColumnMeta(): void {
245
250
  this._columnMeta = computed(() => {
246
- const fields = this._view?.fields ?? [];
251
+ const fields = this._projection?.fields ?? [];
247
252
  const meta = Object.fromEntries(
248
- fields.map((field, index: number) => [index, { size: field?.size ?? 256, resizeable: true }]),
253
+ fields.map((field, index: number) => [index, { size: this.table.sizes[field.path] ?? 256, resizeable: true }]),
249
254
  );
250
255
 
251
256
  return {
@@ -258,7 +263,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
258
263
 
259
264
  private initializeEffects(): void {
260
265
  const rowOrderWatcher = effect(() => {
261
- touch(this._sorting.sortedRows.value);
266
+ touch(this.sorting.sortedRows.value);
262
267
  this._onRowOrderChange?.();
263
268
  });
264
269
  this._ctx.onDispose(rowOrderWatcher);
@@ -274,8 +279,8 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
274
279
  for (let row = start.row; row <= end.row; row++) {
275
280
  rowEffects.push(
276
281
  effect(() => {
277
- const obj = this._sorting.sortedRows.value[row];
278
- this._view?.fields.forEach((field) => touch(getValue(obj, field.path)));
282
+ const obj = this.sorting.sortedRows.value[row];
283
+ this._projection?.fields.forEach((field) => touch(getValue(obj, field.path)));
279
284
  this._onCellUpdate?.({ row, col: start.col, plane: 'grid' });
280
285
  }),
281
286
  );
@@ -329,7 +334,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
329
334
  return validationErrors.some((error) => error.path === fieldPath);
330
335
  }
331
336
 
332
- public getColumnCount = (): number => this._view?.fields.length ?? 0;
337
+ public getColumnCount = (): number => this._projection?.fields.length ?? 0;
333
338
 
334
339
  public insertRow = (): void => {
335
340
  const result = this._onInsertRow?.();
@@ -339,7 +344,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
339
344
  };
340
345
 
341
346
  private createDraftRow(): void {
342
- if (!this._view) {
347
+ if (!this._projection) {
343
348
  return;
344
349
  }
345
350
 
@@ -376,7 +381,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
376
381
  }
377
382
 
378
383
  private validateDraftRowData(data: T): ValidationError[] {
379
- const schema = toEffectSchema(this._projection.schema);
384
+ const schema = toEffectSchema(this._projection.baseSchema);
380
385
  return validateSchema(schema, data) || [];
381
386
  }
382
387
 
@@ -430,7 +435,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
430
435
 
431
436
  public getCellData = (cell: DxGridPosition): any => {
432
437
  const { col, row, plane } = cell;
433
- const fields = this._view?.fields ?? [];
438
+ const fields = this._projection?.fields ?? [];
434
439
  if (col < 0 || col >= fields.length) {
435
440
  return undefined;
436
441
  }
@@ -472,7 +477,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
472
477
  };
473
478
 
474
479
  public validateCellData = async ({ col, row, plane }: DxGridPosition, value: any): Promise<ValidationResult> => {
475
- const fields = this._view?.fields ?? [];
480
+ const fields = this._projection?.fields ?? [];
476
481
  if (col < 0 || col >= fields.length) {
477
482
  return { valid: false, error: 'Invalid column index' };
478
483
  }
@@ -494,6 +499,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
494
499
  setValue(snapshot, field.path, transformedValue);
495
500
 
496
501
  const validationErrors = this.validateDraftRowData(snapshot);
502
+ // TODO(thure): These errors sometimes result in a useless message like “is missing” (what is missing?)
497
503
  if (validationErrors.length > 0) {
498
504
  const error = validationErrors.find((err) => err.path === field.path);
499
505
  if (error) {
@@ -525,7 +531,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
525
531
 
526
532
  public setCellData = (cell: DxGridPosition, value: any): void => {
527
533
  const { col, row, plane } = cell;
528
- const fields = this._view?.fields ?? [];
534
+ const fields = this._projection?.fields ?? [];
529
535
  if (col < 0 || col >= fields.length) {
530
536
  return;
531
537
  }
@@ -560,7 +566,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
560
566
  */
561
567
  public updateCellData({ col, row }: DxGridPlanePosition, update: (value: any) => any): void {
562
568
  const dataRow = this._sorting.getDataIndex(row);
563
- const fields = this._view?.fields ?? [];
569
+ const fields = this._projection?.fields ?? [];
564
570
  const field = fields[col];
565
571
 
566
572
  const value = getValue(this._rows.value[dataRow], field.path);
@@ -600,11 +606,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
600
606
  //
601
607
 
602
608
  public deleteColumn(fieldId: string): void {
603
- if (!this._view) {
609
+ if (!this._projection) {
604
610
  return;
605
611
  }
606
612
 
607
- const field = this._view?.fields.find((field) => field.id === fieldId);
613
+ const field = this._projection?.fields.find((field) => field.id === fieldId);
608
614
  if (field && this._onDeleteColumn) {
609
615
  this._onDeleteColumn(field.id);
610
616
  }
@@ -615,12 +621,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
615
621
  //
616
622
 
617
623
  public setColumnWidth(columnIndex: number, width: number): void {
618
- const fields = this._view?.fields ?? [];
624
+ const fields = this._projection?.fields ?? [];
619
625
  if (columnIndex < fields.length) {
620
626
  const newWidth = Math.max(0, width);
621
627
  const field = fields[columnIndex];
622
628
  if (field) {
623
- field.size = newWidth;
629
+ this.table.sizes[field.path] = newWidth;
624
630
  }
625
631
  }
626
632
  }
@@ -3,17 +3,18 @@
3
3
  //
4
4
 
5
5
  import { Schema } from 'effect';
6
- import { describe, expect, it, beforeEach } from 'vitest';
6
+ import { beforeEach, describe, expect, it } from 'vitest';
7
7
 
8
- import { Obj, Ref } from '@dxos/echo';
8
+ import { Obj } from '@dxos/echo';
9
9
  import { TypedObject } from '@dxos/echo-schema';
10
10
  import { live } from '@dxos/live-object';
11
11
  import { createEchoSchema } from '@dxos/live-object/testing';
12
- import { createView, ViewProjection } from '@dxos/schema';
12
+ import { createView } from '@dxos/schema';
13
+
14
+ import { TableView } from '../types';
13
15
 
14
16
  import { TableModel, type TableModelProps } from './table-model';
15
17
  import { TablePresentation } from './table-presentation';
16
- import { TableType } from '../types';
17
18
 
18
19
  describe('TablePresentation', () => {
19
20
  describe('row reactivity', () => {
@@ -97,8 +98,7 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
97
98
 
98
99
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
99
100
  const schema = createEchoSchema(Test);
100
- const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
101
- const projection = new ViewProjection(schema.jsonSchema, view);
102
- const table = Obj.make(TableType, { view: Ref.make(view) });
103
- return new TableModel({ id: table.id, view, projection, ...props });
101
+ const table = Obj.make(TableView, { sizes: {} });
102
+ const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
103
+ return new TableModel({ view, schema: schema.jsonSchema, ...props });
104
104
  };
@@ -6,7 +6,7 @@ import { signal } from '@preact/signals-core';
6
6
  import { isNotNullable } from 'effect/Predicate';
7
7
 
8
8
  import { Obj } from '@dxos/echo';
9
- import { FormatEnum, getValue, TypeEnum } from '@dxos/echo-schema';
9
+ import { FormatEnum, TypeEnum, getValue } from '@dxos/echo-schema';
10
10
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
11
11
  import {
12
12
  type DxGridCellValue,
@@ -16,12 +16,13 @@ import {
16
16
  toPlaneCellIndex,
17
17
  } from '@dxos/react-ui-grid';
18
18
  import { mx } from '@dxos/react-ui-theme';
19
- import { VIEW_FIELD_LIMIT, type FieldType } from '@dxos/schema';
19
+ import { type FieldType, VIEW_FIELD_LIMIT } from '@dxos/schema';
20
20
 
21
- import { type SelectionMode } from './selection-model';
22
- import { type TableRow, type TableModel } from './table-model';
23
21
  import { tableButtons, tableControls } from '../util';
24
22
 
23
+ import { type SelectionMode } from './selection-model';
24
+ import { type TableModel, type TableRow } from './table-model';
25
+
25
26
  /**
26
27
  * Presentation layer for a table component, handling cell rendering and grid display logic.
27
28
  * Manages rendering of table cells, headers, selection columns, and action columns across
@@ -67,6 +68,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
67
68
  case 'frozenRowsEnd':
68
69
  cells = this.getDraftRowCells(range);
69
70
  break;
71
+ case 'fixedEndStart':
72
+ cells = this.getDraftSelectCells(range);
73
+ break;
70
74
  case 'fixedEndEnd':
71
75
  cells = this.getDraftActionCells(range);
72
76
  break;
@@ -186,7 +190,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
186
190
  .map(getLabel)
187
191
  .filter(isNotNullable)
188
192
  .map((title) => {
189
- return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
193
+ return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
190
194
  })
191
195
  .join('');
192
196
 
@@ -199,7 +203,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
199
203
  const targetObj = getValue(obj, field.path)?.target;
200
204
  if (targetObj) {
201
205
  const dxn = Obj.getDXN(targetObj)?.toString();
202
- cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
206
+ cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-2 block-start-[.2rem]" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
203
207
  }
204
208
  }
205
209
 
@@ -254,7 +258,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
254
258
 
255
259
  private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
256
260
  const cells: DxGridPlaneCells = {};
257
- const fields = this.model.view?.fields ?? [];
261
+ const fields = this.model.projection?.fields ?? [];
258
262
 
259
263
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
260
264
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
@@ -272,12 +276,13 @@ export class TablePresentation<T extends TableRow = TableRow> {
272
276
 
273
277
  private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
274
278
  const cells: DxGridPlaneCells = {};
275
- const fields = this.model.view?.fields ?? [];
279
+ const fields = this.model.projection?.fields ?? [];
276
280
  const draftRows = this.model.draftRows.value;
277
281
 
278
282
  for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
279
283
  const draftRow = draftRows[row];
280
284
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
285
+ const cellIndex = toPlaneCellIndex({ col, row });
281
286
  const field = fields[col];
282
287
  if (!field) {
283
288
  continue;
@@ -286,7 +291,6 @@ export class TablePresentation<T extends TableRow = TableRow> {
286
291
  this.createDataCell(cells, draftRow.data, field, col, row);
287
292
 
288
293
  if (this.model.hasDraftRowValidationError(row, field.path)) {
289
- const cellIndex = toPlaneCellIndex({ col, row });
290
294
  const cellValue = cells[cellIndex];
291
295
  if (cellValue) {
292
296
  const existingClasses = cellValue.className || '';
@@ -294,6 +298,8 @@ export class TablePresentation<T extends TableRow = TableRow> {
294
298
  cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
295
299
  }
296
300
  }
301
+
302
+ cells[cellIndex].className += ' !bg-toolbarSurface';
297
303
  }
298
304
  }
299
305
 
@@ -302,9 +308,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
302
308
 
303
309
  private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
304
310
  const cells: DxGridPlaneCells = {};
305
- const fields = this.model.view?.fields ?? [];
311
+ const fields = this.model.projection?.fields ?? [];
306
312
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
307
- const { field, props } = this.getFieldProjection(fields[col].id);
313
+ const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
308
314
  const sorting = this.model.sorting?.sorting;
309
315
  const direction = sorting?.fieldId === field.id ? sorting.direction : undefined;
310
316
 
@@ -386,7 +392,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
386
392
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
387
393
  accessoryHtml: this.model.features.schemaEditable
388
394
  ? tableButtons.addColumn.render({
389
- disabled: (this.model.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
395
+ disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
390
396
  })
391
397
  : undefined,
392
398
  className: '!bg-toolbarSurface',
@@ -408,6 +414,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
408
414
  value: '',
409
415
  readonly: true,
410
416
  accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
417
+ className: '!bg-toolbarSurface',
418
+ };
419
+ }
420
+
421
+ return cells;
422
+ }
423
+
424
+ private getDraftSelectCells(range: DxGridPlaneRange): DxGridPlaneCells {
425
+ const cells: DxGridPlaneCells = {};
426
+ const draftRows = this.model.draftRows.value;
427
+
428
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
429
+ cells[toPlaneCellIndex({ col: 0, row })] = {
430
+ value: '',
431
+ readonly: true,
432
+ className: '!bg-toolbarSurface',
411
433
  };
412
434
  }
413
435
 
@@ -427,7 +449,7 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
427
449
  switch (selectionMode) {
428
450
  case 'single':
429
451
  // TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
430
- return ['!bg-currentRelated hover:bg-hoverSurface', '!cursor-pointer'];
452
+ return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
431
453
  case 'multiple':
432
454
  return ['!bg-gridCellSelected'];
433
455
  }
@@ -2,12 +2,12 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { computed, signal, type Signal, type ReadonlySignal } from '@preact/signals-core';
5
+ import { type ReadonlySignal, type Signal, computed, signal } from '@preact/signals-core';
6
6
  import orderBy from 'lodash.orderby';
7
7
 
8
- import { getValue, FormatEnum, TypeEnum, type SortDirectionType, type FieldSortType } from '@dxos/echo-schema';
8
+ import { type FieldSortType, FormatEnum, type SortDirectionType, TypeEnum, getValue } from '@dxos/echo-schema';
9
9
  import { formatForDisplay } from '@dxos/react-ui-form';
10
- import type { PropertyType, FieldType, ViewProjection, ViewType } from '@dxos/schema';
10
+ import type { DataType, FieldType, ProjectionModel, PropertyType } from '@dxos/schema';
11
11
 
12
12
  import { type TableRow } from './table-model';
13
13
 
@@ -49,13 +49,13 @@ export class TableSorting<T extends TableRow> {
49
49
 
50
50
  constructor(
51
51
  rows: Signal<T[]>,
52
- private readonly _view: ViewType | undefined,
53
- private readonly _projection: ViewProjection,
52
+ private readonly _view: DataType.View,
53
+ private readonly _projection: ProjectionModel,
54
54
  ) {
55
55
  this._rows = rows;
56
56
  this._isDirty = computed(() => {
57
57
  const local = this._localSort.value;
58
- const viewSort = this._view?.query.sort?.[0];
58
+ const viewSort = this._view.query.sort?.[0];
59
59
  if (local?.type === 'cleared') {
60
60
  return viewSort !== undefined;
61
61
  }
@@ -83,7 +83,7 @@ export class TableSorting<T extends TableRow> {
83
83
  return local.sort;
84
84
  }
85
85
 
86
- return this._view?.query.sort?.[0];
86
+ return this._view.query.sort?.[0];
87
87
  }
88
88
 
89
89
  /**
@@ -106,7 +106,7 @@ export class TableSorting<T extends TableRow> {
106
106
  }
107
107
 
108
108
  public toggleSort(fieldId: string): void {
109
- if (!this._view || !this.sorting || this.sorting.fieldId !== fieldId) {
109
+ if (!this.sorting || this.sorting.fieldId !== fieldId) {
110
110
  return;
111
111
  }
112
112
 
@@ -127,7 +127,7 @@ export class TableSorting<T extends TableRow> {
127
127
  }
128
128
 
129
129
  public save(): void {
130
- if (this._view && this._localSort.value !== undefined) {
130
+ if (this._localSort.value !== undefined) {
131
131
  if (this._localSort.value.type === 'active') {
132
132
  this._view.query.sort = [this._localSort.value.sort];
133
133
  } else {
@@ -145,11 +145,11 @@ export class TableSorting<T extends TableRow> {
145
145
  return computed(() => {
146
146
  this._displayToDataIndex.clear();
147
147
  const sort = this.sorting;
148
- if (!sort || !this._view) {
148
+ if (!sort) {
149
149
  return this._rows.value;
150
150
  }
151
151
 
152
- const field = this._view.fields.find((f) => f.id === sort.fieldId);
152
+ const field = this._projection.fields.find((f) => f.id === sort.fieldId);
153
153
  if (!field) {
154
154
  return this._rows.value;
155
155
  }