@dxos/react-ui-table 0.8.4-main.dedc0f3 → 0.8.4-main.dfabb4ec29

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 (176) hide show
  1. package/dist/lib/browser/chunk-FBURZBPH.mjs +70 -0
  2. package/dist/lib/browser/chunk-FBURZBPH.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  4. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-YGC7LZJN.mjs +1695 -0
  6. package/dist/lib/browser/chunk-YGC7LZJN.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +1139 -1193
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/testing/index.mjs +33 -42
  11. package/dist/lib/browser/testing/index.mjs.map +3 -3
  12. package/dist/lib/browser/translations.mjs +36 -0
  13. package/dist/lib/browser/translations.mjs.map +7 -0
  14. package/dist/lib/browser/types/index.mjs +4 -3
  15. package/dist/lib/node-esm/chunk-DKKMSVKP.mjs +71 -0
  16. package/dist/lib/node-esm/chunk-DKKMSVKP.mjs.map +7 -0
  17. package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs +1697 -0
  18. package/dist/lib/node-esm/chunk-GKIWDEZ7.mjs.map +7 -0
  19. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  20. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  21. package/dist/lib/node-esm/index.mjs +1139 -1193
  22. package/dist/lib/node-esm/index.mjs.map +4 -4
  23. package/dist/lib/node-esm/meta.json +1 -1
  24. package/dist/lib/node-esm/testing/index.mjs +33 -42
  25. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  26. package/dist/lib/node-esm/translations.mjs +37 -0
  27. package/dist/lib/node-esm/translations.mjs.map +7 -0
  28. package/dist/lib/node-esm/types/index.mjs +4 -3
  29. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  30. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  32. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
  34. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +23 -39
  36. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Relations.stories.d.ts +96 -29
  38. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  40. package/dist/types/src/components/Table/Table.d.ts +20 -18
  41. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  42. package/dist/types/src/components/Table/Table.stories.d.ts +96 -31
  43. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/Table/TableContent.d.ts +17 -0
  45. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
  46. package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
  47. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
  48. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  49. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  50. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -12
  51. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  52. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +7 -7
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  54. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +25 -28
  55. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  57. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  58. package/dist/types/src/components/index.d.ts +0 -1
  59. package/dist/types/src/components/index.d.ts.map +1 -1
  60. package/dist/types/src/components/testing.d.ts +23 -16
  61. package/dist/types/src/components/testing.d.ts.map +1 -1
  62. package/dist/types/src/hooks/index.d.ts +2 -1
  63. package/dist/types/src/hooks/index.d.ts.map +1 -1
  64. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  65. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  66. package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
  67. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  68. package/dist/types/src/hooks/useTableModel.d.ts +9 -9
  69. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  70. package/dist/types/src/index.d.ts +0 -2
  71. package/dist/types/src/index.d.ts.map +1 -1
  72. package/dist/types/src/model/modal-controller.d.ts +5 -8
  73. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  74. package/dist/types/src/model/selection-model.d.ts +18 -9
  75. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  76. package/dist/types/src/model/table-model.d.ts +106 -26
  77. package/dist/types/src/model/table-model.d.ts.map +1 -1
  78. package/dist/types/src/model/table-presentation.d.ts +3 -1
  79. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  80. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  81. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  82. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  83. package/dist/types/src/testing/useTestTableModel.d.ts +18 -15
  84. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  85. package/dist/types/src/translations.d.ts +21 -23
  86. package/dist/types/src/translations.d.ts.map +1 -1
  87. package/dist/types/src/types/Table.d.ts +26 -0
  88. package/dist/types/src/types/Table.d.ts.map +1 -0
  89. package/dist/types/src/types/index.d.ts +1 -1
  90. package/dist/types/src/types/index.d.ts.map +1 -1
  91. package/dist/types/src/util/dynamic-table.d.ts +14 -12
  92. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  93. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  94. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  95. package/dist/types/src/util/index.d.ts +2 -0
  96. package/dist/types/src/util/index.d.ts.map +1 -1
  97. package/dist/types/src/util/query-ast.d.ts +7 -0
  98. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  99. package/dist/types/src/util/schema.d.ts +1 -1
  100. package/dist/types/src/util/schema.d.ts.map +1 -1
  101. package/dist/types/src/util/signals.d.ts.map +1 -1
  102. package/dist/types/src/util/sort.d.ts +12 -0
  103. package/dist/types/src/util/sort.d.ts.map +1 -0
  104. package/dist/types/src/util/table-buttons.d.ts +20 -20
  105. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  106. package/dist/types/src/util/table-controls.d.ts +4 -4
  107. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  108. package/dist/types/src/util/tag.d.ts.map +1 -1
  109. package/dist/types/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +67 -76
  111. package/src/components/Table/ColumnActionsMenu.tsx +17 -12
  112. package/src/components/Table/ColumnSettings.tsx +8 -9
  113. package/src/components/Table/DynamicTable.stories.tsx +34 -33
  114. package/src/components/Table/DynamicTable.tsx +33 -36
  115. package/src/components/Table/Relations.stories.tsx +196 -48
  116. package/src/components/Table/RowActionsMenu.tsx +27 -17
  117. package/src/components/Table/Table.stories.tsx +144 -127
  118. package/src/components/Table/Table.tsx +43 -403
  119. package/src/components/Table/TableContent.tsx +421 -0
  120. package/src/components/Table/TableToolbar.tsx +111 -0
  121. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
  122. package/src/components/TableCellEditor/FormCellEditor.tsx +125 -131
  123. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +32 -55
  124. package/src/components/TableCellEditor/TableCellEditor.tsx +39 -124
  125. package/src/components/TableCellEditor/index.ts +1 -0
  126. package/src/components/index.ts +0 -1
  127. package/src/components/testing.ts +44 -18
  128. package/src/hooks/index.ts +2 -1
  129. package/src/hooks/useAddRow.ts +8 -10
  130. package/src/hooks/useProjectionModel.ts +39 -0
  131. package/src/hooks/useTableModel.ts +36 -34
  132. package/src/index.ts +0 -4
  133. package/src/model/modal-controller.ts +23 -40
  134. package/src/model/selection-model.ts +75 -44
  135. package/src/model/table-model.test.ts +93 -56
  136. package/src/model/table-model.ts +437 -170
  137. package/src/model/table-presentation.test.ts +67 -56
  138. package/src/model/table-presentation.ts +45 -41
  139. package/src/playwright/TableManager.ts +13 -6
  140. package/src/playwright/playwright.config.ts +1 -1
  141. package/src/playwright/smoke.spec.ts +59 -13
  142. package/src/testing/useTestTableModel.ts +48 -39
  143. package/src/translations.ts +20 -22
  144. package/src/types/Table.ts +79 -0
  145. package/src/types/index.ts +1 -1
  146. package/src/util/dynamic-table.test.ts +70 -0
  147. package/src/util/dynamic-table.ts +43 -31
  148. package/src/util/index.ts +2 -0
  149. package/src/util/query-ast.ts +22 -0
  150. package/src/util/schema.ts +4 -4
  151. package/src/util/sort.ts +90 -0
  152. package/src/util/table-buttons.ts +1 -1
  153. package/src/util/table-controls.ts +2 -2
  154. package/dist/lib/browser/chunk-63UUF4IG.mjs +0 -66
  155. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-GPZSXPSA.mjs +0 -1822
  157. package/dist/lib/browser/chunk-GPZSXPSA.mjs.map +0 -7
  158. package/dist/lib/node-esm/chunk-K734DJB7.mjs +0 -67
  159. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
  160. package/dist/lib/node-esm/chunk-XUD6W42C.mjs +0 -1823
  161. package/dist/lib/node-esm/chunk-XUD6W42C.mjs.map +0 -7
  162. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  163. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  164. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  165. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  166. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  167. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  168. package/dist/types/src/model/table-sorting.d.ts +0 -52
  169. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  170. package/dist/types/src/types/schema.d.ts +0 -28
  171. package/dist/types/src/types/schema.d.ts.map +0 -1
  172. package/src/components/Table/CreateRefPanel.tsx +0 -103
  173. package/src/components/TableToolbar/TableToolbar.tsx +0 -102
  174. package/src/components/TableToolbar/index.ts +0 -5
  175. package/src/model/table-sorting.ts +0 -225
  176. package/src/types/schema.ts +0 -73
@@ -2,102 +2,113 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import { Registry } from '@effect-atom/atom-react';
6
+ import * as Schema from 'effect/Schema';
6
7
  import { beforeEach, describe, expect, it } from 'vitest';
7
8
 
8
- import { TypedObject } from '@dxos/echo-schema';
9
- import { live } from '@dxos/live-object';
10
- import { createEchoSchema } from '@dxos/live-object/testing';
11
- import { createView } from '@dxos/schema';
9
+ import { Filter, JsonSchema, Query, Type } from '@dxos/echo';
10
+ import { createEchoSchema } from '@dxos/echo/testing';
11
+ import { ProjectionModel, ViewModel, createDirectChangeCallback } from '@dxos/schema';
12
12
 
13
13
  import { Table } from '../types';
14
-
15
14
  import { TableModel, type TableModelProps } from './table-model';
16
15
  import { TablePresentation } from './table-presentation';
17
16
 
18
17
  describe('TablePresentation', () => {
19
- describe('row reactivity', () => {
18
+ describe('getCells', () => {
19
+ let registry: Registry.Registry;
20
20
  let data: any[];
21
- let updateCount: number;
22
21
  let model: any;
23
22
  let presentation: TablePresentation;
24
23
 
25
24
  beforeEach(async () => {
26
- updateCount = 0;
27
- ({ data } = live({
28
- data: [
29
- { col1: 'A', col2: 1, col3: true },
30
- { col1: 'B', col2: 2, col3: false },
31
- ],
32
- }));
33
-
34
- model = createTableModel({
35
- onCellUpdate: () => updateCount++,
36
- });
37
- model.setRows(data);
25
+ registry = Registry.make();
26
+ data = [
27
+ { id: '1', title: 'A', count: 1 },
28
+ { id: '2', title: 'B', count: 2 },
29
+ ];
30
+
31
+ model = createTableModel(registry, {});
38
32
  await model.open();
39
- presentation = new TablePresentation(model);
33
+ model.setRows(data);
34
+ presentation = new TablePresentation(registry, model);
40
35
  });
41
36
 
42
- it('should update with row-level reactivity', () => {
43
- // Set up visible range to include our test data.
44
- presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
37
+ it('should return cells for the visible range', () => {
38
+ const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
45
39
 
46
- // Trigger a row update.
47
- data[0].col1 = 'New Value';
48
- expect(updateCount).toBe(1);
40
+ // Verify that cells are returned for the range. The exact column indices depend on the projection.
41
+ const cellKeys = Object.keys(cells);
42
+ expect(cellKeys.length).toBeGreaterThan(0);
49
43
 
50
- // Verify the new value through getCells.
51
- const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
52
- expect(cells['0,0']?.value).toBe('New Value');
44
+ // Check that values from the data are present in the cells.
45
+ const values = Object.values(cells).map((c) => c.value);
46
+ expect(values).toContain('A');
47
+ expect(values).toContain('B');
53
48
  });
54
49
 
55
- it('should update reactively when adding a new row', () => {
56
- // Set up visible range to include our test data.
57
- presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
50
+ it('should reflect row data changes', () => {
51
+ // Update the row data and refresh.
52
+ data[0].title = 'New Value';
53
+ model.setRows(data);
58
54
 
59
- data.push({ col1: 'C', col2: 3, col3: true });
60
- expect(updateCount).toBe(1);
55
+ // Verify the new value through getCells.
56
+ const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 2 } }, 'grid');
57
+ const values = Object.values(cells).map((c) => c.value);
58
+ expect(values).toContain('New Value');
59
+ });
60
+
61
+ it('should reflect new rows', () => {
62
+ // Add a new row.
63
+ data.push({ id: '3', title: 'C', count: 3 });
64
+ model.setRows(data);
61
65
 
62
66
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
63
67
 
64
68
  // Verify the new row's data.
65
69
  expect(cells['0,2']?.value).toBe('C');
66
70
  expect(cells['1,2']?.value).toBe('3');
67
- expect(cells['2,2']?.value).toBe('true');
68
71
  });
69
72
 
70
- it('should handle combined operations reactively', () => {
71
- // Set up visible range.
72
- presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
73
-
74
- // Multiple operations.
75
- data[0].col1 = 'Updated A';
76
- data.push({ col1: 'C', col2: 3, col3: true });
73
+ it('should handle row modifications', () => {
74
+ // Modify the data.
75
+ data[0].title = 'Updated A';
76
+ data.push({ id: '3', title: 'C', count: 3 });
77
77
  data.splice(1, 1);
78
-
79
- // We expect one update per operation affecting visible rows.
80
- expect(updateCount).toBe(3);
78
+ model.setRows(data);
81
79
 
82
80
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
83
81
 
84
82
  // Verify final state.
85
83
  expect(cells['0,0']?.value).toBe('Updated A');
86
84
  expect(cells['0,1']?.value).toBe('C');
87
- expect(cells['1,1']?.value).toBe('3');
88
- expect(cells['2,1']?.value).toBe('true');
89
85
  });
90
86
  });
91
87
  });
92
88
 
93
- class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
89
+ const Test = Schema.Struct({
94
90
  title: Schema.String,
95
- completed: Schema.Boolean,
96
- }) {}
97
-
98
- const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
91
+ count: Schema.Number,
92
+ }).pipe(
93
+ Type.object({
94
+ typename: 'com.example.type.test',
95
+ version: '0.1.0',
96
+ }),
97
+ );
98
+
99
+ const createTableModel = (registry: Registry.Registry, props: Partial<TableModelProps> = {}): TableModel => {
99
100
  const schema = createEchoSchema(Test);
100
- const table = Table.make();
101
- const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
102
- return new TableModel({ view, schema: schema.jsonSchema, ...props });
101
+ const view = ViewModel.make({
102
+ query: Query.select(Filter.type(schema)),
103
+ jsonSchema: schema.jsonSchema,
104
+ });
105
+ const object = Table.make({ view });
106
+ const projection = new ProjectionModel({
107
+ registry,
108
+ view,
109
+ baseSchema: schema.jsonSchema,
110
+ change: createDirectChangeCallback(view.projection, JsonSchema.toJsonSchema(schema)),
111
+ });
112
+ projection.normalizeView();
113
+ return new TableModel({ registry, object, projection, ...props });
103
114
  };
@@ -2,11 +2,12 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { signal } from '@preact/signals-core';
6
- import { isNotNullable } from 'effect/Predicate';
5
+ import { Atom, type Registry } from '@effect-atom/atom-react';
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 { type View } from '@dxos/echo';
10
+ import { Format, TypeEnum, getValue } from '@dxos/echo/internal';
10
11
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
11
12
  import {
12
13
  type DxGridCellValue,
@@ -15,11 +16,10 @@ import {
15
16
  type DxGridPlaneRange,
16
17
  toPlaneCellIndex,
17
18
  } from '@dxos/react-ui-grid';
18
- import { mx } from '@dxos/react-ui-theme';
19
- import { type FieldType, VIEW_FIELD_LIMIT } from '@dxos/schema';
19
+ import { VIEW_FIELD_LIMIT } from '@dxos/schema';
20
+ import { mx } from '@dxos/ui-theme';
20
21
 
21
22
  import { tableButtons, tableControls } from '../util';
22
-
23
23
  import { type SelectionMode } from './selection-model';
24
24
  import { type TableModel, type TableRow } from './table-model';
25
25
 
@@ -29,15 +29,20 @@ import { type TableModel, type TableRow } from './table-model';
29
29
  * different grid planes.
30
30
  */
31
31
  export class TablePresentation<T extends TableRow = TableRow> {
32
+ private readonly _registry: Registry.Registry;
33
+ private readonly _visibleRange: Atom.Writable<DxGridPlaneRange>;
32
34
  private fieldProjectionCache = new Map<string, ReturnType<typeof this.model.projection.getFieldProjection>>();
33
35
 
34
36
  constructor(
37
+ registry: Registry.Registry,
35
38
  private readonly model: TableModel<T>,
36
- private readonly _visibleRange = signal<DxGridPlaneRange>({
39
+ ) {
40
+ this._registry = registry;
41
+ this._visibleRange = Atom.make<DxGridPlaneRange>({
37
42
  start: { row: 0, col: 0 },
38
43
  end: { row: 0, col: 0 },
39
- }),
40
- ) {}
44
+ });
45
+ }
41
46
 
42
47
  public getCells(range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells {
43
48
  // Clear cache at the start of each render pass
@@ -47,7 +52,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
47
52
 
48
53
  switch (plane) {
49
54
  case 'grid':
50
- this._visibleRange.value = range;
55
+ this._registry.set(this._visibleRange, range);
51
56
  cells = this.getMainGridCells(range);
52
57
  break;
53
58
  case 'frozenRowsStart':
@@ -102,7 +107,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
102
107
  private createDataCell(
103
108
  cells: DxGridPlaneCells,
104
109
  obj: T,
105
- field: FieldType,
110
+ field: View.FieldType,
106
111
  colIndex: number,
107
112
  displayIndex: number,
108
113
  ): void {
@@ -120,12 +125,12 @@ export class TablePresentation<T extends TableRow = TableRow> {
120
125
  }
121
126
 
122
127
  switch (props.format) {
123
- case FormatEnum.Boolean:
124
- case FormatEnum.SingleSelect:
125
- case FormatEnum.MultiSelect: {
128
+ case Format.TypeFormat.Boolean:
129
+ case Format.TypeFormat.SingleSelect:
130
+ case Format.TypeFormat.MultiSelect: {
126
131
  return '';
127
132
  }
128
- case FormatEnum.Ref: {
133
+ case Format.TypeFormat.Ref: {
129
134
  if (!field.referencePath) {
130
135
  return ''; // TODO(burdon): Show error.
131
136
  }
@@ -190,7 +195,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
190
195
 
191
196
  const tags = targetArray
192
197
  .map(getLabel)
193
- .filter(isNotNullable)
198
+ .filter(Predicate.isNotNullable)
194
199
  .map((title) => {
195
200
  return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
196
201
  })
@@ -201,16 +206,16 @@ export class TablePresentation<T extends TableRow = TableRow> {
201
206
  }
202
207
 
203
208
  // References.
204
- if (props.format === FormatEnum.Ref && props.referenceSchema) {
209
+ if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
205
210
  const targetObj = getValue(obj, field.path)?.target;
206
211
  if (targetObj) {
207
212
  const dxn = Obj.getDXN(targetObj)?.toString();
208
- cell.accessoryHtml = `<dx-anchor 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-anchor>`;
213
+ cell.accessoryHtml = `<div role="none" class="absolute end-0 inset-y-0 p-(--dx-grid-cell-content-padding-block)"><dx-anchor dxn=${dxn} class="dx-button w-6 aspect-square min-h-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
209
214
  }
210
215
  }
211
216
 
212
217
  // Booleans.
213
- if (props.format === FormatEnum.Boolean) {
218
+ if (props.format === Format.TypeFormat.Boolean) {
214
219
  const value = getValue(obj, field.path);
215
220
  cell.accessoryHtml = tableControls.switch.render({
216
221
  colIndex,
@@ -221,7 +226,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
221
226
  }
222
227
 
223
228
  // Single-Selects.
224
- if (props.format === FormatEnum.SingleSelect) {
229
+ if (props.format === Format.TypeFormat.SingleSelect) {
225
230
  const value = getValue(obj, field.path);
226
231
  const options = this.model.projection.getFieldProjection(field.id).props.options;
227
232
  if (options) {
@@ -233,7 +238,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
233
238
  }
234
239
 
235
240
  // Multi-Selects.
236
- if (props.format === FormatEnum.MultiSelect) {
241
+ if (props.format === Format.TypeFormat.MultiSelect) {
237
242
  const values = getValue(obj, field.path) as string[] | undefined;
238
243
  const options = this.model.projection.getFieldProjection(field.id).props.options;
239
244
  if (options && values && values.length > 0) {
@@ -260,7 +265,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
260
265
 
261
266
  private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
262
267
  const cells: DxGridPlaneCells = {};
263
- const fields = this.model.projection?.fields ?? [];
268
+ const fields = this.model.projection?.getFields() ?? [];
264
269
 
265
270
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
266
271
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
@@ -269,7 +274,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
269
274
  continue;
270
275
  }
271
276
 
272
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
277
+ this.createDataCell(cells, this.model.getRows()[row], field, col, row);
273
278
  }
274
279
  }
275
280
 
@@ -278,8 +283,8 @@ export class TablePresentation<T extends TableRow = TableRow> {
278
283
 
279
284
  private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
280
285
  const cells: DxGridPlaneCells = {};
281
- const fields = this.model.projection?.fields ?? [];
282
- const draftRows = this.model.draftRows.value;
286
+ const fields = this.model.projection?.getFields() ?? [];
287
+ const draftRows = this.model.getDraftRows();
283
288
 
284
289
  // Return cells of the CTA row if no draft row is active
285
290
  if (draftRows.length === 0) {
@@ -319,22 +324,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
319
324
 
320
325
  private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
321
326
  const cells: DxGridPlaneCells = {};
322
- const fields = this.model.projection?.fields ?? [];
327
+ const fields = this.model.projection?.getFields() ?? [];
328
+ const currentSort = this.model.getSorting();
323
329
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
324
330
  const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
325
- const sorting = this.model.sorting?.sorting;
326
- const direction = sorting?.fieldId === field.id ? sorting.direction : undefined;
331
+ const direction = currentSort?.fieldId === field.id ? currentSort.direction : undefined;
327
332
 
328
333
  cells[toPlaneCellIndex({ col, row: 0 })] = {
329
334
  // TODO(burdon): Use same logic as form for fallback title.
330
335
  value: '',
331
336
  resizeHandle: 'col',
332
337
  accessoryHtml: `
333
- <span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
338
+ <span class="grow min-w-0 truncate">${props.title ?? field.path}</span>
334
339
  ${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
335
340
  ${tableButtons.columnSettings.render({ fieldId: field.id })}
336
341
  `,
337
- className: '!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch',
342
+ className: '!bg-toolbar-surface text-description! [&>div]:flex [&>div]:items-stretch',
338
343
  };
339
344
  }
340
345
 
@@ -377,7 +382,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
377
382
  if (!this.model.features.selection.enabled || this.model.selection.selectionMode === 'single') {
378
383
  return {
379
384
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
380
- className: '!bg-toolbarSurface',
385
+ className: '!bg-toolbar-surface',
381
386
  readonly: true,
382
387
  value: '',
383
388
  },
@@ -389,9 +394,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
389
394
  accessoryHtml: tableControls.checkbox.render({
390
395
  rowIndex: 0,
391
396
  header: true,
392
- checked: this.model.selection.allRowsSeleted.value,
397
+ checked: this.model.selection.allRowsSelected,
393
398
  }),
394
- className: '!bg-toolbarSurface',
399
+ className: '!bg-toolbar-surface',
395
400
  readonly: true,
396
401
  value: '',
397
402
  },
@@ -403,10 +408,10 @@ export class TablePresentation<T extends TableRow = TableRow> {
403
408
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
404
409
  accessoryHtml: this.model.features.schemaEditable
405
410
  ? tableButtons.addColumn.render({
406
- disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
411
+ disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT,
407
412
  })
408
413
  : undefined,
409
- className: '!bg-toolbarSurface',
414
+ className: '!bg-toolbar-surface',
410
415
  readonly: true,
411
416
  value: '',
412
417
  },
@@ -415,7 +420,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
415
420
 
416
421
  private getDraftActionCells(range: DxGridPlaneRange): DxGridPlaneCells {
417
422
  const cells: DxGridPlaneCells = {};
418
- const draftRows = this.model.draftRows.value;
423
+ const draftRows = this.model.getDraftRows();
419
424
 
420
425
  // Return cells of the CTA row if no draft row is active
421
426
  if (draftRows.length === 0) {
@@ -442,7 +447,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
442
447
 
443
448
  private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
444
449
  const cells: DxGridPlaneCells = {};
445
- const draftRows = this.model.draftRows.value;
450
+ const draftRows = this.model.getDraftRows();
446
451
 
447
452
  for (let row = range.start.row; row <= range.end.row; row++) {
448
453
  cells[toPlaneCellIndex({ col: 0, row })] = {
@@ -460,7 +465,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
460
465
  export const cellClassesForRowSelection = (selected: boolean, selectionMode: SelectionMode) => {
461
466
  if (!selected) {
462
467
  if (selectionMode === 'single') {
463
- return ['!cursor-pointer'];
468
+ return ['cursor-pointer!'];
464
469
  } else {
465
470
  return undefined;
466
471
  }
@@ -468,9 +473,8 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
468
473
 
469
474
  switch (selectionMode) {
470
475
  case 'single':
471
- // TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
472
- return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
476
+ return ['dx-grid__cell--no-focus-unfurl !bg-current-surface hover:bg-hover-surface cursor-pointer!'];
473
477
  case 'multiple':
474
- return ['!bg-gridCellSelected'];
478
+ return ['!bg-grid-cell-selected'];
475
479
  }
476
480
  };
@@ -17,6 +17,7 @@ const TABLE_SELECTORS = {
17
17
  },
18
18
  columnDelete: 'column-delete',
19
19
  rowDelete: 'row-menu-delete',
20
+ addRow: 'table.toolbar.add-row',
20
21
  } as const;
21
22
 
22
23
  export class TableManager {
@@ -53,12 +54,18 @@ export class TableManager {
53
54
  await this.page.getByTestId(TABLE_SELECTORS.columnSort[direction]).click();
54
55
  }
55
56
 
56
- public async addColumn({ label, format }: { label: string; format: 'number' }): Promise<void> {
57
+ public async addRow(): Promise<void> {
58
+ await this.page.getByTestId(TABLE_SELECTORS.addRow).click();
59
+ }
60
+
61
+ public async addColumn({ label, format }: { label: string; format?: string }): Promise<void> {
57
62
  await this.page.getByTestId(TABLE_SELECTORS.newColumnButton).click();
58
- await this.page.getByRole('combobox').click();
59
- await this.page.getByLabel(`format ${format}`).click();
60
- await this.page.getByPlaceholder('Property label.').click();
61
- await this.page.getByPlaceholder('Property label.').fill(label);
62
- await this.page.getByRole('button', { name: 'button save' }).click();
63
+ if (format) {
64
+ await this.page.getByRole('combobox').click();
65
+ await this.page.getByLabel(format).click();
66
+ }
67
+ await this.page.getByPlaceholder('Property label').click();
68
+ await this.page.getByPlaceholder('Property label').fill(label);
69
+ await this.page.getByRole('button', { name: 'Save' }).click();
63
70
  }
64
71
  }
@@ -10,7 +10,7 @@ export default defineConfig({
10
10
  ...e2ePreset(import.meta.dirname),
11
11
  // TODO(wittjosiah): Avoid hard-coding ports.
12
12
  webServer: {
13
- command: 'moon run storybook:serve-e2e -- --port=9004',
13
+ command: 'pnpm storybook dev --ci --quiet --port=9004 --config-dir=.storybook',
14
14
  port: 9004,
15
15
  reuseExistingServer: false,
16
16
  },
@@ -5,7 +5,7 @@
5
5
  import { expect, test } from '@playwright/test';
6
6
 
7
7
  import { type DxGrid } from '@dxos/lit-grid';
8
- import { faker } from '@dxos/random';
8
+ import { random } from '@dxos/random';
9
9
  import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
10
10
 
11
11
  import { TableManager } from './TableManager';
@@ -13,7 +13,7 @@ import { TableManager } from './TableManager';
13
13
  const storyUrl = storybookUrl('ui-react-ui-table-table--default', 9004);
14
14
  const relationsStoryUrl = storybookUrl('ui-react-ui-table-relations--default', 9004);
15
15
 
16
- // NOTE(ZaymonFC): This test suite relies on the faker seed being set to 0 in the story.
16
+ // NOTE(ZaymonFC): This test suite relies on the random seed being set to 0 in the story.
17
17
  test.describe('Table', () => {
18
18
  test('Loads', async ({ browser, browserName }) => {
19
19
  test.skip(browserName === 'webkit');
@@ -71,6 +71,7 @@ test.describe('Table', () => {
71
71
  const table = new TableManager(page);
72
72
 
73
73
  await table.grid.ready();
74
+ await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(1);
74
75
  await table.deleteRow(0);
75
76
  await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
76
77
  await page.close();
@@ -88,7 +89,7 @@ test.describe('Table', () => {
88
89
  // Delete action affects all selected rows.
89
90
  await table.deleteRow(0);
90
91
 
91
- await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
92
+ await expect(page.getByRole('gridcell', { name: 'Aut.' })).toHaveCount(0);
92
93
  await expect(page.getByRole('gridcell', { name: 'Beatae.' })).toHaveCount(0);
93
94
  await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(0);
94
95
  await page.close();
@@ -120,8 +121,22 @@ test.describe('Table', () => {
120
121
  await page.close();
121
122
  });
122
123
 
123
- // Rest of add column test remains the same as it's a more complex flow.
124
- test('add column', async ({ browser, browserName }) => {
124
+ test('add column without changing format', async ({ browser, browserName }) => {
125
+ test.skip(browserName === 'webkit');
126
+ test.skip(browserName === 'firefox');
127
+ const { page } = await setupPage(browser, { url: storyUrl });
128
+ const table = new TableManager(page);
129
+
130
+ await table.grid.ready();
131
+ const newColumnLabel = 'DEFAULT FORMAT COL';
132
+
133
+ await table.addColumn({ label: newColumnLabel });
134
+
135
+ await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
136
+ await page.close();
137
+ });
138
+
139
+ test('add column with format', async ({ browser, browserName }) => {
125
140
  test.skip(browserName === 'webkit');
126
141
  test.skip(browserName === 'firefox');
127
142
  const { page } = await setupPage(browser, { url: storyUrl });
@@ -130,7 +145,7 @@ test.describe('Table', () => {
130
145
  await table.grid.ready();
131
146
  const newColumnLabel = 'TEST LABEL';
132
147
 
133
- await table.addColumn({ label: newColumnLabel, format: 'number' });
148
+ await table.addColumn({ label: newColumnLabel, format: 'Number' });
134
149
 
135
150
  await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
136
151
  await page.close();
@@ -153,13 +168,43 @@ test.describe('Table', () => {
153
168
  // Assert the first two switch checkboxes are checked.
154
169
  await expect(page.getByTestId('table-switch').first()).toBeChecked();
155
170
  await expect(page.getByTestId('table-switch').nth(1)).toBeChecked();
156
- await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Sapiente.');
171
+ await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Aut.');
157
172
  await expect(table.grid.cell(0, 1, 'grid')).toHaveText('Beatae.');
158
173
 
159
174
  await page.close();
160
175
  });
161
176
 
162
- test('extant relations work as expected', async ({ browser, browserName }) => {
177
+ test('add row and edit cell', async ({ browser, browserName }) => {
178
+ test.skip(browserName === 'webkit');
179
+ test.skip(browserName === 'firefox');
180
+ const { page } = await setupPage(browser, { url: storyUrl });
181
+ const table = new TableManager(page);
182
+
183
+ await table.grid.ready();
184
+
185
+ // Click the CTA row at the bottom to add a new row.
186
+ await table.grid.cell(0, 0, 'frozenRowsEnd').click();
187
+
188
+ // All schema fields are optional so the row is added directly to the grid (not as a draft).
189
+ const newCell = table.grid.cell(0, 10, 'grid');
190
+ await newCell.waitFor({ state: 'visible' });
191
+
192
+ // Focus should already be on the new row; engage edit mode.
193
+ await page.keyboard.press('Enter');
194
+ await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
195
+
196
+ // Type text and confirm.
197
+ const cellContent = 'New row content';
198
+ await page.keyboard.type(cellContent);
199
+ await page.keyboard.press('Enter');
200
+
201
+ // Assert the cell content is saved.
202
+ await expect(newCell).toHaveText(cellContent);
203
+ await page.close();
204
+ });
205
+
206
+ // TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
207
+ test.skip('extant relations work as expected', async ({ browser, browserName }) => {
163
208
  test.skip(browserName === 'webkit');
164
209
  test.skip(browserName === 'firefox');
165
210
  const { page } = await setupPage(browser, { url: relationsStoryUrl });
@@ -187,7 +232,7 @@ test.describe('Table', () => {
187
232
  // Click on the combobox to open options
188
233
  await page.getByRole('combobox').click();
189
234
  await page.pause();
190
- await page.getByPlaceholder('Search...').focus();
235
+ await page.getByPlaceholder('Search…').focus();
191
236
 
192
237
  // Type the first few letters of an org name.
193
238
  const orgName =
@@ -213,7 +258,8 @@ test.describe('Table', () => {
213
258
  await page.close();
214
259
  });
215
260
 
216
- test('new relations work as expected', async ({ browser, browserName }) => {
261
+ // TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
262
+ test.skip('new relations work as expected', async ({ browser, browserName }) => {
217
263
  test.skip(browserName === 'webkit');
218
264
  test.skip(browserName === 'firefox');
219
265
  const { page } = await setupPage(browser, { url: relationsStoryUrl });
@@ -240,7 +286,7 @@ test.describe('Table', () => {
240
286
 
241
287
  // Click on the combobox to open options
242
288
  await page.getByRole('combobox').click();
243
- await page.getByPlaceholder('Search...').focus();
289
+ await page.getByPlaceholder('Search…').focus();
244
290
 
245
291
  // Type the first few letters of an org name.
246
292
  const orgName = 'Sally';
@@ -254,7 +300,7 @@ test.describe('Table', () => {
254
300
  await page.pause();
255
301
 
256
302
  // Click the save button in the popover
257
- await page.getByTestId('save-button').click();
303
+ await page.getByTestId('create-referenced-object-form').getByTestId('save-button').click();
258
304
 
259
305
  // Assert that the cell element has the org name
260
306
  await expect(targetCell).toHaveText(orgName);
@@ -264,7 +310,7 @@ test.describe('Table', () => {
264
310
  dxGridElement.scrollToColumn(6);
265
311
  });
266
312
 
267
- const nonRefContent = faker.lorem.words(3);
313
+ const nonRefContent = random.lorem.words(3);
268
314
  const nonRefCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="6"]');
269
315
  await nonRefCell.click();
270
316
  await page.keyboard.press('Enter');