@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
@@ -4,16 +4,20 @@
4
4
 
5
5
  import { expect, test } from '@playwright/test';
6
6
 
7
+ import { type DxGrid } from '@dxos/lit-grid';
8
+ import { random } from '@dxos/random';
7
9
  import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
8
10
 
9
11
  import { TableManager } from './TableManager';
10
12
 
11
13
  const storyUrl = storybookUrl('ui-react-ui-table-table--default', 9004);
14
+ const relationsStoryUrl = storybookUrl('ui-react-ui-table-relations--default', 9004);
12
15
 
13
- // 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.
14
17
  test.describe('Table', () => {
15
18
  test('Loads', async ({ browser, browserName }) => {
16
19
  test.skip(browserName === 'webkit');
20
+ test.skip(browserName === 'firefox');
17
21
  const { page } = await setupPage(browser, { url: storyUrl });
18
22
  const table = new TableManager(page);
19
23
 
@@ -23,22 +27,24 @@ test.describe('Table', () => {
23
27
 
24
28
  test('sort', async ({ browser, browserName }) => {
25
29
  test.skip(browserName === 'webkit');
30
+ test.skip(browserName === 'firefox');
26
31
  const { page } = await setupPage(browser, { url: storyUrl });
27
32
  const table = new TableManager(page);
28
33
 
29
34
  await table.grid.ready();
30
35
  await table.sortColumn(0, 'descending');
31
- await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Uwe Øvergård');
32
- await expect(table.grid.cell(0, 9, 'grid')).toContainText('Anita');
36
+ await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Sit.');
37
+ await expect(table.grid.cell(0, 9, 'grid')).toContainText('Aut.');
33
38
 
34
39
  await table.sortColumn(0, 'ascending');
35
- await expect(table.grid.cell(0, 0, 'grid')).toContainText('Anita');
36
- await expect(table.grid.cell(0, 9, 'grid')).toHaveText('Uwe Øvergård');
40
+ await expect(table.grid.cell(0, 0, 'grid')).toContainText('Aut.');
41
+ await expect(table.grid.cell(0, 9, 'grid')).toHaveText('Sit.');
37
42
  await page.close();
38
43
  });
39
44
 
40
45
  test('selection', async ({ browser, browserName }) => {
41
46
  test.skip(browserName === 'webkit');
47
+ test.skip(browserName === 'firefox');
42
48
  const { page } = await setupPage(browser, { url: storyUrl });
43
49
  const table = new TableManager(page);
44
50
 
@@ -60,17 +66,20 @@ test.describe('Table', () => {
60
66
 
61
67
  test('delete row', async ({ browser, browserName }) => {
62
68
  test.skip(browserName === 'webkit');
69
+ test.skip(browserName === 'firefox');
63
70
  const { page } = await setupPage(browser, { url: storyUrl });
64
71
  const table = new TableManager(page);
65
72
 
66
73
  await table.grid.ready();
74
+ await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(1);
67
75
  await table.deleteRow(0);
68
- await expect(page.getByRole('gridcell', { name: 'Anita Mayer' })).toHaveCount(0);
76
+ await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
69
77
  await page.close();
70
78
  });
71
79
 
72
80
  test('delete row--select all', async ({ browser, browserName }) => {
73
81
  test.skip(browserName === 'webkit');
82
+ test.skip(browserName === 'firefox');
74
83
  const { page } = await setupPage(browser, { url: storyUrl });
75
84
  const table = new TableManager(page);
76
85
 
@@ -80,14 +89,15 @@ test.describe('Table', () => {
80
89
  // Delete action affects all selected rows.
81
90
  await table.deleteRow(0);
82
91
 
83
- await expect(page.getByRole('gridcell', { name: 'Anita Mayer' })).toHaveCount(0);
84
- await expect(page.getByRole('gridcell', { name: 'Phonthip Sigurjónsson' })).toHaveCount(0);
92
+ await expect(page.getByRole('gridcell', { name: 'Aut.' })).toHaveCount(0);
93
+ await expect(page.getByRole('gridcell', { name: 'Beatae.' })).toHaveCount(0);
85
94
  await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(0);
86
95
  await page.close();
87
96
  });
88
97
 
89
98
  test('delete column', async ({ browser, browserName }) => {
90
99
  test.skip(browserName === 'webkit');
100
+ test.skip(browserName === 'firefox');
91
101
  const { page } = await setupPage(browser, { url: storyUrl });
92
102
  const table = new TableManager(page);
93
103
 
@@ -111,60 +121,39 @@ test.describe('Table', () => {
111
121
  await page.close();
112
122
  });
113
123
 
114
- // Rest of add column test remains the same as it's a more complex flow.
115
- // TODO(ZaymonFC): Restore this after fixing format selection.
116
- test('add column', async ({ browser, browserName }) => {
124
+ test('add column without changing format', async ({ browser, browserName }) => {
117
125
  test.skip(browserName === 'webkit');
126
+ test.skip(browserName === 'firefox');
118
127
  const { page } = await setupPage(browser, { url: storyUrl });
119
128
  const table = new TableManager(page);
120
129
 
121
130
  await table.grid.ready();
122
- const newColumnLabel = 'TEST LABEL';
131
+ const newColumnLabel = 'DEFAULT FORMAT COL';
123
132
 
124
- await table.addColumn({ label: newColumnLabel, format: 'number' });
133
+ await table.addColumn({ label: newColumnLabel });
125
134
 
126
135
  await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
127
136
  await page.close();
128
137
  });
129
138
 
130
- test('reference > reference / create new object', async ({ browser, browserName }) => {
139
+ test('add column with format', async ({ browser, browserName }) => {
131
140
  test.skip(browserName === 'webkit');
141
+ test.skip(browserName === 'firefox');
132
142
  const { page } = await setupPage(browser, { url: storyUrl });
133
143
  const table = new TableManager(page);
134
144
 
135
145
  await table.grid.ready();
146
+ const newColumnLabel = 'TEST LABEL';
136
147
 
137
- // Reference existing object.
138
- // Scroll over to the manager column.
139
- await table.grid.panByWheel(10000, 0);
140
-
141
- await table.grid.cell(4, 0, 'grid').click();
142
- // TODO(wittjosiah): Surprisingly long delay needed here.
143
- await page.keyboard.press('A', { delay: 1_000 });
144
- await page.keyboard.press('n', { delay: 1_000 });
145
- await page.getByRole('option', { name: 'Anita Mayer' }).click();
146
-
147
- // Assert that the value is shown in the cell.
148
- await expect(page.getByRole('gridcell', { name: 'Anita Mayer' }).first()).toBeVisible();
149
-
150
- // Create new object.
151
- await table.grid.cell(4, 1, 'grid').click();
152
- // TODO(wittjosiah): Surprisingly long delay needed here.
153
- await page.keyboard.type('t', { delay: 1_000 });
154
- await page.keyboard.type('e', { delay: 1_000 });
155
- await page.keyboard.type('s', { delay: 1_000 });
156
- await page.keyboard.type('t', { delay: 1_000 });
157
- await page.getByText('Create new object', { exact: false }).click();
158
- await page.getByTestId('save-button').click();
159
-
160
- // Scroll to the left.
161
- await page.getByRole('gridcell', { name: 'test' }).first().click();
148
+ await table.addColumn({ label: newColumnLabel, format: 'Number' });
162
149
 
150
+ await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
163
151
  await page.close();
164
152
  });
165
153
 
166
- test('test toggles', async ({ browser, browserName }) => {
154
+ test.skip('test toggles', async ({ browser, browserName }) => {
167
155
  test.skip(browserName === 'webkit');
156
+ test.skip(browserName === 'firefox');
168
157
  const { page } = await setupPage(browser, { url: storyUrl });
169
158
  const table = new TableManager(page);
170
159
 
@@ -174,13 +163,161 @@ test.describe('Table', () => {
174
163
  await page.getByTestId('table-switch').nth(7).click();
175
164
 
176
165
  // Test that checks are durable in the data model by sorting.
177
- await table.sortColumn(3, 'descending');
166
+ await table.sortColumn(1, 'descending');
178
167
 
179
168
  // Assert the first two switch checkboxes are checked.
180
169
  await expect(page.getByTestId('table-switch').first()).toBeChecked();
181
170
  await expect(page.getByTestId('table-switch').nth(1)).toBeChecked();
182
- await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Anita Mayer');
183
- await expect(table.grid.cell(0, 1, 'grid')).toHaveText('Uwe Øvergård');
171
+ await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Aut.');
172
+ await expect(table.grid.cell(0, 1, 'grid')).toHaveText('Beatae.');
173
+
174
+ await page.close();
175
+ });
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
+
206
+ // TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
207
+ test.skip('extant relations work as expected', async ({ browser, browserName }) => {
208
+ test.skip(browserName === 'webkit');
209
+ test.skip(browserName === 'firefox');
210
+ const { page } = await setupPage(browser, { url: relationsStoryUrl });
211
+
212
+ // Wait for the page to load
213
+ await page.locator('dx-grid > .dx-grid').nth(1).waitFor({ state: 'visible' });
214
+
215
+ // Find the dx-grid element for the contactModel (second table)
216
+ // The contactModel is used in the second Table.Main component in the story
217
+ const dxGrid = page.locator('dx-grid').nth(1);
218
+ await dxGrid.waitFor({ state: 'visible' });
219
+
220
+ // Scroll to the last column (column 4)
221
+ await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
222
+ dxGridElement.scrollToColumn(4);
223
+ });
224
+
225
+ // Click on the cell at aria-rowindex=0 aria-colindex=4 to focus it
226
+ const targetCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="4"]');
227
+ await targetCell.click();
228
+
229
+ // Click again to engage edit mode
230
+ await page.keyboard.press('Enter');
231
+
232
+ // Click on the combobox to open options
233
+ await page.getByRole('combobox').click();
234
+ await page.pause();
235
+ await page.getByPlaceholder('Search…').focus();
236
+
237
+ // Type the first few letters of an org name.
238
+ const orgName =
239
+ (await page
240
+ .locator('dx-grid')
241
+ .nth(0)
242
+ .locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="0"] .dx-grid__cell__content')
243
+ .textContent()) ?? 'never';
244
+ await page.keyboard.type(orgName.substring(0, 4), { delay: 500 });
245
+
246
+ // Assert that there is an element with aria-selected on the page
247
+ await expect(page.locator('[role="option"][aria-selected]')).toBeVisible();
248
+
249
+ // Type the enter key
250
+ await page.keyboard.press('Enter');
251
+
252
+ // Save the result
253
+ await page.getByTestId('save-button').click();
254
+
255
+ // Assert that the cell element has the org name
256
+ await expect(targetCell).toHaveText(orgName);
257
+
258
+ await page.close();
259
+ });
260
+
261
+ // TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
262
+ test.skip('new relations work as expected', async ({ browser, browserName }) => {
263
+ test.skip(browserName === 'webkit');
264
+ test.skip(browserName === 'firefox');
265
+ const { page } = await setupPage(browser, { url: relationsStoryUrl });
266
+
267
+ // Wait for the page to load
268
+ await page.locator('dx-grid > .dx-grid').nth(1).waitFor({ state: 'visible' });
269
+
270
+ // Find the dx-grid element for the contactModel (second table)
271
+ // The contactModel is used in the second Table.Main component in the story
272
+ const dxGrid = page.locator('dx-grid').nth(1);
273
+ await dxGrid.waitFor({ state: 'visible' });
274
+
275
+ // Scroll to the last column (column 4)
276
+ await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
277
+ dxGridElement.scrollToColumn(4);
278
+ });
279
+
280
+ // Click on the cell at aria-rowindex=0 aria-colindex=8 to focus it
281
+ const targetCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="4"]');
282
+ await targetCell.click();
283
+
284
+ // Click again to engage edit mode
285
+ await page.keyboard.press('Enter');
286
+
287
+ // Click on the combobox to open options
288
+ await page.getByRole('combobox').click();
289
+ await page.getByPlaceholder('Search…').focus();
290
+
291
+ // Type the first few letters of an org name.
292
+ const orgName = 'Sally';
293
+ await page.keyboard.type(orgName, { delay: 500 });
294
+
295
+ // Assert that there is an element with aria-selected on the page
296
+ await expect(page.locator('[role="option"][aria-selected]')).toBeVisible();
297
+
298
+ // Type the enter key
299
+ await page.keyboard.press('Enter');
300
+ await page.pause();
301
+
302
+ // Click the save button in the popover
303
+ await page.getByTestId('create-referenced-object-form').getByTestId('save-button').click();
304
+
305
+ // Assert that the cell element has the org name
306
+ await expect(targetCell).toHaveText(orgName);
307
+
308
+ // Make a change to a non-ref cell to check that populated refs don’t cause problems with snapshots or schemas.
309
+ await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
310
+ dxGridElement.scrollToColumn(6);
311
+ });
312
+
313
+ const nonRefContent = random.lorem.words(3);
314
+ const nonRefCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="6"]');
315
+ await nonRefCell.click();
316
+ await page.keyboard.press('Enter');
317
+ await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
318
+ await page.keyboard.type(nonRefContent, { delay: 500 });
319
+ await page.keyboard.press('Enter');
320
+ await expect(nonRefCell).toHaveText(nonRefContent);
184
321
 
185
322
  await page.close();
186
323
  });
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './useTestTableModel';
@@ -0,0 +1,133 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import { type RefObject, useCallback, useContext, useMemo, useRef } from 'react';
7
+
8
+ import { type Database, type Type } from '@dxos/echo';
9
+ import { isMutable } from '@dxos/echo/internal';
10
+ import { random } from '@dxos/random';
11
+ import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
12
+ import { useClientStory } from '@dxos/react-client/testing';
13
+ import { useGlobalFilteredObjects } from '@dxos/react-ui-search';
14
+ import { type ProjectionModel, getTypenameFromQuery } from '@dxos/schema';
15
+
16
+ import { type TableController } from '../components';
17
+ import { useAddRow, useProjectionModel, useTableModel } from '../hooks';
18
+ import { type TableModel, TablePresentation } from '../model';
19
+ import { Table } from '../types';
20
+
21
+ random.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
22
+
23
+ export type TestTableModel<T extends Type.AnyObj = Type.AnyObj> = {
24
+ schema: T | undefined;
25
+ table: Table.Table | undefined;
26
+ projection: ProjectionModel | undefined;
27
+ tableRef: RefObject<TableController | null>;
28
+ model: TableModel | undefined;
29
+ presentation: TablePresentation | undefined;
30
+ db: Database.Database | undefined;
31
+ handleInsertRow: () => void;
32
+ handleSaveView: () => void;
33
+ handleDeleteRows: (rowIndex: number, objects: any[]) => void;
34
+ handleDeleteColumn: (fieldId: string) => void;
35
+ };
36
+
37
+ /**
38
+ * Custom hook to create and manage a test table model for storybook demonstrations.
39
+ * Provides table data, schema, and handlers for table operations.
40
+ */
41
+ export const useTestTableModel = <T extends Type.AnyObj = Type.AnyObj>(): TestTableModel<T> => {
42
+ const registry = useContext(RegistryContext);
43
+ const { space } = useClientStory();
44
+ const db = space?.db;
45
+
46
+ const tables = useQuery(space?.db, Filter.type(Table.Table));
47
+ const table = tables.at(0);
48
+ const typename = table?.view.target?.query ? getTypenameFromQuery(table.view.target.query.ast) : undefined;
49
+ const schema = useSchema<T>(space?.db, typename);
50
+ const projection = useProjectionModel(schema, table, registry);
51
+
52
+ const features = useMemo(
53
+ () => ({
54
+ selection: { enabled: true, mode: 'multiple' as const },
55
+ dataEditable: true,
56
+ schemaEditable: schema && isMutable(schema),
57
+ }),
58
+ [schema],
59
+ );
60
+
61
+ const objects = useQuery(db, schema ? Filter.type(schema) : Filter.nothing());
62
+ const filteredObjects = useGlobalFilteredObjects(objects);
63
+
64
+ const tableRef = useRef<TableController>(null);
65
+ const handleCellUpdate = useCallback((cell: any) => {
66
+ tableRef.current?.update?.(cell);
67
+ }, []);
68
+
69
+ const handleRowOrderChange = useCallback(() => {
70
+ tableRef.current?.update?.();
71
+ }, []);
72
+
73
+ const addRow = useAddRow({ db, schema });
74
+
75
+ const handleDeleteRows = useCallback(
76
+ (_: number, objects: any[]) => {
77
+ for (const object of objects) {
78
+ db?.remove(object);
79
+ }
80
+ },
81
+ [db],
82
+ );
83
+
84
+ const handleDeleteColumn = useCallback(
85
+ (fieldId: string) => {
86
+ if (projection) {
87
+ projection.deleteFieldProjection(fieldId);
88
+ }
89
+ },
90
+ [projection],
91
+ );
92
+
93
+ const model = useTableModel({
94
+ object: table,
95
+ projection,
96
+ features,
97
+ rows: filteredObjects,
98
+ onInsertRow: addRow,
99
+ onDeleteRows: handleDeleteRows,
100
+ onColumnDelete: handleDeleteColumn,
101
+ onCellUpdate: handleCellUpdate,
102
+ onRowOrderChange: handleRowOrderChange,
103
+ });
104
+
105
+ const handleInsertRow = useCallback(() => {
106
+ const insertResult = model?.insertRow();
107
+ tableRef.current?.handleInsertRowResult?.(insertResult);
108
+ }, [model]);
109
+
110
+ const handleSaveView = useCallback(() => {
111
+ model?.saveView();
112
+ }, [model]);
113
+
114
+ const presentation = useMemo(() => {
115
+ if (model) {
116
+ return new TablePresentation(registry, model);
117
+ }
118
+ }, [registry, model]);
119
+
120
+ return {
121
+ schema,
122
+ table,
123
+ projection,
124
+ tableRef,
125
+ model,
126
+ presentation,
127
+ db,
128
+ handleInsertRow,
129
+ handleSaveView,
130
+ handleDeleteRows,
131
+ handleDeleteColumn,
132
+ };
133
+ };
@@ -4,33 +4,31 @@
4
4
 
5
5
  import { type Resource } from '@dxos/react-ui';
6
6
 
7
- export const translationKey = 'react-ui-table';
7
+ export const translationKey = '@dxos/react-ui-table';
8
8
 
9
9
  export const translations = [
10
10
  {
11
11
  'en-US': {
12
12
  [translationKey]: {
13
- 'object placeholder': 'New table',
14
- 'create object label': 'Create table',
15
- 'table name placeholder': 'Table name',
16
- 'settings title': 'Table settings',
17
- 'table schema label': 'Schema',
18
- 'new schema': 'New schema',
19
- 'continue label': 'Continue',
20
- 'create stack section label': 'Create table',
21
- 'add row label': 'Add row',
22
- 'create comment': 'Create thread',
23
- 'column action sort descending': 'Sort Descending',
24
- 'column action sort ascending': 'Sort Ascending',
25
- 'column action clear sorting': 'Clear Sorting',
26
- 'column action settings': 'Column Settings',
27
- 'column action delete': 'Delete Column',
28
- 'delete row label': 'Delete row',
29
- 'bulk delete row label': 'Delete selected rows',
30
- 'column deleted label': 'Column deleted',
31
- 'new column button label': 'Create column',
32
- 'create new object label': 'Create new object “{{text}}”',
33
- 'save view label': 'Save changes to view',
13
+ 'object.placeholder': 'New table',
14
+ 'create-object.label': 'Create table',
15
+ 'table-name.placeholder': 'Table name',
16
+ 'settings.title': 'Table settings',
17
+ 'table-schema.label': 'Type',
18
+ 'continue.label': 'Continue',
19
+ 'create-stack-section.label': 'Create table',
20
+ 'add-row.label': 'Add row',
21
+ 'column-action-sort-descending.menu': 'Sort Descending',
22
+ 'column-action-sort-ascending.menu': 'Sort Ascending',
23
+ 'column-action-clear-sorting.menu': 'Clear Sorting',
24
+ 'column-action-settings.menu': 'Column Settings',
25
+ 'column-action-delete.menu': 'Delete Column',
26
+ 'delete-row.label': 'Delete row',
27
+ 'bulk-delete-row.label': 'Delete selected rows',
28
+ 'column-deleted.label': 'Column deleted',
29
+ 'new-column-button.label': 'Create column',
30
+ 'create-new-object.label': 'Create new object “{{text}}”',
31
+ 'save-view.label': 'Save changes to view',
34
32
  },
35
33
  },
36
34
  },
@@ -0,0 +1,79 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Match from 'effect/Match';
6
+ import * as Schema from 'effect/Schema';
7
+ import * as SchemaAST from 'effect/SchemaAST';
8
+
9
+ import { Annotation, JsonSchema, Obj, Ref, Type } from '@dxos/echo';
10
+ import { View } from '@dxos/echo';
11
+ import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
12
+ import { ViewAnnotation } from '@dxos/schema';
13
+
14
+ export const Table = Schema.Struct({
15
+ name: Schema.String.pipe(Schema.optional),
16
+
17
+ view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false)),
18
+
19
+ sizes: Schema.Record({
20
+ // TODO(wittjosiah): Should be JsonPath.
21
+ key: Schema.String,
22
+ value: Schema.Number,
23
+ }).pipe(Schema.mutable, FormInputAnnotation.set(false)),
24
+ }).pipe(
25
+ Type.object({
26
+ typename: 'org.dxos.type.table',
27
+ version: '0.1.0',
28
+ }),
29
+ LabelAnnotation.set(['name']),
30
+ ViewAnnotation.set(['view']),
31
+ Annotation.IconAnnotation.set({
32
+ icon: 'ph--table--regular',
33
+ hue: 'green',
34
+ }),
35
+ );
36
+
37
+ export interface Table extends Schema.Schema.Type<typeof Table> {}
38
+
39
+ type MakeProps = {
40
+ name?: string;
41
+ sizes?: Record<string, number>;
42
+ view: View.View;
43
+ /** Required to auto-size columns. */
44
+ jsonSchema?: JsonSchemaType;
45
+ };
46
+
47
+ /**
48
+ * Make a table as a view of a data set.
49
+ */
50
+ export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table => {
51
+ const table = Obj.make(Table, { name, view: Ref.make(view), sizes } as Obj.MakeProps<typeof Table>);
52
+
53
+ // Preset sizes.
54
+ if (jsonSchema) {
55
+ const schema = JsonSchema.toEffectSchema(jsonSchema);
56
+ const properties = SchemaAST.getPropertySignatures(schema.ast);
57
+ for (const property of properties) {
58
+ const name = property.name.toString() as JsonPath;
59
+ if (sizes?.[name]) {
60
+ table.sizes[name] = sizes[name];
61
+ continue;
62
+ }
63
+
64
+ Match.type<SchemaAST.AST>().pipe(
65
+ Match.when({ _tag: 'BooleanKeyword' }, () => {
66
+ table.sizes[name] = 100;
67
+ }),
68
+ Match.when({ _tag: 'NumberKeyword' }, () => {
69
+ table.sizes[name] = 100;
70
+ }),
71
+ Match.orElse(() => {
72
+ // Noop.
73
+ }),
74
+ )(property.type);
75
+ }
76
+ }
77
+
78
+ return table;
79
+ };
@@ -1,5 +1,5 @@
1
1
  //
2
- // Copyright 2024 DXOS.org
2
+ // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './table';
5
+ export * as Table from './Table';