@dxos/react-ui-table 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8

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 (183) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/chunk-4KOQTPFC.mjs +1702 -0
  3. package/dist/lib/browser/chunk-4KOQTPFC.mjs.map +7 -0
  4. package/dist/lib/browser/chunk-62PYOJPY.mjs +67 -0
  5. package/dist/lib/browser/chunk-62PYOJPY.mjs.map +7 -0
  6. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  7. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  8. package/dist/lib/browser/index.mjs +1151 -1175
  9. package/dist/lib/browser/index.mjs.map +4 -4
  10. package/dist/lib/browser/meta.json +1 -1
  11. package/dist/lib/browser/testing/index.mjs +37 -45
  12. package/dist/lib/browser/testing/index.mjs.map +3 -3
  13. package/dist/lib/browser/translations.mjs +36 -0
  14. package/dist/lib/browser/translations.mjs.map +7 -0
  15. package/dist/lib/browser/types/index.mjs +4 -3
  16. package/dist/lib/node-esm/chunk-7LMOZUTS.mjs +68 -0
  17. package/dist/lib/node-esm/chunk-7LMOZUTS.mjs.map +7 -0
  18. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  19. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  20. package/dist/lib/node-esm/chunk-KOSV2J5N.mjs +1704 -0
  21. package/dist/lib/node-esm/chunk-KOSV2J5N.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +1151 -1175
  23. package/dist/lib/node-esm/index.mjs.map +4 -4
  24. package/dist/lib/node-esm/meta.json +1 -1
  25. package/dist/lib/node-esm/testing/index.mjs +37 -45
  26. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  27. package/dist/lib/node-esm/translations.mjs +37 -0
  28. package/dist/lib/node-esm/translations.mjs.map +7 -0
  29. package/dist/lib/node-esm/types/index.mjs +4 -3
  30. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  31. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  32. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  33. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  34. package/dist/types/src/components/Table/DynamicTable.d.ts +6 -7
  35. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  36. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +36 -3
  37. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/Table/Relations.stories.d.ts +112 -5
  39. package/dist/types/src/components/Table/Relations.stories.d.ts.map +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 -19
  42. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  43. package/dist/types/src/components/Table/Table.stories.d.ts +115 -5
  44. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/Table/TableContent.d.ts +18 -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 +12 -9
  52. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +7 -14
  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 +23 -16
  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 +9 -9
  70. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  71. package/dist/types/src/index.d.ts +0 -2
  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 -9
  76. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  77. package/dist/types/src/model/table-model.d.ts +111 -30
  78. package/dist/types/src/model/table-model.d.ts.map +1 -1
  79. package/dist/types/src/model/table-presentation.d.ts +4 -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/useTestTableModel.d.ts +18 -34
  85. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  86. package/dist/types/src/translations.d.ts +21 -23
  87. package/dist/types/src/translations.d.ts.map +1 -1
  88. package/dist/types/src/types/Table.d.ts +32 -0
  89. package/dist/types/src/types/Table.d.ts.map +1 -0
  90. package/dist/types/src/types/index.d.ts +1 -1
  91. package/dist/types/src/types/index.d.ts.map +1 -1
  92. package/dist/types/src/util/dynamic-table.d.ts +14 -12
  93. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  94. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  95. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  96. package/dist/types/src/util/index.d.ts +2 -1
  97. package/dist/types/src/util/index.d.ts.map +1 -1
  98. package/dist/types/src/util/query-ast.d.ts +7 -0
  99. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  100. package/dist/types/src/util/schema.d.ts +1 -1
  101. package/dist/types/src/util/schema.d.ts.map +1 -1
  102. package/dist/types/src/util/signals.d.ts.map +1 -1
  103. package/dist/types/src/util/sort.d.ts +12 -0
  104. package/dist/types/src/util/sort.d.ts.map +1 -0
  105. package/dist/types/src/util/table-buttons.d.ts +20 -20
  106. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  107. package/dist/types/src/util/table-controls.d.ts +4 -4
  108. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  109. package/dist/types/src/util/tag.d.ts.map +1 -1
  110. package/dist/types/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +68 -77
  112. package/src/components/Table/ColumnActionsMenu.tsx +17 -12
  113. package/src/components/Table/ColumnSettings.tsx +8 -9
  114. package/src/components/Table/DynamicTable.stories.tsx +41 -38
  115. package/src/components/Table/DynamicTable.tsx +32 -36
  116. package/src/components/Table/Relations.stories.tsx +219 -61
  117. package/src/components/Table/RowActionsMenu.tsx +27 -17
  118. package/src/components/Table/Table.stories.tsx +217 -141
  119. package/src/components/Table/Table.tsx +46 -391
  120. package/src/components/Table/TableContent.tsx +426 -0
  121. package/src/components/Table/TableToolbar.tsx +111 -0
  122. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
  123. package/src/components/TableCellEditor/FormCellEditor.tsx +136 -41
  124. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +39 -73
  125. package/src/components/TableCellEditor/TableCellEditor.tsx +81 -223
  126. package/src/components/TableCellEditor/index.ts +1 -0
  127. package/src/components/index.ts +0 -1
  128. package/src/components/testing.ts +42 -21
  129. package/src/hooks/index.ts +2 -1
  130. package/src/hooks/useAddRow.ts +14 -14
  131. package/src/hooks/useProjectionModel.ts +38 -0
  132. package/src/hooks/useTableModel.ts +36 -34
  133. package/src/index.ts +0 -4
  134. package/src/model/modal-controller.ts +23 -40
  135. package/src/model/selection-model.ts +75 -44
  136. package/src/model/table-model.test.ts +86 -56
  137. package/src/model/table-model.ts +464 -182
  138. package/src/model/table-presentation.test.ts +64 -59
  139. package/src/model/table-presentation.ts +107 -83
  140. package/src/playwright/TableManager.ts +13 -6
  141. package/src/playwright/playwright.config.ts +1 -1
  142. package/src/playwright/smoke.spec.ts +92 -46
  143. package/src/testing/useTestTableModel.ts +51 -42
  144. package/src/translations.ts +20 -22
  145. package/src/types/Table.ts +80 -0
  146. package/src/types/index.ts +1 -1
  147. package/src/util/dynamic-table.test.ts +63 -0
  148. package/src/util/dynamic-table.ts +53 -39
  149. package/src/util/index.ts +2 -1
  150. package/src/util/query-ast.ts +22 -0
  151. package/src/util/schema.ts +4 -4
  152. package/src/util/sort.ts +90 -0
  153. package/src/util/table-buttons.ts +3 -3
  154. package/src/util/table-controls.ts +2 -2
  155. package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
  156. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
  157. package/dist/lib/browser/chunk-ZAOSIG3L.mjs +0 -1814
  158. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +0 -7
  159. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
  160. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
  161. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +0 -1815
  162. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +0 -7
  163. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  164. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  165. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  166. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  167. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  168. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  169. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  170. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  171. package/dist/types/src/model/table-sorting.d.ts +0 -53
  172. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  173. package/dist/types/src/types/schema.d.ts +0 -8
  174. package/dist/types/src/types/schema.d.ts.map +0 -1
  175. package/dist/types/src/util/table.d.ts +0 -12
  176. package/dist/types/src/util/table.d.ts.map +0 -1
  177. package/src/components/Table/CreateRefPanel.tsx +0 -86
  178. package/src/components/TableCellEditor/extension.ts +0 -61
  179. package/src/components/TableToolbar/TableToolbar.tsx +0 -102
  180. package/src/components/TableToolbar/index.ts +0 -5
  181. package/src/model/table-sorting.ts +0 -216
  182. package/src/types/schema.ts +0 -16
  183. package/src/util/table.ts +0 -53
@@ -2,103 +2,108 @@
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 { Obj } 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 } from '@dxos/schema';
13
-
14
- import { TableView } from '../types';
9
+ import { DXN, Filter, JsonSchema, Query, Type } from '@dxos/echo';
10
+ import { createEchoSchema } from '@dxos/echo/testing';
11
+ import { ProjectionModel, ViewModel, createDirectChangeCallback } from '@dxos/schema';
15
12
 
13
+ import { Table } from '../types';
16
14
  import { TableModel, type TableModelProps } from './table-model';
17
15
  import { TablePresentation } from './table-presentation';
18
16
 
19
17
  describe('TablePresentation', () => {
20
- describe('row reactivity', () => {
18
+ describe('getCells', () => {
19
+ let registry: Registry.Registry;
21
20
  let data: any[];
22
- let updateCount: number;
23
21
  let model: any;
24
22
  let presentation: TablePresentation;
25
23
 
26
24
  beforeEach(async () => {
27
- updateCount = 0;
28
- ({ data } = live({
29
- data: [
30
- { col1: 'A', col2: 1, col3: true },
31
- { col1: 'B', col2: 2, col3: false },
32
- ],
33
- }));
34
-
35
- model = createTableModel({
36
- onCellUpdate: () => updateCount++,
37
- });
38
- 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, {});
39
32
  await model.open();
40
- presentation = new TablePresentation(model);
33
+ model.setRows(data);
34
+ presentation = new TablePresentation(registry, model);
41
35
  });
42
36
 
43
- it('should update with row-level reactivity', () => {
44
- // Set up visible range to include our test data.
45
- 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');
46
39
 
47
- // Trigger a row update.
48
- data[0].col1 = 'New Value';
49
- 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);
50
43
 
51
- // Verify the new value through getCells.
52
- const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 0, col: 0 } }, 'grid');
53
- 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');
54
48
  });
55
49
 
56
- it('should update reactively when adding a new row', () => {
57
- // Set up visible range to include our test data.
58
- 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);
54
+
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
+ });
59
60
 
60
- data.push({ col1: 'C', col2: 3, col3: true });
61
- expect(updateCount).toBe(1);
61
+ it('should reflect new rows', () => {
62
+ // Add a new row.
63
+ data.push({ id: '3', title: 'C', count: 3 });
64
+ model.setRows(data);
62
65
 
63
66
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
64
67
 
65
68
  // Verify the new row's data.
66
69
  expect(cells['0,2']?.value).toBe('C');
67
70
  expect(cells['1,2']?.value).toBe('3');
68
- expect(cells['2,2']?.value).toBe('true');
69
71
  });
70
72
 
71
- it('should handle combined operations reactively', () => {
72
- // Set up visible range.
73
- presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 2, col: 2 } }, 'grid');
74
-
75
- // Multiple operations.
76
- data[0].col1 = 'Updated A';
77
- 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 });
78
77
  data.splice(1, 1);
79
-
80
- // We expect one update per operation affecting visible rows.
81
- expect(updateCount).toBe(3);
78
+ model.setRows(data);
82
79
 
83
80
  const cells = presentation.getCells({ start: { row: 0, col: 0 }, end: { row: 1, col: 2 } }, 'grid');
84
81
 
85
82
  // Verify final state.
86
83
  expect(cells['0,0']?.value).toBe('Updated A');
87
84
  expect(cells['0,1']?.value).toBe('C');
88
- expect(cells['1,1']?.value).toBe('3');
89
- expect(cells['2,1']?.value).toBe('true');
90
85
  });
91
86
  });
92
87
  });
93
88
 
94
- class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
89
+ const Test = Schema.Struct({
95
90
  title: Schema.String,
96
- completed: Schema.Boolean,
97
- }) {}
98
-
99
- const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
100
- const schema = createEchoSchema(Test);
101
- const table = Obj.make(TableView, { sizes: {} });
102
- const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
103
- return new TableModel({ view, schema: schema.jsonSchema, ...props });
91
+ count: Schema.Number,
92
+ }).pipe(Type.makeObject(DXN.make('com.example.type.test', '0.1.0')));
93
+
94
+ const createTableModel = (registry: Registry.Registry, props: Partial<TableModelProps> = {}): TableModel => {
95
+ const schema = createEchoSchema(Type.getSchema(Test));
96
+ const view = ViewModel.make({
97
+ query: Query.select(Filter.type(schema)),
98
+ jsonSchema: schema.jsonSchema,
99
+ });
100
+ const object = Table.make({ view });
101
+ const projection = new ProjectionModel({
102
+ registry,
103
+ view,
104
+ baseSchema: schema.jsonSchema,
105
+ change: createDirectChangeCallback(view.projection, JsonSchema.toJsonSchema(Type.getSchema(schema))),
106
+ });
107
+ projection.normalizeView();
108
+ return new TableModel({ registry, object, projection, ...props });
104
109
  };
@@ -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
- import { Obj } from '@dxos/echo';
9
- import { FormatEnum, TypeEnum, getValue } from '@dxos/echo-schema';
8
+ import { Obj, type View } from '@dxos/echo';
9
+ import { Format, TypeEnum } from '@dxos/echo/Format';
10
+ import { SchemaEx } from '@dxos/effect';
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':
@@ -69,7 +74,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
69
74
  cells = this.getDraftRowCells(range);
70
75
  break;
71
76
  case 'fixedEndStart':
72
- cells = this.getDraftSelectCells(range);
77
+ cells = this.getDraftIconCells(range);
73
78
  break;
74
79
  case 'fixedEndEnd':
75
80
  cells = this.getDraftActionCells(range);
@@ -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 {
@@ -110,7 +115,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
110
115
 
111
116
  const cell: DxGridCellValue = {
112
117
  get value() {
113
- const value = getValue(obj, field.path);
118
+ const value = SchemaEx.getValue(obj, field.path);
114
119
  if (value == null) {
115
120
  return '';
116
121
  }
@@ -120,17 +125,17 @@ 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
  }
132
137
 
133
- return getValue(value.target, field.referencePath);
138
+ return SchemaEx.getValue(value.target, field.referencePath);
134
139
  }
135
140
 
136
141
  default: {
@@ -141,24 +146,26 @@ export class TablePresentation<T extends TableRow = TableRow> {
141
146
  };
142
147
 
143
148
  const classes = [];
144
- const formatClasses = cellClassesForFieldType({ type: props.type, format: props.format });
149
+ const formatClasses = cellClassesForFieldType(props);
150
+
145
151
  if (formatClasses) {
146
152
  classes.push(formatClasses);
147
153
  }
154
+
148
155
  const rowSelectionClasses = cellClassesForRowSelection(
149
156
  this.model.selection.isObjectSelected(obj),
150
157
  this.model.selection.selectionMode,
151
158
  );
159
+
152
160
  if (rowSelectionClasses) {
153
161
  classes.push(rowSelectionClasses);
154
162
  }
155
- if (classes.length > 0) {
156
- cell.className = mx(classes.flat());
157
- }
163
+
164
+ cell.className = mx(classes);
158
165
 
159
166
  // Arrays.
160
167
  if (props.type === TypeEnum.Array) {
161
- const targetArray = getValue(obj, field.path);
168
+ const targetArray = SchemaEx.getValue(obj, field.path);
162
169
  if (targetArray && Array.isArray(targetArray)) {
163
170
  // TODO(ZaymonFC): For arrays of objects, objects should have a 'label' annotation on the field
164
171
  // that can be used to render tags.
@@ -188,28 +195,28 @@ export class TablePresentation<T extends TableRow = TableRow> {
188
195
 
189
196
  const tags = targetArray
190
197
  .map(getLabel)
191
- .filter(isNotNullable)
198
+ .filter(Predicate.isNotNullable)
192
199
  .map((title) => {
193
200
  return `<span class="dx-tag" data-hue="neutral">${title}</span>`;
194
201
  })
195
202
  .join('');
196
203
 
197
- cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
204
+ cell.accessoryHtml = `<div class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
198
205
  }
199
206
  }
200
207
 
201
208
  // References.
202
- if (props.format === FormatEnum.Ref && props.referenceSchema) {
203
- const targetObj = getValue(obj, field.path)?.target;
209
+ if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
210
+ const targetObj = SchemaEx.getValue(obj, field.path)?.target;
204
211
  if (targetObj) {
205
- 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>`;
212
+ const uri = Obj.getURI(targetObj);
213
+ cell.accessoryHtml = `<div role="none" class="absolute end-0 inset-y-0 p-(--dx-grid-cell-content-padding-block)"><dx-anchor uri=${uri} 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>`;
207
214
  }
208
215
  }
209
216
 
210
217
  // Booleans.
211
- if (props.format === FormatEnum.Boolean) {
212
- const value = getValue(obj, field.path);
218
+ if (props.format === Format.TypeFormat.Boolean) {
219
+ const value = SchemaEx.getValue(obj, field.path);
213
220
  cell.accessoryHtml = tableControls.switch.render({
214
221
  colIndex,
215
222
  rowIndex: displayIndex,
@@ -219,8 +226,8 @@ export class TablePresentation<T extends TableRow = TableRow> {
219
226
  }
220
227
 
221
228
  // Single-Selects.
222
- if (props.format === FormatEnum.SingleSelect) {
223
- const value = getValue(obj, field.path);
229
+ if (props.format === Format.TypeFormat.SingleSelect) {
230
+ const value = SchemaEx.getValue(obj, field.path);
224
231
  const options = this.model.projection.getFieldProjection(field.id).props.options;
225
232
  if (options) {
226
233
  const option = options.find((o) => o.id === value);
@@ -231,8 +238,8 @@ export class TablePresentation<T extends TableRow = TableRow> {
231
238
  }
232
239
 
233
240
  // Multi-Selects.
234
- if (props.format === FormatEnum.MultiSelect) {
235
- const values = getValue(obj, field.path) as string[] | undefined;
241
+ if (props.format === Format.TypeFormat.MultiSelect) {
242
+ const values = SchemaEx.getValue(obj, field.path) as string[] | undefined;
236
243
  const options = this.model.projection.getFieldProjection(field.id).props.options;
237
244
  if (options && values && values.length > 0) {
238
245
  const tags = values
@@ -258,7 +265,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
258
265
 
259
266
  private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
260
267
  const cells: DxGridPlaneCells = {};
261
- const fields = this.model.projection?.fields ?? [];
268
+ const fields = this.model.projection?.getFields() ?? [];
262
269
 
263
270
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
264
271
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
@@ -267,7 +274,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
267
274
  continue;
268
275
  }
269
276
 
270
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
277
+ this.createDataCell(cells, this.model.getRows()[row], field, col, row);
271
278
  }
272
279
  }
273
280
 
@@ -276,30 +283,39 @@ export class TablePresentation<T extends TableRow = TableRow> {
276
283
 
277
284
  private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
278
285
  const cells: DxGridPlaneCells = {};
279
- const fields = this.model.projection?.fields ?? [];
280
- const draftRows = this.model.draftRows.value;
286
+ const fields = this.model.projection?.getFields() ?? [];
287
+ const draftRows = this.model.getDraftRows();
281
288
 
282
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
283
- const draftRow = draftRows[row];
289
+ // Return cells of the CTA row if no draft row is active
290
+ if (draftRows.length === 0) {
284
291
  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
- }
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
+ }
290
307
 
291
- this.createDataCell(cells, draftRow.data, field, col, row);
308
+ this.createDataCell(cells, draftRow.data, field, col, row);
292
309
 
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;
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
+ }
299
317
  }
300
318
  }
301
-
302
- cells[cellIndex].className += ' !bg-toolbarSurface';
303
319
  }
304
320
  }
305
321
 
@@ -308,22 +324,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
308
324
 
309
325
  private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
310
326
  const cells: DxGridPlaneCells = {};
311
- const fields = this.model.projection?.fields ?? [];
327
+ const fields = this.model.projection?.getFields() ?? [];
328
+ const currentSort = this.model.getSorting();
312
329
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
313
330
  const { field, props } = this.model.projection.getFieldProjection(fields[col].id);
314
- const sorting = this.model.sorting?.sorting;
315
- const direction = sorting?.fieldId === field.id ? sorting.direction : undefined;
331
+ const direction = currentSort?.fieldId === field.id ? currentSort.direction : undefined;
316
332
 
317
333
  cells[toPlaneCellIndex({ col, row: 0 })] = {
318
334
  // TODO(burdon): Use same logic as form for fallback title.
319
- value: props.title ?? field.path,
320
- readonly: true,
335
+ value: '',
321
336
  resizeHandle: 'col',
322
337
  accessoryHtml: `
338
+ <span class="grow min-w-0 truncate">${props.title ?? field.path}</span>
323
339
  ${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
324
340
  ${tableButtons.columnSettings.render({ fieldId: field.id })}
325
341
  `,
326
- className: '!bg-toolbarSurface',
342
+ className: '!bg-toolbar-surface text-description! [&>div]:flex [&>div]:items-stretch',
327
343
  };
328
344
  }
329
345
 
@@ -366,7 +382,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
366
382
  if (!this.model.features.selection.enabled || this.model.selection.selectionMode === 'single') {
367
383
  return {
368
384
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
369
- className: '!bg-toolbarSurface',
385
+ className: '!bg-toolbar-surface',
370
386
  readonly: true,
371
387
  value: '',
372
388
  },
@@ -378,9 +394,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
378
394
  accessoryHtml: tableControls.checkbox.render({
379
395
  rowIndex: 0,
380
396
  header: true,
381
- checked: this.model.selection.allRowsSeleted.value,
397
+ checked: this.model.selection.allRowsSelected,
382
398
  }),
383
- className: '!bg-toolbarSurface',
399
+ className: '!bg-toolbar-surface',
384
400
  readonly: true,
385
401
  value: '',
386
402
  },
@@ -392,10 +408,10 @@ export class TablePresentation<T extends TableRow = TableRow> {
392
408
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
393
409
  accessoryHtml: this.model.features.schemaEditable
394
410
  ? tableButtons.addColumn.render({
395
- disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
411
+ disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT,
396
412
  })
397
413
  : undefined,
398
- className: '!bg-toolbarSurface',
414
+ className: '!bg-toolbar-surface',
399
415
  readonly: true,
400
416
  value: '',
401
417
  },
@@ -404,32 +420,41 @@ export class TablePresentation<T extends TableRow = TableRow> {
404
420
 
405
421
  private getDraftActionCells(range: DxGridPlaneRange): DxGridPlaneCells {
406
422
  const cells: DxGridPlaneCells = {};
407
- const draftRows = this.model.draftRows.value;
408
-
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;
423
+ const draftRows = this.model.getDraftRows();
412
424
 
413
- cells[toPlaneCellIndex({ col: 0, row })] = {
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 })] = {
414
428
  value: '',
429
+ className: 'dx-grid__row--cta__cell',
415
430
  readonly: true,
416
- accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
417
- className: '!bg-toolbarSurface',
418
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;
436
+
437
+ cells[toPlaneCellIndex({ col: 0, row })] = {
438
+ value: '',
439
+ readonly: true,
440
+ accessoryHtml: tableButtons.saveDraftRow.render({ rowIndex: row, disabled }),
441
+ };
442
+ }
419
443
  }
420
444
 
421
445
  return cells;
422
446
  }
423
447
 
424
- private getDraftSelectCells(range: DxGridPlaneRange): DxGridPlaneCells {
448
+ private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
425
449
  const cells: DxGridPlaneCells = {};
426
- const draftRows = this.model.draftRows.value;
450
+ const draftRows = this.model.getDraftRows();
427
451
 
428
- for (let row = range.start.row; row <= range.end.row && row < draftRows.length; row++) {
452
+ for (let row = range.start.row; row <= range.end.row; row++) {
429
453
  cells[toPlaneCellIndex({ col: 0, row })] = {
430
454
  value: '',
431
455
  readonly: true,
432
- className: '!bg-toolbarSurface',
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'),
433
458
  };
434
459
  }
435
460
 
@@ -440,7 +465,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
440
465
  export const cellClassesForRowSelection = (selected: boolean, selectionMode: SelectionMode) => {
441
466
  if (!selected) {
442
467
  if (selectionMode === 'single') {
443
- return ['!cursor-pointer'];
468
+ return ['cursor-pointer!'];
444
469
  } else {
445
470
  return undefined;
446
471
  }
@@ -448,9 +473,8 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
448
473
 
449
474
  switch (selectionMode) {
450
475
  case 'single':
451
- // TODO(ZaymonFC): @thure, do we need a grid version of 'currentRelated'?
452
- 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!'];
453
477
  case 'multiple':
454
- return ['!bg-gridCellSelected'];
478
+ return ['bg-grid-cell-selected!'];
455
479
  }
456
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
  },