@dxos/react-ui-table 0.8.4-main.e8ec1fe → 0.8.4-main.effb148878

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 (166) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  3. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  4. package/dist/lib/browser/{chunk-GS2UNGJK.mjs → chunk-KC62K4WS.mjs} +643 -722
  5. package/dist/lib/browser/chunk-KC62K4WS.mjs.map +7 -0
  6. package/dist/lib/browser/chunk-VWQWF3CP.mjs +67 -0
  7. package/dist/lib/browser/chunk-VWQWF3CP.mjs.map +7 -0
  8. package/dist/lib/browser/index.mjs +1103 -1023
  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 +35 -46
  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-TRR3C5NX.mjs → chunk-62FOU4M7.mjs} +643 -721
  17. package/dist/lib/node-esm/chunk-62FOU4M7.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-UBXOF64L.mjs +68 -0
  21. package/dist/lib/node-esm/chunk-UBXOF64L.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +1103 -1023
  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 +35 -46
  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 +22 -37
  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 +85 -80
  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 +20 -21
  42. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  43. package/dist/types/src/components/Table/Table.stories.d.ts +93 -26
  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 +10 -12
  52. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
  54. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  55. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +24 -26
  56. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/index.d.ts +0 -1
  58. package/dist/types/src/components/index.d.ts.map +1 -1
  59. package/dist/types/src/components/testing.d.ts +22 -15
  60. package/dist/types/src/components/testing.d.ts.map +1 -1
  61. package/dist/types/src/hooks/index.d.ts +2 -1
  62. package/dist/types/src/hooks/index.d.ts.map +1 -1
  63. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  64. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
  66. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  67. package/dist/types/src/hooks/useTableModel.d.ts +9 -9
  68. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  69. package/dist/types/src/index.d.ts +0 -2
  70. package/dist/types/src/index.d.ts.map +1 -1
  71. package/dist/types/src/model/modal-controller.d.ts +5 -1
  72. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  73. package/dist/types/src/model/selection-model.d.ts +18 -9
  74. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  75. package/dist/types/src/model/table-model.d.ts +103 -24
  76. package/dist/types/src/model/table-model.d.ts.map +1 -1
  77. package/dist/types/src/model/table-presentation.d.ts +3 -1
  78. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  79. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  80. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  81. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  82. package/dist/types/src/testing/useTestTableModel.d.ts +18 -15
  83. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  84. package/dist/types/src/translations.d.ts +21 -23
  85. package/dist/types/src/translations.d.ts.map +1 -1
  86. package/dist/types/src/types/Table.d.ts +32 -0
  87. package/dist/types/src/types/Table.d.ts.map +1 -0
  88. package/dist/types/src/types/index.d.ts +1 -1
  89. package/dist/types/src/types/index.d.ts.map +1 -1
  90. package/dist/types/src/util/dynamic-table.d.ts +14 -11
  91. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  92. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  93. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  94. package/dist/types/src/util/index.d.ts +2 -0
  95. package/dist/types/src/util/index.d.ts.map +1 -1
  96. package/dist/types/src/util/query-ast.d.ts +7 -0
  97. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  98. package/dist/types/src/util/schema.d.ts.map +1 -1
  99. package/dist/types/src/util/signals.d.ts.map +1 -1
  100. package/dist/types/src/util/sort.d.ts +12 -0
  101. package/dist/types/src/util/sort.d.ts.map +1 -0
  102. package/dist/types/src/util/table-buttons.d.ts +20 -20
  103. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  104. package/dist/types/src/util/table-controls.d.ts +4 -4
  105. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  106. package/dist/types/src/util/tag.d.ts.map +1 -1
  107. package/dist/types/tsconfig.tsbuildinfo +1 -1
  108. package/package.json +69 -79
  109. package/src/components/Table/ColumnActionsMenu.tsx +17 -12
  110. package/src/components/Table/ColumnSettings.tsx +8 -9
  111. package/src/components/Table/DynamicTable.stories.tsx +32 -32
  112. package/src/components/Table/DynamicTable.tsx +32 -33
  113. package/src/components/Table/Relations.stories.tsx +104 -64
  114. package/src/components/Table/RowActionsMenu.tsx +6 -4
  115. package/src/components/Table/Table.stories.tsx +125 -112
  116. package/src/components/Table/Table.tsx +43 -407
  117. package/src/components/Table/TableContent.tsx +426 -0
  118. package/src/components/{TableToolbar → Table}/TableToolbar.tsx +44 -45
  119. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
  120. package/src/components/TableCellEditor/FormCellEditor.tsx +120 -136
  121. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +25 -19
  122. package/src/components/TableCellEditor/TableCellEditor.tsx +35 -33
  123. package/src/components/index.ts +0 -1
  124. package/src/components/testing.ts +38 -17
  125. package/src/hooks/index.ts +2 -1
  126. package/src/hooks/useAddRow.ts +8 -10
  127. package/src/hooks/useProjectionModel.ts +38 -0
  128. package/src/hooks/useTableModel.ts +36 -34
  129. package/src/index.ts +0 -4
  130. package/src/model/modal-controller.ts +23 -12
  131. package/src/model/selection-model.ts +75 -44
  132. package/src/model/table-model.test.ts +77 -55
  133. package/src/model/table-model.ts +431 -168
  134. package/src/model/table-presentation.test.ts +54 -50
  135. package/src/model/table-presentation.ts +44 -40
  136. package/src/playwright/TableManager.ts +13 -6
  137. package/src/playwright/playwright.config.ts +1 -1
  138. package/src/playwright/smoke.spec.ts +52 -8
  139. package/src/testing/useTestTableModel.ts +49 -43
  140. package/src/translations.ts +20 -22
  141. package/src/types/Table.ts +81 -0
  142. package/src/types/index.ts +1 -1
  143. package/src/util/dynamic-table.test.ts +63 -0
  144. package/src/util/dynamic-table.ts +48 -33
  145. package/src/util/index.ts +2 -0
  146. package/src/util/query-ast.ts +22 -0
  147. package/src/util/sort.ts +90 -0
  148. package/src/util/table-buttons.ts +1 -1
  149. package/src/util/table-controls.ts +2 -2
  150. package/dist/lib/browser/chunk-2PFAPQL4.mjs +0 -66
  151. package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +0 -7
  152. package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +0 -7
  153. package/dist/lib/node-esm/chunk-6POOFFYF.mjs +0 -67
  154. package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +0 -7
  155. package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +0 -7
  156. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  157. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  158. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  159. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  160. package/dist/types/src/model/table-sorting.d.ts +0 -52
  161. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  162. package/dist/types/src/types/schema.d.ts +0 -28
  163. package/dist/types/src/types/schema.d.ts.map +0 -1
  164. package/src/components/TableToolbar/index.ts +0 -5
  165. package/src/model/table-sorting.ts +0 -225
  166. package/src/types/schema.ts +0 -73
@@ -2,63 +2,66 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { Registry } from '@effect-atom/atom-react';
5
6
  import * as Schema from 'effect/Schema';
6
7
  import { beforeEach, describe, expect, it } from 'vitest';
7
8
 
8
- import { Filter, Query } from '@dxos/echo';
9
- import { TypedObject } from '@dxos/echo/internal';
9
+ import { DXN, Filter, JsonSchema, Query, Type } from '@dxos/echo';
10
10
  import { createEchoSchema } from '@dxos/echo/testing';
11
- import { live } from '@dxos/live-object';
12
- import { View } from '@dxos/schema';
11
+ import { ProjectionModel, ViewModel, createDirectChangeCallback } from '@dxos/schema';
13
12
 
14
13
  import { Table } from '../types';
15
-
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
- { title: 'A', count: 1 },
31
- { title: 'B', count: 2 },
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');
39
+
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);
43
+
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');
48
+ });
46
49
 
47
- // Trigger a row update.
50
+ it('should reflect row data changes', () => {
51
+ // Update the row data and refresh.
48
52
  data[0].title = 'New Value';
49
- expect(updateCount).toBe(2);
53
+ model.setRows(data);
50
54
 
51
55
  // 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');
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');
54
59
  });
55
60
 
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');
59
-
60
- data.push({ title: 'C', count: 3 });
61
- expect(updateCount).toBe(2);
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
 
@@ -67,17 +70,12 @@ describe('TablePresentation', () => {
67
70
  expect(cells['1,2']?.value).toBe('3');
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.
73
+ it('should handle row modifications', () => {
74
+ // Modify the data.
75
75
  data[0].title = 'Updated A';
76
- data.push({ title: 'C', count: 3, completed: true });
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(4);
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
 
@@ -88,18 +86,24 @@ describe('TablePresentation', () => {
88
86
  });
89
87
  });
90
88
 
91
- class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
89
+ const Test = Schema.Struct({
92
90
  title: Schema.String,
93
91
  count: Schema.Number,
94
- }) {}
92
+ }).pipe(Type.makeObject(DXN.make('com.example.type.test', '0.1.0')));
95
93
 
96
- const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
97
- const schema = createEchoSchema(Test);
98
- const table = Table.make();
99
- const view = View.make({
94
+ const createTableModel = (registry: Registry.Registry, props: Partial<TableModelProps> = {}): TableModel => {
95
+ const schema = createEchoSchema(Type.getSchema(Test));
96
+ const view = ViewModel.make({
100
97
  query: Query.select(Filter.type(schema)),
101
98
  jsonSchema: schema.jsonSchema,
102
- presentation: table,
103
99
  });
104
- return new TableModel({ view, schema: schema.jsonSchema, ...props });
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 });
105
109
  };
@@ -2,11 +2,11 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { signal } from '@preact/signals-core';
5
+ import { Atom, type Registry } from '@effect-atom/atom-react';
6
6
  import * as Predicate from 'effect/Predicate';
7
7
 
8
- import { Obj } from '@dxos/echo';
9
- import { FormatEnum, TypeEnum, getValue } from '@dxos/echo/internal';
8
+ import { Obj, type View } from '@dxos/echo';
9
+ import { Format, TypeEnum, getValue } from '@dxos/echo/internal';
10
10
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
11
11
  import {
12
12
  type DxGridCellValue,
@@ -15,11 +15,10 @@ import {
15
15
  type DxGridPlaneRange,
16
16
  toPlaneCellIndex,
17
17
  } from '@dxos/react-ui-grid';
18
- import { mx } from '@dxos/react-ui-theme';
19
- import { type FieldType, VIEW_FIELD_LIMIT } from '@dxos/schema';
18
+ import { VIEW_FIELD_LIMIT } from '@dxos/schema';
19
+ import { mx } from '@dxos/ui-theme';
20
20
 
21
21
  import { tableButtons, tableControls } from '../util';
22
-
23
22
  import { type SelectionMode } from './selection-model';
24
23
  import { type TableModel, type TableRow } from './table-model';
25
24
 
@@ -29,15 +28,20 @@ import { type TableModel, type TableRow } from './table-model';
29
28
  * different grid planes.
30
29
  */
31
30
  export class TablePresentation<T extends TableRow = TableRow> {
31
+ private readonly _registry: Registry.Registry;
32
+ private readonly _visibleRange: Atom.Writable<DxGridPlaneRange>;
32
33
  private fieldProjectionCache = new Map<string, ReturnType<typeof this.model.projection.getFieldProjection>>();
33
34
 
34
35
  constructor(
36
+ registry: Registry.Registry,
35
37
  private readonly model: TableModel<T>,
36
- private readonly _visibleRange = signal<DxGridPlaneRange>({
38
+ ) {
39
+ this._registry = registry;
40
+ this._visibleRange = Atom.make<DxGridPlaneRange>({
37
41
  start: { row: 0, col: 0 },
38
42
  end: { row: 0, col: 0 },
39
- }),
40
- ) {}
43
+ });
44
+ }
41
45
 
42
46
  public getCells(range: DxGridPlaneRange, plane: DxGridPlane): DxGridPlaneCells {
43
47
  // Clear cache at the start of each render pass
@@ -47,7 +51,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
47
51
 
48
52
  switch (plane) {
49
53
  case 'grid':
50
- this._visibleRange.value = range;
54
+ this._registry.set(this._visibleRange, range);
51
55
  cells = this.getMainGridCells(range);
52
56
  break;
53
57
  case 'frozenRowsStart':
@@ -102,7 +106,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
102
106
  private createDataCell(
103
107
  cells: DxGridPlaneCells,
104
108
  obj: T,
105
- field: FieldType,
109
+ field: View.FieldType,
106
110
  colIndex: number,
107
111
  displayIndex: number,
108
112
  ): void {
@@ -120,12 +124,12 @@ export class TablePresentation<T extends TableRow = TableRow> {
120
124
  }
121
125
 
122
126
  switch (props.format) {
123
- case FormatEnum.Boolean:
124
- case FormatEnum.SingleSelect:
125
- case FormatEnum.MultiSelect: {
127
+ case Format.TypeFormat.Boolean:
128
+ case Format.TypeFormat.SingleSelect:
129
+ case Format.TypeFormat.MultiSelect: {
126
130
  return '';
127
131
  }
128
- case FormatEnum.Ref: {
132
+ case Format.TypeFormat.Ref: {
129
133
  if (!field.referencePath) {
130
134
  return ''; // TODO(burdon): Show error.
131
135
  }
@@ -201,16 +205,16 @@ export class TablePresentation<T extends TableRow = TableRow> {
201
205
  }
202
206
 
203
207
  // References.
204
- if (props.format === FormatEnum.Ref && props.referenceSchema) {
208
+ if (props.format === Format.TypeFormat.Ref && props.referenceSchema) {
205
209
  const targetObj = getValue(obj, field.path)?.target;
206
210
  if (targetObj) {
207
- const dxn = Obj.getDXN(targetObj)?.toString();
208
- cell.accessoryHtml = `<div role="none" class="absolute inline-end-0 inset-block-0 p-[--dx-grid-cell-content-padding-block]"><dx-anchor refId=${dxn} class="dx-button is-6 aspect-square min-bs-0" data-dx-grid-action="accessory"><dx-icon icon="ph--link-simple--regular"/></dx-anchor></div>`;
211
+ const uri = Obj.getURI(targetObj);
212
+ 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>`;
209
213
  }
210
214
  }
211
215
 
212
216
  // Booleans.
213
- if (props.format === FormatEnum.Boolean) {
217
+ if (props.format === Format.TypeFormat.Boolean) {
214
218
  const value = getValue(obj, field.path);
215
219
  cell.accessoryHtml = tableControls.switch.render({
216
220
  colIndex,
@@ -221,7 +225,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
221
225
  }
222
226
 
223
227
  // Single-Selects.
224
- if (props.format === FormatEnum.SingleSelect) {
228
+ if (props.format === Format.TypeFormat.SingleSelect) {
225
229
  const value = getValue(obj, field.path);
226
230
  const options = this.model.projection.getFieldProjection(field.id).props.options;
227
231
  if (options) {
@@ -233,7 +237,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
233
237
  }
234
238
 
235
239
  // Multi-Selects.
236
- if (props.format === FormatEnum.MultiSelect) {
240
+ if (props.format === Format.TypeFormat.MultiSelect) {
237
241
  const values = getValue(obj, field.path) as string[] | undefined;
238
242
  const options = this.model.projection.getFieldProjection(field.id).props.options;
239
243
  if (options && values && values.length > 0) {
@@ -260,7 +264,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
260
264
 
261
265
  private getMainGridCells(range: DxGridPlaneRange): DxGridPlaneCells {
262
266
  const cells: DxGridPlaneCells = {};
263
- const fields = this.model.projection?.fields ?? [];
267
+ const fields = this.model.projection?.getFields() ?? [];
264
268
 
265
269
  for (let row = range.start.row; row <= range.end.row && row < this.model.getRowCount(); row++) {
266
270
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
@@ -269,7 +273,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
269
273
  continue;
270
274
  }
271
275
 
272
- this.createDataCell(cells, this.model.rows.value[row], field, col, row);
276
+ this.createDataCell(cells, this.model.getRows()[row], field, col, row);
273
277
  }
274
278
  }
275
279
 
@@ -278,8 +282,8 @@ export class TablePresentation<T extends TableRow = TableRow> {
278
282
 
279
283
  private getDraftRowCells(range: DxGridPlaneRange): DxGridPlaneCells {
280
284
  const cells: DxGridPlaneCells = {};
281
- const fields = this.model.projection?.fields ?? [];
282
- const draftRows = this.model.draftRows.value;
285
+ const fields = this.model.projection?.getFields() ?? [];
286
+ const draftRows = this.model.getDraftRows();
283
287
 
284
288
  // Return cells of the CTA row if no draft row is active
285
289
  if (draftRows.length === 0) {
@@ -319,22 +323,22 @@ export class TablePresentation<T extends TableRow = TableRow> {
319
323
 
320
324
  private getHeaderCells(range: DxGridPlaneRange): DxGridPlaneCells {
321
325
  const cells: DxGridPlaneCells = {};
322
- const fields = this.model.projection?.fields ?? [];
326
+ const fields = this.model.projection?.getFields() ?? [];
327
+ const currentSort = this.model.getSorting();
323
328
  for (let col = range.start.col; col <= range.end.col && col < fields.length; col++) {
324
329
  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;
330
+ const direction = currentSort?.fieldId === field.id ? currentSort.direction : undefined;
327
331
 
328
332
  cells[toPlaneCellIndex({ col, row: 0 })] = {
329
333
  // TODO(burdon): Use same logic as form for fallback title.
330
334
  value: '',
331
335
  resizeHandle: 'col',
332
336
  accessoryHtml: `
333
- <span class="grow min-is-0 truncate">${props.title ?? field.path}</span>
337
+ <span class="grow min-w-0 truncate">${props.title ?? field.path}</span>
334
338
  ${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
335
339
  ${tableButtons.columnSettings.render({ fieldId: field.id })}
336
340
  `,
337
- className: '!bg-toolbarSurface !text-description [&>div]:flex [&>div]:items-stretch',
341
+ className: '!bg-toolbar-surface text-description! [&>div]:flex [&>div]:items-stretch',
338
342
  };
339
343
  }
340
344
 
@@ -377,7 +381,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
377
381
  if (!this.model.features.selection.enabled || this.model.selection.selectionMode === 'single') {
378
382
  return {
379
383
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
380
- className: '!bg-toolbarSurface',
384
+ className: '!bg-toolbar-surface',
381
385
  readonly: true,
382
386
  value: '',
383
387
  },
@@ -389,9 +393,9 @@ export class TablePresentation<T extends TableRow = TableRow> {
389
393
  accessoryHtml: tableControls.checkbox.render({
390
394
  rowIndex: 0,
391
395
  header: true,
392
- checked: this.model.selection.allRowsSeleted.value,
396
+ checked: this.model.selection.allRowsSelected,
393
397
  }),
394
- className: '!bg-toolbarSurface',
398
+ className: '!bg-toolbar-surface',
395
399
  readonly: true,
396
400
  value: '',
397
401
  },
@@ -403,10 +407,10 @@ export class TablePresentation<T extends TableRow = TableRow> {
403
407
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
404
408
  accessoryHtml: this.model.features.schemaEditable
405
409
  ? tableButtons.addColumn.render({
406
- disabled: (this.model.projection?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
410
+ disabled: (this.model.projection?.getFields()?.length ?? 0) >= VIEW_FIELD_LIMIT,
407
411
  })
408
412
  : undefined,
409
- className: '!bg-toolbarSurface',
413
+ className: '!bg-toolbar-surface',
410
414
  readonly: true,
411
415
  value: '',
412
416
  },
@@ -415,7 +419,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
415
419
 
416
420
  private getDraftActionCells(range: DxGridPlaneRange): DxGridPlaneCells {
417
421
  const cells: DxGridPlaneCells = {};
418
- const draftRows = this.model.draftRows.value;
422
+ const draftRows = this.model.getDraftRows();
419
423
 
420
424
  // Return cells of the CTA row if no draft row is active
421
425
  if (draftRows.length === 0) {
@@ -442,7 +446,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
442
446
 
443
447
  private getDraftIconCells(range: DxGridPlaneRange): DxGridPlaneCells {
444
448
  const cells: DxGridPlaneCells = {};
445
- const draftRows = this.model.draftRows.value;
449
+ const draftRows = this.model.getDraftRows();
446
450
 
447
451
  for (let row = range.start.row; row <= range.end.row; row++) {
448
452
  cells[toPlaneCellIndex({ col: 0, row })] = {
@@ -460,7 +464,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
460
464
  export const cellClassesForRowSelection = (selected: boolean, selectionMode: SelectionMode) => {
461
465
  if (!selected) {
462
466
  if (selectionMode === 'single') {
463
- return ['!cursor-pointer'];
467
+ return ['cursor-pointer!'];
464
468
  } else {
465
469
  return undefined;
466
470
  }
@@ -468,8 +472,8 @@ export const cellClassesForRowSelection = (selected: boolean, selectionMode: Sel
468
472
 
469
473
  switch (selectionMode) {
470
474
  case 'single':
471
- return ['!bg-currentRelated dx-grid__cell--no-focus-unfurl hover:bg-hoverSurface !cursor-pointer'];
475
+ return ['dx-grid__cell--no-focus-unfurl bg-current-surface! hover:bg-hover-surface cursor-pointer!'];
472
476
  case 'multiple':
473
- return ['!bg-gridCellSelected'];
477
+ return ['bg-grid-cell-selected!'];
474
478
  }
475
479
  };
@@ -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: 'save button' }).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,12 +168,41 @@ 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
 
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
+
162
206
  // TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
163
207
  test.skip('extant relations work as expected', async ({ browser, browserName }) => {
164
208
  test.skip(browserName === 'webkit');
@@ -266,7 +310,7 @@ test.describe('Table', () => {
266
310
  dxGridElement.scrollToColumn(6);
267
311
  });
268
312
 
269
- const nonRefContent = faker.lorem.words(3);
313
+ const nonRefContent = random.lorem.words(3);
270
314
  const nonRefCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="6"]');
271
315
  await nonRefCell.click();
272
316
  await page.keyboard.press('Enter');