@dxos/react-ui-table 0.8.4-main.28f8d3d → 0.8.4-main.406dc2a

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 (108) hide show
  1. package/dist/lib/browser/chunk-4K7JPALT.mjs +66 -0
  2. package/dist/lib/browser/chunk-4K7JPALT.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-ZAOSIG3L.mjs → chunk-OZTXLZGM.mjs} +526 -536
  4. package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +373 -481
  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 +11 -8
  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-MK6OVUF6.mjs +67 -0
  12. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-ZOFFI3JJ.mjs → chunk-S56Z4V4X.mjs} +526 -536
  14. package/dist/lib/node-esm/chunk-S56Z4V4X.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +373 -481
  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 +11 -8
  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 +2 -2
  22. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +51 -3
  24. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/Relations.stories.d.ts +104 -5
  26. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/Table.d.ts +8 -5
  28. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Table.stories.d.ts +41 -3
  30. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -6
  32. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  33. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -13
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  38. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  39. package/dist/types/src/components/testing.d.ts +3 -3
  40. package/dist/types/src/components/testing.d.ts.map +1 -1
  41. package/dist/types/src/hooks/useAddRow.d.ts +3 -2
  42. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  43. package/dist/types/src/hooks/useTableModel.d.ts +1 -1
  44. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  45. package/dist/types/src/model/modal-controller.d.ts +0 -7
  46. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  47. package/dist/types/src/model/table-model.d.ts +7 -6
  48. package/dist/types/src/model/table-model.d.ts.map +1 -1
  49. package/dist/types/src/model/table-presentation.d.ts +1 -1
  50. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  51. package/dist/types/src/model/table-sorting.d.ts +1 -2
  52. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  53. package/dist/types/src/testing/useTestTableModel.d.ts +3 -22
  54. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  55. package/dist/types/src/types/index.d.ts +1 -1
  56. package/dist/types/src/types/index.d.ts.map +1 -1
  57. package/dist/types/src/types/schema.d.ts +25 -5
  58. package/dist/types/src/types/schema.d.ts.map +1 -1
  59. package/dist/types/src/util/dynamic-table.d.ts +4 -5
  60. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  61. package/dist/types/src/util/index.d.ts +0 -1
  62. package/dist/types/src/util/index.d.ts.map +1 -1
  63. package/dist/types/src/util/schema.d.ts +1 -1
  64. package/dist/types/src/util/schema.d.ts.map +1 -1
  65. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  66. package/dist/types/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +51 -50
  68. package/src/components/Table/DynamicTable.stories.tsx +12 -9
  69. package/src/components/Table/DynamicTable.tsx +4 -7
  70. package/src/components/Table/Relations.stories.tsx +162 -38
  71. package/src/components/Table/Table.stories.tsx +59 -52
  72. package/src/components/Table/Table.tsx +109 -90
  73. package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
  74. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +22 -63
  75. package/src/components/TableCellEditor/TableCellEditor.tsx +62 -208
  76. package/src/components/TableCellEditor/index.ts +1 -0
  77. package/src/components/testing.ts +2 -2
  78. package/src/hooks/useAddRow.ts +7 -5
  79. package/src/hooks/useTableModel.ts +1 -1
  80. package/src/model/modal-controller.ts +0 -28
  81. package/src/model/table-model.test.ts +18 -10
  82. package/src/model/table-model.ts +26 -17
  83. package/src/model/table-presentation.test.ts +21 -20
  84. package/src/model/table-presentation.ts +58 -39
  85. package/src/model/table-sorting.ts +15 -6
  86. package/src/playwright/smoke.spec.ts +46 -46
  87. package/src/testing/useTestTableModel.ts +9 -6
  88. package/src/types/index.ts +1 -1
  89. package/src/types/schema.ts +63 -6
  90. package/src/util/dynamic-table.ts +11 -11
  91. package/src/util/index.ts +0 -1
  92. package/src/util/schema.ts +4 -4
  93. package/src/util/table-buttons.ts +3 -3
  94. package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
  95. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
  96. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +0 -7
  97. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
  98. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
  99. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +0 -7
  100. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  101. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  102. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  103. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  104. package/dist/types/src/util/table.d.ts +0 -12
  105. package/dist/types/src/util/table.d.ts.map +0 -1
  106. package/src/components/Table/CreateRefPanel.tsx +0 -86
  107. package/src/components/TableCellEditor/extension.ts +0 -61
  108. package/src/util/table.ts +0 -53
@@ -7,19 +7,17 @@ import { type ReadonlySignal, computed, effect, signal } from '@preact/signals-c
7
7
  import { Resource } from '@dxos/context';
8
8
  import { Obj, Ref } from '@dxos/echo';
9
9
  import {
10
- type FieldSortType,
11
10
  FormatEnum,
12
11
  type JsonProp,
13
12
  type JsonSchemaType,
14
13
  getSchema,
15
- getSnapshot,
16
14
  getValue,
17
15
  setValue,
18
16
  toEffectSchema,
19
- } from '@dxos/echo-schema';
17
+ } from '@dxos/echo/internal';
20
18
  import { invariant } from '@dxos/invariant';
21
19
  import { ObjectId } from '@dxos/keys';
22
- import { isLiveObject } from '@dxos/live-object';
20
+ import { getSnapshot, isLiveObject } from '@dxos/live-object';
23
21
  import { fullyQualifiedId } from '@dxos/react-client/echo';
24
22
  import { type Label } from '@dxos/react-ui';
25
23
  import { formatForEditing, parseValue } from '@dxos/react-ui-form';
@@ -29,9 +27,16 @@ import {
29
27
  type DxGridPlaneRange,
30
28
  type DxGridPosition,
31
29
  } from '@dxos/react-ui-grid';
32
- import { type DataType, ProjectionModel, type PropertyType, type ValidationError, validateSchema } from '@dxos/schema';
30
+ import {
31
+ type DataType,
32
+ type FieldSortType,
33
+ ProjectionModel,
34
+ type PropertyType,
35
+ type ValidationError,
36
+ validateSchema,
37
+ } from '@dxos/schema';
33
38
 
34
- import { TableView } from '../types';
39
+ import { Table } from '../types';
35
40
  import { touch } from '../util';
36
41
  import { extractTagIds } from '../util/tag';
37
42
 
@@ -70,6 +75,8 @@ const defaultFeatures: TableFeatures = {
70
75
  schemaEditable: false,
71
76
  };
72
77
 
78
+ export type InsertRowResult = 'draft' | 'final';
79
+
73
80
  export type TableModelProps<T extends TableRow = TableRow> = {
74
81
  view: DataType.View;
75
82
  schema: JsonSchemaType;
@@ -80,7 +87,7 @@ export type TableModelProps<T extends TableRow = TableRow> = {
80
87
  pinnedRows?: { top: number[]; bottom: number[] };
81
88
  rowActions?: TableRowAction[];
82
89
  onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
83
- onInsertRow?: (data?: any) => boolean;
90
+ onInsertRow?: (data?: any) => InsertRowResult;
84
91
  onDeleteRows?: (index: number, obj: T[]) => void;
85
92
  onDeleteColumn?: (fieldId: string) => void;
86
93
  onCellUpdate?: (cell: DxGridPosition) => void;
@@ -91,14 +98,14 @@ export type TableModelProps<T extends TableRow = TableRow> = {
91
98
  export class TableModel<T extends TableRow = TableRow> extends Resource {
92
99
  private readonly _view: DataType.View;
93
100
  private readonly _projection: ProjectionModel;
94
- private _table?: TableView;
101
+ private _table?: Table.Table;
95
102
 
96
103
  private readonly _visibleRange = signal<DxGridPlaneRange>({
97
104
  start: { row: 0, col: 0 },
98
105
  end: { row: 0, col: 0 },
99
106
  });
100
107
 
101
- private readonly _onInsertRow?: (data?: any) => boolean;
108
+ private readonly _onInsertRow?: (data?: any) => InsertRowResult;
102
109
  private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
103
110
  private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
104
111
  private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
@@ -134,6 +141,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
134
141
  super();
135
142
  this._view = view;
136
143
  this._projection = projection ?? new ProjectionModel(schema, view.projection);
144
+ this._projection.normalizeView();
137
145
 
138
146
  // TODO(ZaymonFC): Use our more robust config merging module?
139
147
  this._features = { ...defaultFeatures, ...features };
@@ -175,7 +183,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
175
183
  return this._view;
176
184
  }
177
185
 
178
- public get table(): TableView {
186
+ public get table(): Table.Table {
179
187
  invariant(this._table, 'Model not initialized');
180
188
  return this._table;
181
189
  }
@@ -238,7 +246,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
238
246
 
239
247
  protected override async _open(): Promise<void> {
240
248
  const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
241
- invariant(Obj.instanceOf(TableView, presentation));
249
+ invariant(Obj.instanceOf(Table.Table, presentation));
242
250
  this._table = presentation;
243
251
 
244
252
  this.initializeColumnMeta();
@@ -256,7 +264,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
256
264
  return {
257
265
  grid: meta,
258
266
  frozenColsStart: { 0: { size: 30, resizeable: false } },
259
- frozenColsEnd: { 0: { size: 40, resizeable: false } },
267
+ frozenColsEnd: { 0: { size: 32, resizeable: false } },
260
268
  };
261
269
  });
262
270
  }
@@ -336,11 +344,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
336
344
 
337
345
  public getColumnCount = (): number => this._projection?.fields.length ?? 0;
338
346
 
339
- public insertRow = (): void => {
347
+ public insertRow = (): InsertRowResult => {
340
348
  const result = this._onInsertRow?.();
341
- if (result === false && this._draftRows.value.length === 0) {
349
+ if (result === 'draft' && this._draftRows.value.length === 0) {
342
350
  this.createDraftRow();
343
351
  }
352
+ return result ?? 'final';
344
353
  };
345
354
 
346
355
  private createDraftRow(): void {
@@ -395,14 +404,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
395
404
  return false;
396
405
  }
397
406
 
398
- const success = this._onInsertRow?.(draftRow.data);
407
+ const insertRowResult = this._onInsertRow?.(draftRow.data);
399
408
 
400
- if (success) {
409
+ if (insertRowResult === 'final') {
401
410
  const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
402
411
  this._draftRows.value = newDraftRows;
403
412
  }
404
413
 
405
- return success ?? false;
414
+ return insertRowResult === 'final';
406
415
  };
407
416
 
408
417
  public deleteRow = (rowIndex: number): void => {
@@ -2,16 +2,16 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
  import { beforeEach, describe, expect, it } from 'vitest';
7
7
 
8
- import { Obj } from '@dxos/echo';
9
- import { TypedObject } from '@dxos/echo-schema';
8
+ import { Filter, Query } from '@dxos/echo';
9
+ import { TypedObject } from '@dxos/echo/internal';
10
+ import { createEchoSchema } from '@dxos/echo/testing';
10
11
  import { live } from '@dxos/live-object';
11
- import { createEchoSchema } from '@dxos/live-object/testing';
12
12
  import { createView } from '@dxos/schema';
13
13
 
14
- import { TableView } from '../types';
14
+ import { Table } from '../types';
15
15
 
16
16
  import { TableModel, type TableModelProps } from './table-model';
17
17
  import { TablePresentation } from './table-presentation';
@@ -27,8 +27,8 @@ describe('TablePresentation', () => {
27
27
  updateCount = 0;
28
28
  ({ data } = live({
29
29
  data: [
30
- { col1: 'A', col2: 1, col3: true },
31
- { col1: 'B', col2: 2, col3: false },
30
+ { title: 'A', count: 1 },
31
+ { title: 'B', count: 2 },
32
32
  ],
33
33
  }));
34
34
 
@@ -45,8 +45,8 @@ describe('TablePresentation', () => {
45
45
  presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
46
46
 
47
47
  // Trigger a row update.
48
- data[0].col1 = 'New Value';
49
- expect(updateCount).toBe(1);
48
+ data[0].title = 'New Value';
49
+ expect(updateCount).toBe(2);
50
50
 
51
51
  // Verify the new value through getCells.
52
52
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
@@ -57,15 +57,14 @@ describe('TablePresentation', () => {
57
57
  // Set up visible range to include our test data.
58
58
  presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
59
59
 
60
- data.push({ col1: 'C', col2: 3, col3: true });
61
- expect(updateCount).toBe(1);
60
+ data.push({ title: 'C', count: 3 });
61
+ expect(updateCount).toBe(2);
62
62
 
63
63
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
64
64
 
65
65
  // Verify the new row's data.
66
66
  expect(cells['0,2']?.value).toBe('C');
67
67
  expect(cells['1,2']?.value).toBe('3');
68
- expect(cells['2,2']?.value).toBe('true');
69
68
  });
70
69
 
71
70
  it('should handle combined operations reactively', () => {
@@ -73,32 +72,34 @@ describe('TablePresentation', () => {
73
72
  presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
74
73
 
75
74
  // Multiple operations.
76
- data[0].col1 = 'Updated A';
77
- data.push({ col1: 'C', col2: 3, col3: true });
75
+ data[0].title = 'Updated A';
76
+ data.push({ title: 'C', count: 3, completed: true });
78
77
  data.splice(1, 1);
79
78
 
80
79
  // We expect one update per operation affecting visible rows.
81
- expect(updateCount).toBe(3);
80
+ expect(updateCount).toBe(4);
82
81
 
83
82
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
84
83
 
85
84
  // Verify final state.
86
85
  expect(cells['0,0']?.value).toBe('Updated A');
87
86
  expect(cells['0,1']?.value).toBe('C');
88
- expect(cells['1,1']?.value).toBe('3');
89
- expect(cells['2,1']?.value).toBe('true');
90
87
  });
91
88
  });
92
89
  });
93
90
 
94
91
  class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
95
92
  title: Schema.String,
96
- completed: Schema.Boolean,
93
+ count: Schema.Number,
97
94
  }) {}
98
95
 
99
96
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
100
97
  const schema = createEchoSchema(Test);
101
- const table = Obj.make(TableView, { sizes: {} });
102
- const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
98
+ const table = Table.make();
99
+ const view = createView({
100
+ query: Query.select(Filter.type(schema)),
101
+ jsonSchema: schema.jsonSchema,
102
+ presentation: table,
103
+ });
103
104
  return new TableModel({ view, schema: schema.jsonSchema, ...props });
104
105
  };
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import { signal } from '@preact/signals-core';
6
- import { isNotNullable } from 'effect/Predicate';
6
+ import * as Predicate from 'effect/Predicate';
7
7
 
8
8
  import { Obj } from '@dxos/echo';
9
- import { FormatEnum, TypeEnum, getValue } from '@dxos/echo-schema';
9
+ import { FormatEnum, TypeEnum, getValue } from '@dxos/echo/internal';
10
10
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
11
11
  import {
12
12
  type DxGridCellValue,
@@ -69,7 +69,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
69
69
  cells = this.getDraftRowCells(range);
70
70
  break;
71
71
  case 'fixedEndStart':
72
- cells = this.getDraftSelectCells(range);
72
+ cells = this.getDraftIconCells(range);
73
73
  break;
74
74
  case 'fixedEndEnd':
75
75
  cells = this.getDraftActionCells(range);
@@ -141,20 +141,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
141
141
  };
142
142
 
143
143
  const classes = [];
144
- const formatClasses = cellClassesForFieldType({ type: props.type, format: props.format });
144
+ const formatClasses = cellClassesForFieldType(props);
145
+
145
146
  if (formatClasses) {
146
147
  classes.push(formatClasses);
147
148
  }
149
+
148
150
  const rowSelectionClasses = cellClassesForRowSelection(
149
151
  this.model.selection.isObjectSelected(obj),
150
152
  this.model.selection.selectionMode,
151
153
  );
154
+
152
155
  if (rowSelectionClasses) {
153
156
  classes.push(rowSelectionClasses);
154
157
  }
155
- if (classes.length > 0) {
156
- cell.className = mx(classes.flat());
157
- }
158
+
159
+ cell.className = mx(classes);
158
160
 
159
161
  // Arrays.
160
162
  if (props.type === TypeEnum.Array) {
@@ -188,7 +190,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
188
190
 
189
191
  const tags = targetArray
190
192
  .map(getLabel)
191
- .filter(isNotNullable)
193
+ .filter(Predicate.isNotNullable)
192
194
  .map((title) => {
193
195
  return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
194
196
  })
@@ -203,7 +205,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
203
205
  const targetObj = getValue(obj, field.path)?.target;
204
206
  if (targetObj) {
205
207
  const dxn = Obj.getDXN(targetObj)?.toString();
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>`;
208
+ cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor refId=${dxn} class="dx-button is-6 aspect-square min-bs-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
207
209
  }
208
210
  }
209
211
 
@@ -279,27 +281,36 @@ export class TablePresentation<T extends TableRow = TableRow> {
279
281
  const fields = this.model.projection?.fields ?? [];
280
282
  const draftRows = this.model.draftRows.value;
281
283
 
282
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
283
- const draftRow = draftRows[row];
284
+ // Return cells of the CTA row if no draft row is active
285
+ if (draftRows.length === 0) {
284
286
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
285
- const cellIndex = toPlaneCellIndex({ col, row });
286
- const field = fields[col];
287
- if (!field) {
288
- continue;
289
- }
287
+ cells[toPlaneCellIndex({ col, row: 0 })] = {
288
+ value: '',
289
+ readonly: true,
290
+ className: 'dx-grid__row--cta__cell',
291
+ };
292
+ }
293
+ } else {
294
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
295
+ const draftRow = draftRows[row];
296
+ for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
297
+ const cellIndex = toPlaneCellIndex({ col, row });
298
+ const field = fields[col];
299
+ if (!field) {
300
+ continue;
301
+ }
290
302
 
291
- this.createDataCell(cells, draftRow.data, field, col, row);
303
+ this.createDataCell(cells, draftRow.data, field, col, row);
292
304
 
293
- if (this.model.hasDraftRowValidationError(row, field.path)) {
294
- const cellValue = cells[cellIndex];
295
- if (cellValue) {
296
- const existingClasses = cellValue.className || '';
297
- const draftClasses = 'dx-grid__cell--flagged';
298
- cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
305
+ if (this.model.hasDraftRowValidationError(row, field.path)) {
306
+ const cellValue = cells[cellIndex];
307
+ if (cellValue) {
308
+ const existingClasses = cellValue.className || '';
309
+ const draftClasses = 'dx-grid__cell--flagged';
310
+ cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
311
+ }
299
312
  }
300
313
  }
301
-
302
- cells[cellIndex].className += ' !bg-toolbarSurface';
303
314
  }
304
315
  }
305
316
 
@@ -316,14 +327,14 @@ export class TablePresentation<T extends TableRow = TableRow> {
316
327
 
317
328
  cells[toPlaneCellIndex({ col, row: 0 })] = {
318
329
  // TODO(burdon): Use same logic as form for fallback title.
319
- value: props.title ?? field.path,
320
- readonly: true,
330
+ value: '',
321
331
  resizeHandle: 'col',
322
332
  accessoryHtml: `
333
+ <span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
323
334
  ${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
324
335
  ${tableButtons.columnSettings.render({ fieldId: field.id })}
325
336
  `,
326
- className: '!bg-toolbarSurface',
337
+ className: '!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch',
327
338
  };
328
339
  }
329
340
 
@@ -406,30 +417,39 @@ export class TablePresentation<T extends TableRow = TableRow> {
406
417
  const cells: DxGridPlaneCells = {};
407
418
  const draftRows = this.model.draftRows.value;
408
419
 
409
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
410
- const draftRow = draftRows[row];
411
- const disabled = !draftRow.valid;
412
-
413
- cells[toPlaneCellIndex({ col: 0, row })] = {
420
+ // Return cells of the CTA row if no draft row is active
421
+ if (draftRows.length === 0) {
422
+ cells[toPlaneCellIndex({ col: 0, row: 0 })] = {
414
423
  value: '',
424
+ className: 'dx-grid__row--cta__cell',
415
425
  readonly: true,
416
- accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
417
- className: '!bg-toolbarSurface',
418
426
  };
427
+ } else {
428
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
429
+ const draftRow = draftRows[row];
430
+ const disabled = !draftRow.valid;
431
+
432
+ cells[toPlaneCellIndex({ col: 0, row })] = {
433
+ value: '',
434
+ readonly: true,
435
+ accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
436
+ };
437
+ }
419
438
  }
420
439
 
421
440
  return cells;
422
441
  }
423
442
 
424
- private getDraftSelectCells(range: DxGridPlaneRange): DxGridPlaneCells {
443
+ private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
425
444
  const cells: DxGridPlaneCells = {};
426
445
  const draftRows = this.model.draftRows.value;
427
446
 
428
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
447
+ for (let row = range.start.row; row <= range.end.row; row++) {
429
448
  cells[toPlaneCellIndex({ col: 0, row })] = {
430
449
  value: '',
431
450
  readonly: true,
432
- className: '!bg-toolbarSurface',
451
+ accessoryHtml: '<dx-icon icon="ph--plus--regular" class="contents"></dx-icon>',
452
+ className: mx('[&>div]:grid [&>div]:place-content-center', draftRows.length < 1 && 'dx-grid__row--cta__cell'),
433
453
  };
434
454
  }
435
455
 
@@ -448,7 +468,6 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
448
468
 
449
469
  switch (selectionMode) {
450
470
  case 'single':
451
- // TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
452
471
  return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
453
472
  case 'multiple':
454
473
  return ['!bg-gridCellSelected'];
@@ -5,12 +5,21 @@
5
5
  import { type ReadonlySignal, type Signal, computed, signal } from '@preact/signals-core';
6
6
  import orderBy from 'lodash.orderby';
7
7
 
8
- import { type FieldSortType, FormatEnum, type SortDirectionType, TypeEnum, getValue } from '@dxos/echo-schema';
8
+ import { FormatEnum, TypeEnum, getValue } from '@dxos/echo/internal';
9
9
  import { formatForDisplay } from '@dxos/react-ui-form';
10
- import type { DataType, FieldType, ProjectionModel, PropertyType } from '@dxos/schema';
10
+ import type {
11
+ DataType,
12
+ FieldSortType,
13
+ FieldType,
14
+ ProjectionModel,
15
+ PropertyType,
16
+ SortDirectionType,
17
+ } from '@dxos/schema';
11
18
 
12
19
  import { type TableRow } from './table-model';
13
20
 
21
+ // TODO(wittjosiah): Remove. Use ordering in queries instead.
22
+
14
23
  /**
15
24
  * Represents the local sort state.
16
25
  * - undefined: No local sort set, fallback to view sort.
@@ -55,7 +64,7 @@ export class TableSorting<T extends TableRow> {
55
64
  this._rows = rows;
56
65
  this._isDirty = computed(() => {
57
66
  const local = this._localSort.value;
58
- const viewSort = this._view.query.sort?.[0];
67
+ const viewSort = this._view.sort?.[0];
59
68
  if (local?.type === 'cleared') {
60
69
  return viewSort !== undefined;
61
70
  }
@@ -83,7 +92,7 @@ export class TableSorting<T extends TableRow> {
83
92
  return local.sort;
84
93
  }
85
94
 
86
- return this._view.query.sort?.[0];
95
+ return this._view.sort?.[0];
87
96
  }
88
97
 
89
98
  /**
@@ -129,9 +138,9 @@ export class TableSorting<T extends TableRow> {
129
138
  public save(): void {
130
139
  if (this._localSort.value !== undefined) {
131
140
  if (this._localSort.value.type === 'active') {
132
- this._view.query.sort = [this._localSort.value.sort];
141
+ this._view.sort = [this._localSort.value.sort];
133
142
  } else {
134
- this._view.query.sort = [];
143
+ this._view.sort = [];
135
144
  }
136
145
  this._localSort.value = undefined;
137
146
  }