@dxos/react-ui-table 0.8.4-main.28f8d3d → 0.8.4-main.2c6827d

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 (114) hide show
  1. package/dist/lib/browser/chunk-2PFAPQL4.mjs +66 -0
  2. package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-ZAOSIG3L.mjs → chunk-GS2UNGJK.mjs} +174 -213
  4. package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +411 -515
  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 +12 -9
  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-6POOFFYF.mjs +67 -0
  12. package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-ZOFFI3JJ.mjs → chunk-TRR3C5NX.mjs} +174 -213
  14. package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +411 -515
  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 +12 -9
  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 +103 -5
  26. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  28. package/dist/types/src/components/Table/Table.d.ts +8 -5
  29. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  30. package/dist/types/src/components/Table/Table.stories.d.ts +41 -3
  31. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -6
  33. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -13
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
  37. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  39. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  40. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
  41. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  42. package/dist/types/src/components/testing.d.ts +3 -3
  43. package/dist/types/src/components/testing.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useAddRow.d.ts +3 -2
  45. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useTableModel.d.ts +3 -3
  47. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  48. package/dist/types/src/model/modal-controller.d.ts +0 -7
  49. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  50. package/dist/types/src/model/table-model.d.ts +9 -8
  51. package/dist/types/src/model/table-model.d.ts.map +1 -1
  52. package/dist/types/src/model/table-presentation.d.ts +1 -1
  53. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  54. package/dist/types/src/model/table-sorting.d.ts +2 -3
  55. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  56. package/dist/types/src/testing/useTestTableModel.d.ts +3 -22
  57. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  58. package/dist/types/src/types/index.d.ts +1 -1
  59. package/dist/types/src/types/index.d.ts.map +1 -1
  60. package/dist/types/src/types/schema.d.ts +25 -5
  61. package/dist/types/src/types/schema.d.ts.map +1 -1
  62. package/dist/types/src/util/dynamic-table.d.ts +5 -6
  63. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  64. package/dist/types/src/util/index.d.ts +0 -1
  65. package/dist/types/src/util/index.d.ts.map +1 -1
  66. package/dist/types/src/util/schema.d.ts +1 -1
  67. package/dist/types/src/util/schema.d.ts.map +1 -1
  68. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  69. package/dist/types/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +53 -51
  71. package/src/components/Table/DynamicTable.stories.tsx +15 -12
  72. package/src/components/Table/DynamicTable.tsx +4 -7
  73. package/src/components/Table/Relations.stories.tsx +160 -42
  74. package/src/components/Table/RowActionsMenu.tsx +21 -13
  75. package/src/components/Table/Table.stories.tsx +63 -56
  76. package/src/components/Table/Table.tsx +109 -90
  77. package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
  78. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -64
  79. package/src/components/TableCellEditor/TableCellEditor.tsx +62 -208
  80. package/src/components/TableCellEditor/index.ts +1 -0
  81. package/src/components/TableToolbar/TableToolbar.tsx +32 -22
  82. package/src/components/testing.ts +2 -2
  83. package/src/hooks/useAddRow.ts +7 -5
  84. package/src/hooks/useTableModel.ts +5 -5
  85. package/src/model/modal-controller.ts +0 -28
  86. package/src/model/table-model.test.ts +19 -11
  87. package/src/model/table-model.ts +36 -24
  88. package/src/model/table-presentation.test.ts +22 -21
  89. package/src/model/table-presentation.ts +58 -39
  90. package/src/model/table-sorting.ts +16 -7
  91. package/src/playwright/TableManager.ts +1 -1
  92. package/src/playwright/smoke.spec.ts +51 -49
  93. package/src/testing/useTestTableModel.ts +10 -7
  94. package/src/types/index.ts +1 -1
  95. package/src/types/schema.ts +63 -6
  96. package/src/util/dynamic-table.ts +15 -16
  97. package/src/util/index.ts +0 -1
  98. package/src/util/schema.ts +4 -4
  99. package/src/util/table-buttons.ts +3 -3
  100. package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
  101. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
  102. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +0 -7
  103. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
  104. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
  105. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +0 -7
  106. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  107. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  108. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  109. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  110. package/dist/types/src/util/table.d.ts +0 -12
  111. package/dist/types/src/util/table.d.ts.map +0 -1
  112. package/src/components/Table/CreateRefPanel.tsx +0 -86
  113. package/src/components/TableCellEditor/extension.ts +0 -61
  114. package/src/util/table.ts +0 -53
@@ -3,18 +3,18 @@
3
3
  //
4
4
 
5
5
  import { computed } from '@preact/signals-core';
6
- import { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
7
7
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
8
 
9
- import { Obj } from '@dxos/echo';
10
- import { TypedObject } from '@dxos/echo-schema';
11
- import { updateCounter } from '@dxos/echo-schema/testing';
9
+ import { Filter, Query } from '@dxos/echo';
10
+ import { TypedObject } from '@dxos/echo/internal';
11
+ import { updateCounter } from '@dxos/echo/testing';
12
+ import { createEchoSchema } from '@dxos/echo/testing';
12
13
  import { registerSignalsRuntime } from '@dxos/echo-signals';
13
14
  import { live } from '@dxos/live-object';
14
- import { createEchoSchema } from '@dxos/live-object/testing';
15
- import { createView } from '@dxos/schema';
15
+ import { View } from '@dxos/schema';
16
16
 
17
- import { TableView } from '../types';
17
+ import { Table } from '../types';
18
18
 
19
19
  import { TableModel, type TableModelProps } from './table-model';
20
20
 
@@ -25,7 +25,7 @@ registerSignalsRuntime();
25
25
 
26
26
  describe('TableModel', () => {
27
27
  let updateCount = 0;
28
- let model: TableModel;
28
+ let model: any;
29
29
 
30
30
  beforeEach(async () => {
31
31
  updateCount = 0;
@@ -33,6 +33,11 @@ describe('TableModel', () => {
33
33
  onCellUpdate: () => updateCount++,
34
34
  });
35
35
  await model.open();
36
+ model.setRows([
37
+ { id: '1', title: 'Test', completed: false },
38
+ { id: '2', title: 'Test 2', completed: true },
39
+ { id: '3', title: 'Test 3', completed: false },
40
+ ]);
36
41
  });
37
42
 
38
43
  afterEach(async () => {
@@ -73,7 +78,6 @@ describe('TableModel', () => {
73
78
  describe('reactivity', () => {
74
79
  it('pure signals should nest', () => {
75
80
  const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
76
-
77
81
  const computed$ = computed(() => {
78
82
  return signal$.arr.map((row) =>
79
83
  computed(() => {
@@ -108,7 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
108
112
 
109
113
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
110
114
  const schema = createEchoSchema(Test);
111
- const table = Obj.make(TableView, { sizes: {} });
112
- const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
115
+ const table = Table.make();
116
+ const view = View.make({
117
+ query: Query.select(Filter.type(schema)),
118
+ jsonSchema: schema.jsonSchema,
119
+ presentation: table,
120
+ });
113
121
  return new TableModel({ view, schema: schema.jsonSchema, ...props });
114
122
  };
@@ -7,20 +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';
23
- import { fullyQualifiedId } from '@dxos/react-client/echo';
20
+ import { getSnapshot, isLiveObject } from '@dxos/live-object';
24
21
  import { type Label } from '@dxos/react-ui';
25
22
  import { formatForEditing, parseValue } from '@dxos/react-ui-form';
26
23
  import {
@@ -29,9 +26,16 @@ import {
29
26
  type DxGridPlaneRange,
30
27
  type DxGridPosition,
31
28
  } from '@dxos/react-ui-grid';
32
- import { type DataType, ProjectionModel, type PropertyType, type ValidationError, validateSchema } from '@dxos/schema';
33
-
34
- import { TableView } from '../types';
29
+ import {
30
+ type FieldSortType,
31
+ ProjectionModel,
32
+ type PropertyType,
33
+ type ValidationError,
34
+ type View,
35
+ validateSchema,
36
+ } from '@dxos/schema';
37
+
38
+ import { Table } from '../types';
35
39
  import { touch } from '../util';
36
40
  import { extractTagIds } from '../util/tag';
37
41
 
@@ -70,8 +74,10 @@ const defaultFeatures: TableFeatures = {
70
74
  schemaEditable: false,
71
75
  };
72
76
 
77
+ export type InsertRowResult = 'draft' | 'final';
78
+
73
79
  export type TableModelProps<T extends TableRow = TableRow> = {
74
- view: DataType.View;
80
+ view: View.View;
75
81
  schema: JsonSchemaType;
76
82
  projection?: ProjectionModel;
77
83
  features?: Partial<TableFeatures>;
@@ -80,7 +86,7 @@ export type TableModelProps<T extends TableRow = TableRow> = {
80
86
  pinnedRows?: { top: number[]; bottom: number[] };
81
87
  rowActions?: TableRowAction[];
82
88
  onResolveSchema?: (typename: string) => Promise<JsonSchemaType>;
83
- onInsertRow?: (data?: any) => boolean;
89
+ onInsertRow?: (data?: any) => InsertRowResult;
84
90
  onDeleteRows?: (index: number, obj: T[]) => void;
85
91
  onDeleteColumn?: (fieldId: string) => void;
86
92
  onCellUpdate?: (cell: DxGridPosition) => void;
@@ -89,16 +95,16 @@ export type TableModelProps<T extends TableRow = TableRow> = {
89
95
  };
90
96
 
91
97
  export class TableModel<T extends TableRow = TableRow> extends Resource {
92
- private readonly _view: DataType.View;
98
+ private readonly _view: View.View;
93
99
  private readonly _projection: ProjectionModel;
94
- private _table?: TableView;
100
+ private _table?: Table.Table;
95
101
 
96
102
  private readonly _visibleRange = signal<DxGridPlaneRange>({
97
103
  start: { row: 0, col: 0 },
98
104
  end: { row: 0, col: 0 },
99
105
  });
100
106
 
101
- private readonly _onInsertRow?: (data?: any) => boolean;
107
+ private readonly _onInsertRow?: (data?: any) => InsertRowResult;
102
108
  private readonly _onDeleteRows?: TableModelProps<T>['onDeleteRows'];
103
109
  private readonly _onDeleteColumn?: TableModelProps<T>['onDeleteColumn'];
104
110
  private readonly _onCellUpdate?: TableModelProps<T>['onCellUpdate'];
@@ -134,6 +140,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
134
140
  super();
135
141
  this._view = view;
136
142
  this._projection = projection ?? new ProjectionModel(schema, view.projection);
143
+ this._projection.normalizeView();
137
144
 
138
145
  // TODO(ZaymonFC): Use our more robust config merging module?
139
146
  this._features = { ...defaultFeatures, ...features };
@@ -168,14 +175,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
168
175
  }
169
176
 
170
177
  public get id(): string {
171
- return fullyQualifiedId(this._view);
178
+ return Obj.getDXN(this._view).toString();
172
179
  }
173
180
 
174
- public get view(): DataType.View {
181
+ public get view(): View.View {
175
182
  return this._view;
176
183
  }
177
184
 
178
- public get table(): TableView {
185
+ public get table(): Table.Table {
179
186
  invariant(this._table, 'Model not initialized');
180
187
  return this._table;
181
188
  }
@@ -238,7 +245,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
238
245
 
239
246
  protected override async _open(): Promise<void> {
240
247
  const presentation = this._view.presentation.target ?? (await this._view.presentation.load());
241
- invariant(Obj.instanceOf(TableView, presentation));
248
+ invariant(Obj.instanceOf(Table.Table, presentation));
242
249
  this._table = presentation;
243
250
 
244
251
  this.initializeColumnMeta();
@@ -256,7 +263,7 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
256
263
  return {
257
264
  grid: meta,
258
265
  frozenColsStart: { 0: { size: 30, resizeable: false } },
259
- frozenColsEnd: { 0: { size: 40, resizeable: false } },
266
+ frozenColsEnd: { 0: { size: 32, resizeable: false } },
260
267
  };
261
268
  });
262
269
  }
@@ -336,11 +343,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
336
343
 
337
344
  public getColumnCount = (): number => this._projection?.fields.length ?? 0;
338
345
 
339
- public insertRow = (): void => {
346
+ public insertRow = (): InsertRowResult => {
340
347
  const result = this._onInsertRow?.();
341
- if (result === false && this._draftRows.value.length === 0) {
348
+ if (result === 'draft' && this._draftRows.value.length === 0) {
342
349
  this.createDraftRow();
343
350
  }
351
+ return result ?? 'final';
344
352
  };
345
353
 
346
354
  private createDraftRow(): void {
@@ -395,14 +403,14 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
395
403
  return false;
396
404
  }
397
405
 
398
- const success = this._onInsertRow?.(draftRow.data);
406
+ const insertRowResult = this._onInsertRow?.(draftRow.data);
399
407
 
400
- if (success) {
408
+ if (insertRowResult === 'final') {
401
409
  const newDraftRows = this._draftRows.value.filter((_, index) => index !== draftRowIndex);
402
410
  this._draftRows.value = newDraftRows;
403
411
  }
404
412
 
405
- return success ?? false;
413
+ return insertRowResult === 'final';
406
414
  };
407
415
 
408
416
  public deleteRow = (rowIndex: number): void => {
@@ -471,7 +479,11 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
471
479
  }
472
480
 
473
481
  default: {
474
- return formatForEditing({ type: props.type, format: props.format, value });
482
+ return formatForEditing({
483
+ type: props.type,
484
+ format: props.format,
485
+ value,
486
+ });
475
487
  }
476
488
  }
477
489
  };
@@ -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
- import { createView } from '@dxos/schema';
12
+ import { View } 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 = View.make({
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 {
11
+ type FieldSortType,
12
+ type FieldType,
13
+ type ProjectionModel,
14
+ type PropertyType,
15
+ type SortDirectionType,
16
+ type View,
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.
@@ -49,13 +58,13 @@ export class TableSorting<T extends TableRow> {
49
58
 
50
59
  constructor(
51
60
  rows: Signal<T[]>,
52
- private readonly _view: DataType.View,
61
+ private readonly _view: View.View,
53
62
  private readonly _projection: ProjectionModel,
54
63
  ) {
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
  }
@@ -59,6 +59,6 @@ export class TableManager {
59
59
  await this.page.getByLabel(`format ${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: 'button save' }).click();
62
+ await this.page.getByRole('button', { name: 'save button' }).click();
63
63
  }
64
64
  }