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

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 (185) 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 +1158 -2872
  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 +115 -0
  11. package/dist/lib/browser/testing/index.mjs.map +7 -0
  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 -5
  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 +1158 -2872
  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 +116 -0
  25. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  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 -5
  29. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +2 -3
  30. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -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 +35 -3
  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 +111 -0
  38. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  39. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  40. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  41. package/dist/types/src/components/Table/Table.d.ts +21 -18
  42. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  43. package/dist/types/src/components/Table/Table.stories.d.ts +110 -6
  44. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/Table/TableContent.d.ts +17 -0
  46. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
  47. package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
  48. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
  49. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  50. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  51. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +14 -9
  52. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -18
  54. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  55. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +38 -5
  56. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  58. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  59. package/dist/types/src/components/index.d.ts +0 -1
  60. package/dist/types/src/components/index.d.ts.map +1 -1
  61. package/dist/types/src/components/testing.d.ts +26 -56
  62. package/dist/types/src/components/testing.d.ts.map +1 -1
  63. package/dist/types/src/hooks/index.d.ts +2 -1
  64. package/dist/types/src/hooks/index.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useAddRow.d.ts +6 -6
  66. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  67. package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
  68. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  69. package/dist/types/src/hooks/useTableModel.d.ts +11 -10
  70. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  71. package/dist/types/src/index.d.ts +0 -3
  72. package/dist/types/src/index.d.ts.map +1 -1
  73. package/dist/types/src/model/modal-controller.d.ts +5 -8
  74. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  75. package/dist/types/src/model/selection-model.d.ts +18 -8
  76. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  77. package/dist/types/src/model/table-model.d.ts +116 -34
  78. package/dist/types/src/model/table-model.d.ts.map +1 -1
  79. package/dist/types/src/model/table-presentation.d.ts +5 -2
  80. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  81. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  82. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  83. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  84. package/dist/types/src/testing/index.d.ts +2 -0
  85. package/dist/types/src/testing/index.d.ts.map +1 -0
  86. package/dist/types/src/testing/useTestTableModel.d.ts +25 -0
  87. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
  88. package/dist/types/src/translations.d.ts +21 -23
  89. package/dist/types/src/translations.d.ts.map +1 -1
  90. package/dist/types/src/types/Table.d.ts +26 -0
  91. package/dist/types/src/types/Table.d.ts.map +1 -0
  92. package/dist/types/src/types/index.d.ts +1 -1
  93. package/dist/types/src/types/index.d.ts.map +1 -1
  94. package/dist/types/src/util/dynamic-table.d.ts +15 -14
  95. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  96. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  97. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  98. package/dist/types/src/util/index.d.ts +2 -1
  99. package/dist/types/src/util/index.d.ts.map +1 -1
  100. package/dist/types/src/util/query-ast.d.ts +7 -0
  101. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  102. package/dist/types/src/util/schema.d.ts +1 -1
  103. package/dist/types/src/util/schema.d.ts.map +1 -1
  104. package/dist/types/src/util/signals.d.ts.map +1 -1
  105. package/dist/types/src/util/sort.d.ts +12 -0
  106. package/dist/types/src/util/sort.d.ts.map +1 -0
  107. package/dist/types/src/util/table-buttons.d.ts +20 -20
  108. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  109. package/dist/types/src/util/table-controls.d.ts +4 -4
  110. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  111. package/dist/types/src/util/tag.d.ts.map +1 -1
  112. package/dist/types/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +80 -69
  114. package/src/components/Table/ColumnActionsMenu.tsx +18 -13
  115. package/src/components/Table/ColumnSettings.tsx +18 -15
  116. package/src/components/Table/DynamicTable.stories.tsx +40 -36
  117. package/src/components/Table/DynamicTable.tsx +38 -34
  118. package/src/components/Table/Relations.stories.tsx +281 -0
  119. package/src/components/Table/RowActionsMenu.tsx +28 -18
  120. package/src/components/Table/Table.stories.tsx +198 -220
  121. package/src/components/Table/Table.tsx +47 -376
  122. package/src/components/Table/TableContent.tsx +421 -0
  123. package/src/components/Table/TableToolbar.tsx +111 -0
  124. package/src/components/TableCellEditor/CellValidationMessage.tsx +6 -6
  125. package/src/components/TableCellEditor/FormCellEditor.tsx +153 -49
  126. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +38 -77
  127. package/src/components/TableCellEditor/TableCellEditor.tsx +104 -233
  128. package/src/components/TableCellEditor/index.ts +1 -0
  129. package/src/components/index.ts +0 -1
  130. package/src/components/testing.ts +46 -37
  131. package/src/hooks/index.ts +2 -1
  132. package/src/hooks/useAddRow.ts +14 -14
  133. package/src/hooks/useProjectionModel.ts +39 -0
  134. package/src/hooks/useTableModel.ts +41 -35
  135. package/src/index.ts +0 -5
  136. package/src/model/modal-controller.ts +24 -41
  137. package/src/model/selection-model.ts +75 -41
  138. package/src/model/table-model.test.ts +91 -56
  139. package/src/model/table-model.ts +468 -187
  140. package/src/model/table-presentation.test.ts +77 -60
  141. package/src/model/table-presentation.ts +112 -66
  142. package/src/playwright/TableManager.ts +13 -6
  143. package/src/playwright/playwright.config.ts +1 -1
  144. package/src/playwright/smoke.spec.ts +180 -43
  145. package/src/testing/index.ts +5 -0
  146. package/src/testing/useTestTableModel.ts +133 -0
  147. package/src/translations.ts +20 -22
  148. package/src/types/Table.ts +79 -0
  149. package/src/types/index.ts +2 -2
  150. package/src/util/dynamic-table.test.ts +77 -0
  151. package/src/util/dynamic-table.ts +51 -36
  152. package/src/util/index.ts +2 -1
  153. package/src/util/query-ast.ts +22 -0
  154. package/src/util/schema.ts +4 -4
  155. package/src/util/sort.ts +90 -0
  156. package/src/util/table-buttons.ts +4 -3
  157. package/src/util/table-controls.ts +6 -3
  158. package/dist/lib/browser/chunk-Q5QQXJUO.mjs +0 -27
  159. package/dist/lib/browser/chunk-Q5QQXJUO.mjs.map +0 -7
  160. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs +0 -29
  161. package/dist/lib/node-esm/chunk-ZNOU6LHG.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/Table/relations.stories.d.ts +0 -9
  165. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  166. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  167. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  168. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  169. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  170. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  171. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  172. package/dist/types/src/model/table-sorting.d.ts +0 -53
  173. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  174. package/dist/types/src/types/table.d.ts +0 -56
  175. package/dist/types/src/types/table.d.ts.map +0 -1
  176. package/dist/types/src/util/table.d.ts +0 -14
  177. package/dist/types/src/util/table.d.ts.map +0 -1
  178. package/src/components/Table/CreateRefPanel.tsx +0 -84
  179. package/src/components/Table/relations.stories.tsx +0 -123
  180. package/src/components/TableCellEditor/extension.ts +0 -61
  181. package/src/components/TableToolbar/TableToolbar.tsx +0 -102
  182. package/src/components/TableToolbar/index.ts +0 -5
  183. package/src/model/table-sorting.ts +0 -216
  184. package/src/types/table.ts +0 -27
  185. package/src/util/table.ts +0 -127
@@ -2,103 +2,120 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
6
- import { describe, expect, it, beforeEach } from 'vitest';
5
+ import { Registry } from '@effect-atom/atom-react';
6
+ import * as Schema from 'effect/Schema';
7
+ import { beforeEach, describe, expect, it } from 'vitest';
7
8
 
8
- import { Obj, Ref } from '@dxos/echo';
9
- import { TypedObject } from '@dxos/echo-schema';
10
- import { live } from '@dxos/live-object';
11
- import { createEchoSchema } from '@dxos/live-object/testing';
12
- import { createView, ViewProjection } 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';
13
12
 
13
+ import { Table } from '../types';
14
14
  import { TableModel, type TableModelProps } from './table-model';
15
15
  import { TablePresentation } from './table-presentation';
16
- import { TableType } from '../types';
17
16
 
18
- describe('TablePresentation', () => {
19
- describe('row reactivity', () => {
17
+ // Quarantined in CI only — vite resolves `@dxos/react-ui-grid` to the hoisted
18
+ // node_modules copy, which fails the `node` export condition under the
19
+ // workflow's `--no-file-parallelism` runner. Tests pass locally so the suite
20
+ // still runs during development.
21
+ // Trunk.io tracking:
22
+ // https://app.trunk.io/dxos/flaky-tests/test/e027ed98-4aea-5a63-b5ed-10baf76fb11e
23
+ const describeOutsideCI = process.env.CI ? describe.skip : describe;
24
+ describeOutsideCI('TablePresentation', () => {
25
+ describe('getCells', () => {
26
+ let registry: Registry.Registry;
20
27
  let data: any[];
21
- let updateCount: number;
22
28
  let model: any;
23
29
  let presentation: TablePresentation;
24
30
 
25
31
  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);
32
+ registry = Registry.make();
33
+ data = [
34
+ { id: '1', title: 'A', count: 1 },
35
+ { id: '2', title: 'B', count: 2 },
36
+ ];
37
+
38
+ model = createTableModel(registry, {});
38
39
  await model.open();
39
- presentation = new TablePresentation(model);
40
+ model.setRows(data);
41
+ presentation = new TablePresentation(registry, model);
40
42
  });
41
43
 
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');
44
+ it('should return cells for the visible range', () => {
45
+ const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
45
46
 
46
- // Trigger a row update.
47
- data[0].col1 = 'New Value';
48
- expect(updateCount).toBe(1);
47
+ // Verify that cells are returned for the range. The exact column indices depend on the projection.
48
+ const cellKeys = Object.keys(cells);
49
+ expect(cellKeys.length).toBeGreaterThan(0);
49
50
 
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');
51
+ // Check that values from the data are present in the cells.
52
+ const values = Object.values(cells).map((c) => c.value);
53
+ expect(values).toContain('A');
54
+ expect(values).toContain('B');
53
55
  });
54
56
 
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');
57
+ it('should reflect row data changes', () => {
58
+ // Update the row data and refresh.
59
+ data[0].title = 'New Value';
60
+ model.setRows(data);
61
+
62
+ // Verify the new value through getCells.
63
+ const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 2 } }, 'grid');
64
+ const values = Object.values(cells).map((c) => c.value);
65
+ expect(values).toContain('New Value');
66
+ });
58
67
 
59
- data.push({ col1: 'C', col2: 3, col3: true });
60
- expect(updateCount).toBe(1);
68
+ it('should reflect new rows', () => {
69
+ // Add a new row.
70
+ data.push({ id: '3', title: 'C', count: 3 });
71
+ model.setRows(data);
61
72
 
62
73
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
63
74
 
64
75
  // Verify the new row's data.
65
76
  expect(cells['0,2']?.value).toBe('C');
66
77
  expect(cells['1,2']?.value).toBe('3');
67
- expect(cells['2,2']?.value).toBe('true');
68
78
  });
69
79
 
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 });
80
+ it('should handle row modifications', () => {
81
+ // Modify the data.
82
+ data[0].title = 'Updated A';
83
+ data.push({ id: '3', title: 'C', count: 3 });
77
84
  data.splice(1, 1);
78
-
79
- // We expect one update per operation affecting visible rows.
80
- expect(updateCount).toBe(3);
85
+ model.setRows(data);
81
86
 
82
87
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
83
88
 
84
89
  // Verify final state.
85
90
  expect(cells['0,0']?.value).toBe('Updated A');
86
91
  expect(cells['0,1']?.value).toBe('C');
87
- expect(cells['1,1']?.value).toBe('3');
88
- expect(cells['2,1']?.value).toBe('true');
89
92
  });
90
93
  });
91
94
  });
92
95
 
93
- class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
96
+ const Test = Schema.Struct({
94
97
  title: Schema.String,
95
- completed: Schema.Boolean,
96
- }) {}
97
-
98
- const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
98
+ count: Schema.Number,
99
+ }).pipe(
100
+ Type.object({
101
+ typename: 'com.example.type.test',
102
+ version: '0.1.0',
103
+ }),
104
+ );
105
+
106
+ const createTableModel = (registry: Registry.Registry, props: Partial<TableModelProps> = {}): TableModel => {
99
107
  const schema = createEchoSchema(Test);
100
- const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
101
- const projection = new ViewProjection(schema.jsonSchema, view);
102
- const table = Obj.make(TableType, { view: Ref.make(view) });
103
- return new TableModel({ id: table.id, view, projection, ...props });
108
+ const view = ViewModel.make({
109
+ query: Query.select(Filter.type(schema)),
110
+ jsonSchema: schema.jsonSchema,
111
+ });
112
+ const object = Table.make({ view });
113
+ const projection = new ProjectionModel({
114
+ registry,
115
+ view,
116
+ baseSchema: schema.jsonSchema,
117
+ change: createDirectChangeCallback(view.projection, JsonSchema.toJsonSchema(schema)),
118
+ });
119
+ projection.normalizeView();
120
+ return new TableModel({ registry, object, projection, ...props });
104
121
  };
@@ -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, getValue, TypeEnum } 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,12 +16,12 @@ 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 { VIEW_FIELD_LIMIT, type FieldType } from '@dxos/schema';
19
+ import { VIEW_FIELD_LIMIT } from '@dxos/schema';
20
+ import { mx } from '@dxos/ui-theme';
20
21
 
21
- import { type SelectionMode } from './selection-model';
22
- import { type TableRow, type TableModel } from './table-model';
23
22
  import { tableButtons, tableControls } from '../util';
23
+ import { type SelectionMode } from './selection-model';
24
+ import { type TableModel, type TableRow } from './table-model';
24
25
 
25
26
  /**
26
27
  * Presentation layer for a table component, handling cell rendering and grid display logic.
@@ -28,15 +29,20 @@ import { tableButtons, tableControls } from '../util';
28
29
  * different grid planes.
29
30
  */
30
31
  export class TablePresentation<T extends TableRow = TableRow> {
32
+ private readonly _registry: Registry.Registry;
33
+ private readonly _visibleRange: Atom.Writable<DxGridPlaneRange>;
31
34
  private fieldProjectionCache = new Map<string, ReturnType<typeof this.model.projection.getFieldProjection>>();
32
35
 
33
36
  constructor(
37
+ registry: Registry.Registry,
34
38
  private readonly model: TableModel<T>,
35
- private readonly _visibleRange = signal<DxGridPlaneRange>({
39
+ ) {
40
+ this._registry = registry;
41
+ this._visibleRange = Atom.make<DxGridPlaneRange>({
36
42
  start: { row: 0, col: 0 },
37
43
  end: { row: 0, col: 0 },
38
- }),
39
- ) {}
44
+ });
45
+ }
40
46
 
41
47
  public getCells(range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells {
42
48
  // Clear cache at the start of each render pass
@@ -46,7 +52,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
46
52
 
47
53
  switch (plane) {
48
54
  case 'grid':
49
- this._visibleRange.value = range;
55
+ this._registry.set(this._visibleRange, range);
50
56
  cells = this.getMainGridCells(range);
51
57
  break;
52
58
  case 'frozenRowsStart':
@@ -67,6 +73,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
67
73
  case 'frozenRowsEnd':
68
74
  cells = this.getDraftRowCells(range);
69
75
  break;
76
+ case 'fixedEndStart':
77
+ cells = this.getDraftIconCells(range);
78
+ break;
70
79
  case 'fixedEndEnd':
71
80
  cells = this.getDraftActionCells(range);
72
81
  break;
@@ -98,7 +107,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
98
107
  private createDataCell(
99
108
  cells: DxGridPlaneCells,
100
109
  obj: T,
101
- field: FieldType,
110
+ field: View.FieldType,
102
111
  colIndex: number,
103
112
  displayIndex: number,
104
113
  ): void {
@@ -116,12 +125,12 @@ export class TablePresentation<T extends TableRow = TableRow> {
116
125
  }
117
126
 
118
127
  switch (props.format) {
119
- case FormatEnum.Boolean:
120
- case FormatEnum.SingleSelect:
121
- case FormatEnum.MultiSelect: {
128
+ case Format.TypeFormat.Boolean:
129
+ case Format.TypeFormat.SingleSelect:
130
+ case Format.TypeFormat.MultiSelect: {
122
131
  return '';
123
132
  }
124
- case FormatEnum.Ref: {
133
+ case Format.TypeFormat.Ref: {
125
134
  if (!field.referencePath) {
126
135
  return ''; // TODO(burdon): Show error.
127
136
  }
@@ -137,20 +146,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
137
146
  };
138
147
 
139
148
  const classes = [];
140
- const formatClasses = cellClassesForFieldType({ type: props.type, format: props.format });
149
+ const formatClasses = cellClassesForFieldType(props);
150
+
141
151
  if (formatClasses) {
142
152
  classes.push(formatClasses);
143
153
  }
154
+
144
155
  const rowSelectionClasses = cellClassesForRowSelection(
145
156
  this.model.selection.isObjectSelected(obj),
146
157
  this.model.selection.selectionMode,
147
158
  );
159
+
148
160
  if (rowSelectionClasses) {
149
161
  classes.push(rowSelectionClasses);
150
162
  }
151
- if (classes.length > 0) {
152
- cell.className = mx(classes.flat());
153
- }
163
+
164
+ cell.className = mx(classes);
154
165
 
155
166
  // Arrays.
156
167
  if (props.type === TypeEnum.Array) {
@@ -184,9 +195,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
184
195
 
185
196
  const tags = targetArray
186
197
  .map(getLabel)
187
- .filter(isNotNullable)
198
+ .filter(Predicate.isNotNullable)
188
199
  .map((title) => {
189
- return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
200
+ return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
190
201
  })
191
202
  .join('');
192
203
 
@@ -195,16 +206,16 @@ export class TablePresentation<T extends TableRow = TableRow> {
195
206
  }
196
207
 
197
208
  // References.
198
- if (props.format === FormatEnum.Ref && props.referenceSchema) {
209
+ if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
199
210
  const targetObj = getValue(obj, field.path)?.target;
200
211
  if (targetObj) {
201
212
  const dxn = Obj.getDXN(targetObj)?.toString();
202
- cell.accessoryHtml = `<dx-ref-tag refId=${dxn} class="dx-button is-6 pli-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
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>`;
203
214
  }
204
215
  }
205
216
 
206
217
  // Booleans.
207
- if (props.format === FormatEnum.Boolean) {
218
+ if (props.format === Format.TypeFormat.Boolean) {
208
219
  const value = getValue(obj, field.path);
209
220
  cell.accessoryHtml = tableControls.switch.render({
210
221
  colIndex,
@@ -215,7 +226,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
215
226
  }
216
227
 
217
228
  // Single-Selects.
218
- if (props.format === FormatEnum.SingleSelect) {
229
+ if (props.format === Format.TypeFormat.SingleSelect) {
219
230
  const value = getValue(obj, field.path);
220
231
  const options = this.model.projection.getFieldProjection(field.id).props.options;
221
232
  if (options) {
@@ -227,7 +238,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
227
238
  }
228
239
 
229
240
  // Multi-Selects.
230
- if (props.format === FormatEnum.MultiSelect) {
241
+ if (props.format === Format.TypeFormat.MultiSelect) {
231
242
  const values = getValue(obj, field.path) as string[] | undefined;
232
243
  const options = this.model.projection.getFieldProjection(field.id).props.options;
233
244
  if (options && values && values.length > 0) {
@@ -254,7 +265,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
254
265
 
255
266
  private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
256
267
  const cells: DxGridPlaneCells = {};
257
- const fields = this.model.view?.fields ?? [];
268
+ const fields = this.model.projection?.getFields() ?? [];
258
269
 
259
270
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
260
271
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
@@ -263,7 +274,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
263
274
  continue;
264
275
  }
265
276
 
266
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
277
+ this.createDataCell(cells, this.model.getRows()[row], field, col, row);
267
278
  }
268
279
  }
269
280
 
@@ -272,26 +283,37 @@ export class TablePresentation<T extends TableRow = TableRow> {
272
283
 
273
284
  private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
274
285
  const cells: DxGridPlaneCells = {};
275
- const fields = this.model.view?.fields ?? [];
276
- const draftRows = this.model.draftRows.value;
286
+ const fields = this.model.projection?.getFields() ?? [];
287
+ const draftRows = this.model.getDraftRows();
277
288
 
278
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
279
- const draftRow = draftRows[row];
289
+ // Return cells of the CTA row if no draft row is active
290
+ if (draftRows.length === 0) {
280
291
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
281
- const field = fields[col];
282
- if (!field) {
283
- continue;
284
- }
292
+ cells[toPlaneCellIndex({ col, row: 0 })] = {
293
+ value: '',
294
+ readonly: true,
295
+ className: 'dx-grid__row--cta__cell',
296
+ };
297
+ }
298
+ } else {
299
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
300
+ const draftRow = draftRows[row];
301
+ for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
302
+ const cellIndex = toPlaneCellIndex({ col, row });
303
+ const field = fields[col];
304
+ if (!field) {
305
+ continue;
306
+ }
285
307
 
286
- this.createDataCell(cells, draftRow.data, field, col, row);
308
+ this.createDataCell(cells, draftRow.data, field, col, row);
287
309
 
288
- if (this.model.hasDraftRowValidationError(row, field.path)) {
289
- const cellIndex = toPlaneCellIndex({ col, row });
290
- const cellValue = cells[cellIndex];
291
- if (cellValue) {
292
- const existingClasses = cellValue.className || '';
293
- const draftClasses = 'dx-grid__cell--flagged';
294
- cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
310
+ if (this.model.hasDraftRowValidationError(row, field.path)) {
311
+ const cellValue = cells[cellIndex];
312
+ if (cellValue) {
313
+ const existingClasses = cellValue.className || '';
314
+ const draftClasses = 'dx-grid__cell--flagged';
315
+ cellValue.className = existingClasses ? `${existingClasses} ${draftClasses}` : draftClasses;
316
+ }
295
317
  }
296
318
  }
297
319
  }
@@ -302,22 +324,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
302
324
 
303
325
  private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
304
326
  const cells: DxGridPlaneCells = {};
305
- const fields = this.model.view?.fields ?? [];
327
+ const fields = this.model.projection?.getFields() ?? [];
328
+ const currentSort = this.model.getSorting();
306
329
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
307
- const { field, props } = this.getFieldProjection(fields[col].id);
308
- const sorting = this.model.sorting?.sorting;
309
- const direction = sorting?.fieldId === field.id ? sorting.direction : undefined;
330
+ const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
331
+ const direction = currentSort?.fieldId === field.id ? currentSort.direction : undefined;
310
332
 
311
333
  cells[toPlaneCellIndex({ col, row: 0 })] = {
312
334
  // TODO(burdon): Use same logic as form for fallback title.
313
- value: props.title ?? field.path,
314
- readonly: true,
335
+ value: '',
315
336
  resizeHandle: 'col',
316
337
  accessoryHtml: `
338
+ <span class="grow min-w-0 truncate">${props.title ?? field.path}</span>
317
339
  ${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
318
340
  ${tableButtons.columnSettings.render({ fieldId: field.id })}
319
341
  `,
320
- className: '!bg-toolbarSurface',
342
+ className: '!bg-toolbar-surface text-description! [&>div]:flex [&>div]:items-stretch',
321
343
  };
322
344
  }
323
345
 
@@ -360,7 +382,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
360
382
  if (!this.model.features.selection.enabled || this.model.selection.selectionMode === 'single') {
361
383
  return {
362
384
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
363
- className: '!bg-toolbarSurface',
385
+ className: '!bg-toolbar-surface',
364
386
  readonly: true,
365
387
  value: '',
366
388
  },
@@ -372,9 +394,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
372
394
  accessoryHtml: tableControls.checkbox.render({
373
395
  rowIndex: 0,
374
396
  header: true,
375
- checked: this.model.selection.allRowsSeleted.value,
397
+ checked: this.model.selection.allRowsSelected,
376
398
  }),
377
- className: '!bg-toolbarSurface',
399
+ className: '!bg-toolbar-surface',
378
400
  readonly: true,
379
401
  value: '',
380
402
  },
@@ -386,10 +408,10 @@ export class TablePresentation<T extends TableRow = TableRow> {
386
408
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
387
409
  accessoryHtml: this.model.features.schemaEditable
388
410
  ? tableButtons.addColumn.render({
389
- disabled: (this.model.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
411
+ disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT,
390
412
  })
391
413
  : undefined,
392
- className: '!bg-toolbarSurface',
414
+ className: '!bg-toolbar-surface',
393
415
  readonly: true,
394
416
  value: '',
395
417
  },
@@ -398,16 +420,41 @@ export class TablePresentation<T extends TableRow = TableRow> {
398
420
 
399
421
  private getDraftActionCells(range: DxGridPlaneRange): DxGridPlaneCells {
400
422
  const cells: DxGridPlaneCells = {};
401
- const draftRows = this.model.draftRows.value;
423
+ const draftRows = this.model.getDraftRows();
424
+
425
+ // Return cells of the CTA row if no draft row is active
426
+ if (draftRows.length === 0) {
427
+ cells[toPlaneCellIndex({ col: 0, row: 0 })] = {
428
+ value: '',
429
+ className: 'dx-grid__row--cta__cell',
430
+ readonly: true,
431
+ };
432
+ } else {
433
+ for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
434
+ const draftRow = draftRows[row];
435
+ const disabled = !draftRow.valid;
402
436
 
403
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
404
- const draftRow = draftRows[row];
405
- const disabled = !draftRow.valid;
437
+ cells[toPlaneCellIndex({ col: 0, row })] = {
438
+ value: '',
439
+ readonly: true,
440
+ accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
441
+ };
442
+ }
443
+ }
444
+
445
+ return cells;
446
+ }
447
+
448
+ private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
449
+ const cells: DxGridPlaneCells = {};
450
+ const draftRows = this.model.getDraftRows();
406
451
 
452
+ for (let row = range.start.row; row <= range.end.row; row++) {
407
453
  cells[toPlaneCellIndex({ col: 0, row })] = {
408
454
  value: '',
409
455
  readonly: true,
410
- accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
456
+ accessoryHtml: '<dx-icon icon="ph--plus--regular" class="contents"></dx-icon>',
457
+ className: mx('[&>div]:grid [&>div]:place-content-center', draftRows.length < 1 && 'dx-grid__row--cta__cell'),
411
458
  };
412
459
  }
413
460
 
@@ -418,7 +465,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
418
465
  export const cellClassesForRowSelection = (selected: boolean, selectionMode: SelectionMode) => {
419
466
  if (!selected) {
420
467
  if (selectionMode === 'single') {
421
- return ['!cursor-pointer'];
468
+ return ['cursor-pointer!'];
422
469
  } else {
423
470
  return undefined;
424
471
  }
@@ -426,9 +473,8 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
426
473
 
427
474
  switch (selectionMode) {
428
475
  case 'single':
429
- // TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
430
- return ['!bg-currentRelated hover:bg-hoverSurface', '!cursor-pointer'];
476
+ return ['dx-grid__cell--no-focus-unfurl !bg-current-surface hover:bg-hover-surface cursor-pointer!'];
431
477
  case 'multiple':
432
- return ['!bg-gridCellSelected'];
478
+ return ['!bg-grid-cell-selected'];
433
479
  }
434
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
  },