@dxos/react-ui-table 0.8.4-main.67995b8 → 0.8.4-main.69d29f4
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.
- package/dist/lib/browser/chunk-I5IFW7UF.mjs +1746 -0
- package/dist/lib/browser/chunk-I5IFW7UF.mjs.map +7 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs +76 -0
- package/dist/lib/browser/chunk-OZBLY7XM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1163 -1120
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +30 -37
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +3 -3
- package/dist/lib/node-esm/chunk-DDJKYJFY.mjs +1748 -0
- package/dist/lib/node-esm/chunk-DDJKYJFY.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs +77 -0
- package/dist/lib/node-esm/chunk-WNZSKIRF.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1163 -1120
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +30 -37
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +2 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -3
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +5 -6
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +37 -3
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +111 -0
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +13 -9
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +108 -4
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +13 -9
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +11 -18
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
- package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +4 -4
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +23 -16
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +6 -6
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +9 -9
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +5 -8
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +17 -8
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +109 -28
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +5 -2
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +1 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +18 -34
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +4 -4
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +31 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +13 -12
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +2 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts +7 -0
- package/dist/types/src/util/query-ast.d.ts.map +1 -0
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/src/util/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +65 -64
- package/src/components/Table/ColumnActionsMenu.tsx +11 -7
- package/src/components/Table/ColumnSettings.tsx +7 -7
- package/src/components/Table/DynamicTable.stories.tsx +25 -22
- package/src/components/Table/DynamicTable.tsx +26 -21
- package/src/components/Table/Relations.stories.tsx +237 -0
- package/src/components/Table/RowActionsMenu.tsx +26 -17
- package/src/components/Table/Table.stories.tsx +110 -102
- package/src/components/Table/Table.tsx +353 -298
- package/src/components/TableCellEditor/CellValidationMessage.tsx +2 -2
- package/src/components/TableCellEditor/FormCellEditor.tsx +128 -40
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +30 -66
- package/src/components/TableCellEditor/TableCellEditor.tsx +88 -238
- package/src/components/TableCellEditor/index.ts +1 -0
- package/src/components/TableToolbar/TableToolbar.tsx +49 -33
- package/src/components/testing.ts +39 -13
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +14 -14
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +35 -33
- package/src/model/modal-controller.ts +24 -41
- package/src/model/selection-model.ts +75 -43
- package/src/model/table-model.test.ts +92 -55
- package/src/model/table-model.ts +445 -168
- package/src/model/table-presentation.test.ts +70 -58
- package/src/model/table-presentation.ts +103 -57
- package/src/playwright/TableManager.ts +5 -5
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +135 -40
- package/src/testing/useTestTableModel.ts +45 -34
- package/src/translations.ts +3 -3
- package/src/types/Table.ts +90 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.ts +44 -32
- package/src/util/index.ts +2 -1
- package/src/util/query-ast.ts +19 -0
- package/src/util/schema.ts +4 -4
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +4 -3
- package/src/util/table-controls.ts +4 -1
- package/dist/lib/browser/chunk-5XLSHLOG.mjs +0 -1751
- package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
- package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs +0 -1752
- package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +0 -7
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
- package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
- package/dist/types/src/model/table-sorting.d.ts +0 -53
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -8
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/dist/types/src/util/table.d.ts +0 -11
- package/dist/types/src/util/table.d.ts.map +0 -1
- package/src/components/Table/CreateRefPanel.tsx +0 -86
- package/src/components/Table/relations.stories.tsx +0 -116
- package/src/components/TableCellEditor/extension.ts +0 -61
- package/src/model/table-sorting.ts +0 -216
- package/src/types/schema.ts +0 -16
- package/src/util/table.ts +0 -49
|
@@ -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 { faker } 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
16
|
// NOTE(ZaymonFC): This test suite relies on the faker 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,6 +27,7 @@ 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
|
|
|
@@ -39,6 +44,7 @@ test.describe('Table', () => {
|
|
|
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,10 +66,12 @@ 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
76
|
await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
|
|
69
77
|
await page.close();
|
|
@@ -71,6 +79,7 @@ test.describe('Table', () => {
|
|
|
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,7 +89,7 @@ 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: '
|
|
92
|
+
await expect(page.getByRole('gridcell', { name: 'Aut.' })).toHaveCount(0);
|
|
84
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();
|
|
@@ -88,6 +97,7 @@ test.describe('Table', () => {
|
|
|
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
|
|
|
@@ -114,56 +124,22 @@ test.describe('Table', () => {
|
|
|
114
124
|
// Rest of add column test remains the same as it's a more complex flow.
|
|
115
125
|
test('add column', async ({ browser, browserName }) => {
|
|
116
126
|
test.skip(browserName === 'webkit');
|
|
127
|
+
test.skip(browserName === 'firefox');
|
|
117
128
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
118
129
|
const table = new TableManager(page);
|
|
119
130
|
|
|
120
131
|
await table.grid.ready();
|
|
121
132
|
const newColumnLabel = 'TEST LABEL';
|
|
122
133
|
|
|
123
|
-
await table.addColumn({ label: newColumnLabel, format: '
|
|
134
|
+
await table.addColumn({ label: newColumnLabel, format: 'Number' });
|
|
124
135
|
|
|
125
136
|
await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
|
|
126
137
|
await page.close();
|
|
127
138
|
});
|
|
128
139
|
|
|
129
|
-
test('
|
|
130
|
-
test.skip(browserName === 'webkit');
|
|
131
|
-
const { page } = await setupPage(browser, { url: storyUrl });
|
|
132
|
-
const table = new TableManager(page);
|
|
133
|
-
|
|
134
|
-
await table.grid.ready();
|
|
135
|
-
|
|
136
|
-
// Reference existing object.
|
|
137
|
-
// Scroll over to the manager column.
|
|
138
|
-
await table.grid.panByWheel(10000, 0);
|
|
139
|
-
|
|
140
|
-
await table.grid.cell(4, 0, 'grid').click();
|
|
141
|
-
// TODO(wittjosiah): Surprisingly long delay needed here.
|
|
142
|
-
await page.keyboard.press('A', { delay: 1_000 });
|
|
143
|
-
await page.keyboard.press('u', { delay: 1_000 });
|
|
144
|
-
await page.getByRole('option', { name: 'Aut.' }).click();
|
|
145
|
-
|
|
146
|
-
// Assert that the value is shown in the cell.
|
|
147
|
-
await expect(page.getByRole('gridcell', { name: 'Aut.' }).first()).toBeVisible();
|
|
148
|
-
|
|
149
|
-
// Create new object.
|
|
150
|
-
await table.grid.cell(4, 1, 'grid').click();
|
|
151
|
-
// TODO(wittjosiah): Surprisingly long delay needed here.
|
|
152
|
-
await page.keyboard.type('t', { delay: 1_000 });
|
|
153
|
-
await page.keyboard.type('e', { delay: 1_000 });
|
|
154
|
-
await page.keyboard.type('s', { delay: 1_000 });
|
|
155
|
-
await page.keyboard.type('t', { delay: 1_000 });
|
|
156
|
-
await page.getByText('Create new object', { exact: false }).click();
|
|
157
|
-
await page.getByTestId('save-button').click();
|
|
158
|
-
|
|
159
|
-
// Scroll to the left.
|
|
160
|
-
await page.getByRole('gridcell', { name: 'test' }).first().click();
|
|
161
|
-
|
|
162
|
-
await page.close();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('test toggles', async ({ browser, browserName }) => {
|
|
140
|
+
test.skip('test toggles', async ({ browser, browserName }) => {
|
|
166
141
|
test.skip(browserName === 'webkit');
|
|
142
|
+
test.skip(browserName === 'firefox');
|
|
167
143
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
168
144
|
const table = new TableManager(page);
|
|
169
145
|
|
|
@@ -178,9 +154,128 @@ test.describe('Table', () => {
|
|
|
178
154
|
// Assert the first two switch checkboxes are checked.
|
|
179
155
|
await expect(page.getByTestId('table-switch').first()).toBeChecked();
|
|
180
156
|
await expect(page.getByTestId('table-switch').nth(1)).toBeChecked();
|
|
181
|
-
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('
|
|
157
|
+
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Aut.');
|
|
182
158
|
await expect(table.grid.cell(0, 1, 'grid')).toHaveText('Beatae.');
|
|
183
159
|
|
|
184
160
|
await page.close();
|
|
185
161
|
});
|
|
162
|
+
|
|
163
|
+
// TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
|
|
164
|
+
test.skip('extant relations work as expected', async ({ browser, browserName }) => {
|
|
165
|
+
test.skip(browserName === 'webkit');
|
|
166
|
+
test.skip(browserName === 'firefox');
|
|
167
|
+
const { page } = await setupPage(browser, { url: relationsStoryUrl });
|
|
168
|
+
|
|
169
|
+
// Wait for the page to load
|
|
170
|
+
await page.locator('dx-grid > .dx-grid').nth(1).waitFor({ state: 'visible' });
|
|
171
|
+
|
|
172
|
+
// Find the dx-grid element for the contactModel (second table)
|
|
173
|
+
// The contactModel is used in the second Table.Main component in the story
|
|
174
|
+
const dxGrid = page.locator('dx-grid').nth(1);
|
|
175
|
+
await dxGrid.waitFor({ state: 'visible' });
|
|
176
|
+
|
|
177
|
+
// Scroll to the last column (column 4)
|
|
178
|
+
await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
|
|
179
|
+
dxGridElement.scrollToColumn(4);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Click on the cell at aria-rowindex=0 aria-colindex=4 to focus it
|
|
183
|
+
const targetCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="4"]');
|
|
184
|
+
await targetCell.click();
|
|
185
|
+
|
|
186
|
+
// Click again to engage edit mode
|
|
187
|
+
await page.keyboard.press('Enter');
|
|
188
|
+
|
|
189
|
+
// Click on the combobox to open options
|
|
190
|
+
await page.getByRole('combobox').click();
|
|
191
|
+
await page.pause();
|
|
192
|
+
await page.getByPlaceholder('Search…').focus();
|
|
193
|
+
|
|
194
|
+
// Type the first few letters of an org name.
|
|
195
|
+
const orgName =
|
|
196
|
+
(await page
|
|
197
|
+
.locator('dx-grid')
|
|
198
|
+
.nth(0)
|
|
199
|
+
.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="0"] .dx-grid__cell__content')
|
|
200
|
+
.textContent()) ?? 'never';
|
|
201
|
+
await page.keyboard.type(orgName.substring(0, 4), { delay: 500 });
|
|
202
|
+
|
|
203
|
+
// Assert that there is an element with aria-selected on the page
|
|
204
|
+
await expect(page.locator('[role="option"][aria-selected]')).toBeVisible();
|
|
205
|
+
|
|
206
|
+
// Type the enter key
|
|
207
|
+
await page.keyboard.press('Enter');
|
|
208
|
+
|
|
209
|
+
// Save the result
|
|
210
|
+
await page.getByTestId('save-button').click();
|
|
211
|
+
|
|
212
|
+
// Assert that the cell element has the org name
|
|
213
|
+
await expect(targetCell).toHaveText(orgName);
|
|
214
|
+
|
|
215
|
+
await page.close();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// TODO(wittjosiah): Remove? Conflicts with story play function which is run as a unit test anyways.
|
|
219
|
+
test.skip('new relations work as expected', async ({ browser, browserName }) => {
|
|
220
|
+
test.skip(browserName === 'webkit');
|
|
221
|
+
test.skip(browserName === 'firefox');
|
|
222
|
+
const { page } = await setupPage(browser, { url: relationsStoryUrl });
|
|
223
|
+
|
|
224
|
+
// Wait for the page to load
|
|
225
|
+
await page.locator('dx-grid > .dx-grid').nth(1).waitFor({ state: 'visible' });
|
|
226
|
+
|
|
227
|
+
// Find the dx-grid element for the contactModel (second table)
|
|
228
|
+
// The contactModel is used in the second Table.Main component in the story
|
|
229
|
+
const dxGrid = page.locator('dx-grid').nth(1);
|
|
230
|
+
await dxGrid.waitFor({ state: 'visible' });
|
|
231
|
+
|
|
232
|
+
// Scroll to the last column (column 4)
|
|
233
|
+
await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
|
|
234
|
+
dxGridElement.scrollToColumn(4);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Click on the cell at aria-rowindex=0 aria-colindex=8 to focus it
|
|
238
|
+
const targetCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="4"]');
|
|
239
|
+
await targetCell.click();
|
|
240
|
+
|
|
241
|
+
// Click again to engage edit mode
|
|
242
|
+
await page.keyboard.press('Enter');
|
|
243
|
+
|
|
244
|
+
// Click on the combobox to open options
|
|
245
|
+
await page.getByRole('combobox').click();
|
|
246
|
+
await page.getByPlaceholder('Search…').focus();
|
|
247
|
+
|
|
248
|
+
// Type the first few letters of an org name.
|
|
249
|
+
const orgName = 'Sally';
|
|
250
|
+
await page.keyboard.type(orgName, { delay: 500 });
|
|
251
|
+
|
|
252
|
+
// Assert that there is an element with aria-selected on the page
|
|
253
|
+
await expect(page.locator('[role="option"][aria-selected]')).toBeVisible();
|
|
254
|
+
|
|
255
|
+
// Type the enter key
|
|
256
|
+
await page.keyboard.press('Enter');
|
|
257
|
+
await page.pause();
|
|
258
|
+
|
|
259
|
+
// Click the save button in the popover
|
|
260
|
+
await page.getByTestId('create-referenced-object-form').getByTestId('save-button').click();
|
|
261
|
+
|
|
262
|
+
// Assert that the cell element has the org name
|
|
263
|
+
await expect(targetCell).toHaveText(orgName);
|
|
264
|
+
|
|
265
|
+
// Make a change to a non-ref cell to check that populated refs don’t cause problems with snapshots or schemas.
|
|
266
|
+
await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
|
|
267
|
+
dxGridElement.scrollToColumn(6);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const nonRefContent = faker.lorem.words(3);
|
|
271
|
+
const nonRefCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="6"]');
|
|
272
|
+
await nonRefCell.click();
|
|
273
|
+
await page.keyboard.press('Enter');
|
|
274
|
+
await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
|
|
275
|
+
await page.keyboard.type(nonRefContent, { delay: 500 });
|
|
276
|
+
await page.keyboard.press('Enter');
|
|
277
|
+
await expect(nonRefCell).toHaveText(nonRefContent);
|
|
278
|
+
|
|
279
|
+
await page.close();
|
|
280
|
+
});
|
|
186
281
|
});
|
|
@@ -2,40 +2,52 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import { type RefObject, useCallback, useContext, useMemo, useRef } from 'react';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
8
|
+
import { type Database, type Type } from '@dxos/echo';
|
|
9
|
+
import { isMutable } from '@dxos/echo/internal';
|
|
9
10
|
import { faker } from '@dxos/random';
|
|
10
|
-
import { useClient } from '@dxos/react-client';
|
|
11
11
|
import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { useClientStory } from '@dxos/react-client/testing';
|
|
13
|
+
import { useGlobalFilteredObjects } from '@dxos/react-ui-searchlist';
|
|
14
|
+
import { type ProjectionModel, getTypenameFromQuery } from '@dxos/schema';
|
|
14
15
|
|
|
15
16
|
import { type TableController } from '../components';
|
|
16
|
-
import {
|
|
17
|
-
import { TablePresentation } from '../model';
|
|
17
|
+
import { useAddRow, useProjectionModel, useTableModel } from '../hooks';
|
|
18
|
+
import { type TableModel, TablePresentation } from '../model';
|
|
19
|
+
import { Table } from '../types';
|
|
18
20
|
|
|
19
21
|
faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
|
|
20
22
|
|
|
23
|
+
export type TestTableModel<T extends Type.Obj.Any = Type.Obj.Any> = {
|
|
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
|
+
|
|
21
37
|
/**
|
|
22
38
|
* Custom hook to create and manage a test table model for storybook demonstrations.
|
|
23
39
|
* Provides table data, schema, and handlers for table operations.
|
|
24
40
|
*/
|
|
25
|
-
export const useTestTableModel = () => {
|
|
26
|
-
const
|
|
27
|
-
const { space } =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const projection =
|
|
35
|
-
if (schema && view?.projection) {
|
|
36
|
-
return new ProjectionModel(toJsonSchema(schema), view.projection);
|
|
37
|
-
}
|
|
38
|
-
}, [schema, view?.projection]);
|
|
41
|
+
export const useTestTableModel = <T extends Type.Obj.Any = Type.Obj.Any>(): 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);
|
|
39
51
|
|
|
40
52
|
const features = useMemo(
|
|
41
53
|
() => ({
|
|
@@ -46,7 +58,7 @@ export const useTestTableModel = () => {
|
|
|
46
58
|
[schema],
|
|
47
59
|
);
|
|
48
60
|
|
|
49
|
-
const objects = useQuery(
|
|
61
|
+
const objects = useQuery(db, schema ? Filter.type(schema) : Filter.nothing());
|
|
50
62
|
const filteredObjects = useGlobalFilteredObjects(objects);
|
|
51
63
|
|
|
52
64
|
const tableRef = useRef<TableController>(null);
|
|
@@ -58,15 +70,15 @@ export const useTestTableModel = () => {
|
|
|
58
70
|
tableRef.current?.update?.();
|
|
59
71
|
}, []);
|
|
60
72
|
|
|
61
|
-
const addRow = useAddRow({
|
|
73
|
+
const addRow = useAddRow({ db, schema });
|
|
62
74
|
|
|
63
75
|
const handleDeleteRows = useCallback(
|
|
64
76
|
(_: number, objects: any[]) => {
|
|
65
77
|
for (const object of objects) {
|
|
66
|
-
|
|
78
|
+
db?.remove(object);
|
|
67
79
|
}
|
|
68
80
|
},
|
|
69
|
-
[
|
|
81
|
+
[db],
|
|
70
82
|
);
|
|
71
83
|
|
|
72
84
|
const handleDeleteColumn = useCallback(
|
|
@@ -79,8 +91,7 @@ export const useTestTableModel = () => {
|
|
|
79
91
|
);
|
|
80
92
|
|
|
81
93
|
const model = useTableModel({
|
|
82
|
-
|
|
83
|
-
schema: jsonSchema,
|
|
94
|
+
object: table,
|
|
84
95
|
projection,
|
|
85
96
|
features,
|
|
86
97
|
rows: filteredObjects,
|
|
@@ -92,7 +103,8 @@ export const useTestTableModel = () => {
|
|
|
92
103
|
});
|
|
93
104
|
|
|
94
105
|
const handleInsertRow = useCallback(() => {
|
|
95
|
-
model?.insertRow();
|
|
106
|
+
const insertResult = model?.insertRow();
|
|
107
|
+
tableRef.current?.handleInsertRowResult?.(insertResult);
|
|
96
108
|
}, [model]);
|
|
97
109
|
|
|
98
110
|
const handleSaveView = useCallback(() => {
|
|
@@ -101,19 +113,18 @@ export const useTestTableModel = () => {
|
|
|
101
113
|
|
|
102
114
|
const presentation = useMemo(() => {
|
|
103
115
|
if (model) {
|
|
104
|
-
return new TablePresentation(model);
|
|
116
|
+
return new TablePresentation(registry, model);
|
|
105
117
|
}
|
|
106
|
-
}, [model]);
|
|
118
|
+
}, [registry, model]);
|
|
107
119
|
|
|
108
120
|
return {
|
|
109
121
|
schema,
|
|
110
|
-
|
|
122
|
+
table,
|
|
111
123
|
projection,
|
|
112
124
|
tableRef,
|
|
113
125
|
model,
|
|
114
126
|
presentation,
|
|
115
|
-
|
|
116
|
-
client,
|
|
127
|
+
db,
|
|
117
128
|
handleInsertRow,
|
|
118
129
|
handleSaveView,
|
|
119
130
|
handleDeleteRows,
|
package/src/translations.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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
|
{
|
|
@@ -14,8 +14,8 @@ export const translations = [
|
|
|
14
14
|
'create object label': 'Create table',
|
|
15
15
|
'table name placeholder': 'Table name',
|
|
16
16
|
'settings title': 'Table settings',
|
|
17
|
-
'table schema label': '
|
|
18
|
-
'new schema': 'New
|
|
17
|
+
'table schema label': 'Type',
|
|
18
|
+
'new schema': 'New type',
|
|
19
19
|
'continue label': 'Continue',
|
|
20
20
|
'create stack section label': 'Create table',
|
|
21
21
|
'add row label': 'Add row',
|
|
@@ -0,0 +1,90 @@
|
|
|
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 { Obj, Ref, Type } from '@dxos/echo';
|
|
10
|
+
import { FormInputAnnotation, type JsonPath, type JsonSchemaType, LabelAnnotation } from '@dxos/echo/internal';
|
|
11
|
+
import { View, ViewAnnotation } from '@dxos/schema';
|
|
12
|
+
|
|
13
|
+
export const Table = Schema.Struct({
|
|
14
|
+
name: Schema.String.pipe(Schema.optional),
|
|
15
|
+
|
|
16
|
+
view: Type.Ref(View.View).pipe(FormInputAnnotation.set(false)),
|
|
17
|
+
|
|
18
|
+
sizes: Schema.Record({
|
|
19
|
+
// TODO(wittjosiah): Should be JsonPath.
|
|
20
|
+
key: Schema.String,
|
|
21
|
+
value: Schema.Number,
|
|
22
|
+
}).pipe(Schema.mutable, FormInputAnnotation.set(false)),
|
|
23
|
+
}).pipe(
|
|
24
|
+
Type.object({
|
|
25
|
+
typename: 'dxos.org/type/Table',
|
|
26
|
+
version: '0.2.0',
|
|
27
|
+
}),
|
|
28
|
+
LabelAnnotation.set(['name']),
|
|
29
|
+
ViewAnnotation.set(true),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export interface Table extends Schema.Schema.Type<typeof Table> {}
|
|
33
|
+
|
|
34
|
+
type MakeProps = {
|
|
35
|
+
name?: string;
|
|
36
|
+
sizes?: Record<string, number>;
|
|
37
|
+
view: View.View;
|
|
38
|
+
/** Required to auto-size columns. */
|
|
39
|
+
jsonSchema?: JsonSchemaType;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Make a table as a view of a data set.
|
|
44
|
+
*/
|
|
45
|
+
export const make = ({ name, sizes = {}, view, jsonSchema }: MakeProps): Table => {
|
|
46
|
+
const table = Obj.make(Table, { name, view: Ref.make(view), sizes } as Obj.MakeProps<typeof Table>);
|
|
47
|
+
|
|
48
|
+
// Preset sizes.
|
|
49
|
+
if (jsonSchema) {
|
|
50
|
+
const schema = Type.toEffectSchema(jsonSchema);
|
|
51
|
+
const properties = SchemaAST.getPropertySignatures(schema.ast);
|
|
52
|
+
for (const property of properties) {
|
|
53
|
+
const name = property.name.toString() as JsonPath;
|
|
54
|
+
if (sizes?.[name]) {
|
|
55
|
+
table.sizes[name] = sizes[name];
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Match.type<SchemaAST.AST>().pipe(
|
|
60
|
+
Match.when({ _tag: 'BooleanKeyword' }, () => {
|
|
61
|
+
table.sizes[name] = 100;
|
|
62
|
+
}),
|
|
63
|
+
Match.when({ _tag: 'NumberKeyword' }, () => {
|
|
64
|
+
table.sizes[name] = 100;
|
|
65
|
+
}),
|
|
66
|
+
Match.orElse(() => {
|
|
67
|
+
// Noop.
|
|
68
|
+
}),
|
|
69
|
+
)(property.type);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return table;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//
|
|
77
|
+
// V1
|
|
78
|
+
//
|
|
79
|
+
|
|
80
|
+
export const TableV1 = Schema.Struct({
|
|
81
|
+
sizes: Schema.Record({
|
|
82
|
+
key: Schema.String,
|
|
83
|
+
value: Schema.Number,
|
|
84
|
+
}).pipe(Schema.mutable),
|
|
85
|
+
}).pipe(
|
|
86
|
+
Type.object({
|
|
87
|
+
typename: 'dxos.org/type/Table',
|
|
88
|
+
version: '0.1.0',
|
|
89
|
+
}),
|
|
90
|
+
);
|
package/src/types/index.ts
CHANGED
|
@@ -2,26 +2,24 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type
|
|
5
|
+
import { type Registry } from '@effect-atom/atom-react';
|
|
6
6
|
|
|
7
|
-
import { Obj } from '@dxos/echo';
|
|
8
|
-
import { getTypename, toJsonSchema } from '@dxos/echo-schema';
|
|
9
|
-
import type { JsonSchemaType, SortDirectionType } from '@dxos/echo-schema';
|
|
7
|
+
import { Filter, type JsonSchema, Obj, Order, Query, type QueryAST, Ref, Type } from '@dxos/echo';
|
|
10
8
|
import {
|
|
11
|
-
createView,
|
|
12
|
-
type DataType,
|
|
13
|
-
getSchemaFromPropertyDefinitions,
|
|
14
9
|
ProjectionModel,
|
|
15
10
|
type SchemaPropertyDefinition,
|
|
11
|
+
View,
|
|
12
|
+
createDirectChangeCallback,
|
|
13
|
+
getSchemaFromPropertyDefinitions,
|
|
16
14
|
} from '@dxos/schema';
|
|
17
15
|
|
|
18
|
-
import {
|
|
16
|
+
import { Table } from '../types';
|
|
19
17
|
|
|
20
18
|
// TODO(ZaymonFC): Upstream these extra fields to SchemaPropertyDefinition to enhance schema-tools schema creation.
|
|
21
19
|
type PropertyDisplayProps = {
|
|
22
20
|
size: number;
|
|
23
21
|
title: string;
|
|
24
|
-
sort:
|
|
22
|
+
sort: QueryAST.OrderDirection;
|
|
25
23
|
};
|
|
26
24
|
|
|
27
25
|
export type TablePropertyDefinition = SchemaPropertyDefinition & Partial<PropertyDisplayProps>;
|
|
@@ -31,21 +29,21 @@ export type TablePropertyDefinition = SchemaPropertyDefinition & Partial<Propert
|
|
|
31
29
|
*/
|
|
32
30
|
// TODO(burdon): Remove variance.
|
|
33
31
|
export const getBaseSchema = ({
|
|
32
|
+
schema,
|
|
34
33
|
typename,
|
|
35
34
|
properties,
|
|
36
35
|
jsonSchema,
|
|
37
|
-
schema,
|
|
38
36
|
}: {
|
|
37
|
+
schema?: Type.Entity.Any;
|
|
39
38
|
typename?: string;
|
|
40
39
|
properties?: TablePropertyDefinition[];
|
|
41
|
-
jsonSchema?:
|
|
42
|
-
|
|
43
|
-
}): { typename: string; jsonSchema: JsonSchemaType } => {
|
|
40
|
+
jsonSchema?: JsonSchema.JsonSchema;
|
|
41
|
+
}): { typename: string; jsonSchema: JsonSchema.JsonSchema } => {
|
|
44
42
|
if (typename && properties) {
|
|
45
43
|
const schema = getSchemaFromPropertyDefinitions(typename, properties);
|
|
46
44
|
return { typename: schema.typename, jsonSchema: schema.jsonSchema };
|
|
47
45
|
} else if (schema) {
|
|
48
|
-
return { typename: getTypename(schema)!, jsonSchema: toJsonSchema(schema) };
|
|
46
|
+
return { typename: Type.getTypename(schema)!, jsonSchema: Type.toJsonSchema(schema) };
|
|
49
47
|
} else if (typename && jsonSchema) {
|
|
50
48
|
return { typename, jsonSchema };
|
|
51
49
|
} else {
|
|
@@ -54,41 +52,49 @@ export const getBaseSchema = ({
|
|
|
54
52
|
};
|
|
55
53
|
|
|
56
54
|
export const makeDynamicTable = ({
|
|
57
|
-
|
|
55
|
+
registry,
|
|
58
56
|
jsonSchema,
|
|
59
57
|
properties,
|
|
60
58
|
}: {
|
|
61
|
-
|
|
62
|
-
jsonSchema:
|
|
59
|
+
registry: Registry.Registry;
|
|
60
|
+
jsonSchema: JsonSchema.JsonSchema;
|
|
63
61
|
properties?: TablePropertyDefinition[];
|
|
64
|
-
}): { projection: ProjectionModel;
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
typename,
|
|
62
|
+
}): { projection: ProjectionModel; object: Table.Table } => {
|
|
63
|
+
const view = View.make({
|
|
64
|
+
query: Query.select(Filter.everything()),
|
|
68
65
|
jsonSchema,
|
|
69
|
-
presentation: table,
|
|
70
66
|
...(properties && { fields: properties.map((property) => property.name) }),
|
|
71
67
|
});
|
|
68
|
+
const object = Obj.make(Table.Table, { view: Ref.make(view), sizes: {} });
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
// Use direct change callback for in-memory objects (non-ECHO backed).
|
|
71
|
+
const projection = new ProjectionModel({
|
|
72
|
+
registry,
|
|
73
|
+
view,
|
|
74
|
+
baseSchema: jsonSchema,
|
|
75
|
+
change: createDirectChangeCallback(view.projection, jsonSchema),
|
|
76
|
+
});
|
|
77
|
+
projection.normalizeView();
|
|
78
|
+
if (properties && projection.getFields()) {
|
|
79
|
+
setProperties(view, projection, object, properties);
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
return { projection,
|
|
82
|
+
return { projection, object };
|
|
79
83
|
};
|
|
80
84
|
|
|
81
85
|
const setProperties = (
|
|
82
|
-
view:
|
|
86
|
+
view: View.View,
|
|
83
87
|
projection: ProjectionModel,
|
|
84
|
-
table:
|
|
88
|
+
table: Table.Table,
|
|
85
89
|
properties: TablePropertyDefinition[],
|
|
86
90
|
) => {
|
|
87
91
|
for (const property of properties) {
|
|
88
|
-
const field = projection.
|
|
92
|
+
const field = projection.getFields().find((field) => field.path === property.name);
|
|
89
93
|
if (field) {
|
|
90
94
|
if (property.size !== undefined) {
|
|
91
|
-
table
|
|
95
|
+
Obj.change(table, (t) => {
|
|
96
|
+
t.sizes[field.path] = property.size!;
|
|
97
|
+
});
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
if (property.title !== undefined) {
|
|
@@ -100,8 +106,14 @@ const setProperties = (
|
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
if (property.sort) {
|
|
103
|
-
|
|
104
|
-
view.query.
|
|
109
|
+
// Apply sort to query instead of deprecated view.sort
|
|
110
|
+
const currentQuery = Query.fromAst(Obj.getSnapshot(view).query.ast);
|
|
111
|
+
// Use any type parameter since we're working with dynamic field paths
|
|
112
|
+
const newQuery = currentQuery.orderBy(Order.property<any>(field.path as string, property.sort));
|
|
113
|
+
Obj.change(view, (v) => {
|
|
114
|
+
// Type assertion needed because Query AST types have some variance issues.
|
|
115
|
+
v.query.ast = newQuery.ast as typeof v.query.ast;
|
|
116
|
+
});
|
|
105
117
|
}
|
|
106
118
|
}
|
|
107
119
|
}
|
package/src/util/index.ts
CHANGED