@dxos/react-ui-table 0.8.4-main.f9ba587 → 0.8.4-main.fd6878d
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-L2LSB6KV.mjs +18 -0
- package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
- package/dist/lib/browser/chunk-ZAOSIG3L.mjs +1814 -0
- package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +658 -2359
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +123 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +3 -5
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
- package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +1815 -0
- package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +658 -2359
- 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 +124 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +3 -5
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/{relations.stories.d.ts → Relations.stories.d.ts} +2 -2
- 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 +1 -0
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +3 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +8 -7
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +2 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +17 -16
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +2 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/model/table-sorting.d.ts +4 -4
- package/dist/types/src/model/table-sorting.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +2 -2
- 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/types/schema.d.ts +8 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util/dynamic-table.d.ts +3 -4
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- 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/src/util/table.d.ts +9 -11
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -36
- package/src/components/Table/ColumnActionsMenu.tsx +1 -1
- package/src/components/Table/ColumnSettings.tsx +15 -11
- package/src/components/Table/CreateRefPanel.tsx +6 -4
- package/src/components/Table/DynamicTable.stories.tsx +3 -2
- package/src/components/Table/DynamicTable.tsx +17 -10
- package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +36 -35
- package/src/components/Table/RowActionsMenu.tsx +3 -3
- package/src/components/Table/Table.stories.tsx +99 -144
- package/src/components/Table/Table.tsx +87 -71
- package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
- package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -27
- package/src/components/TableCellEditor/TableCellEditor.tsx +42 -27
- package/src/components/TableCellEditor/extension.ts +2 -2
- package/src/components/TableToolbar/TableToolbar.tsx +2 -2
- package/src/components/testing.ts +7 -24
- package/src/hooks/useTableModel.ts +21 -17
- package/src/index.ts +0 -1
- package/src/model/modal-controller.ts +1 -1
- package/src/model/selection-model.ts +6 -3
- package/src/model/table-model.test.ts +8 -8
- package/src/model/table-model.ts +68 -62
- package/src/model/table-presentation.test.ts +8 -8
- package/src/model/table-presentation.ts +35 -13
- package/src/model/table-sorting.ts +11 -11
- package/src/playwright/smoke.spec.ts +105 -14
- package/src/testing/index.ts +5 -0
- package/src/testing/useTestTableModel.ts +123 -0
- package/src/translations.ts +2 -2
- package/src/types/index.ts +2 -2
- package/src/types/schema.ts +16 -0
- package/src/util/dynamic-table.ts +20 -18
- package/src/util/schema.ts +1 -1
- package/src/util/table-buttons.ts +2 -1
- package/src/util/table-controls.ts +4 -1
- package/src/util/table.ts +37 -111
- package/dist/lib/browser/chunk-Q5QQXJUO.mjs +0 -27
- package/dist/lib/browser/chunk-Q5QQXJUO.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs +0 -29
- package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs.map +0 -7
- package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
- package/dist/types/src/types/table.d.ts +0 -56
- package/dist/types/src/types/table.d.ts.map +0 -1
- package/src/types/table.ts +0 -27
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { expect, test } from '@playwright/test';
|
|
6
6
|
|
|
7
|
+
import { type DxGrid } from '@dxos/lit-grid';
|
|
7
8
|
import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
|
|
8
9
|
|
|
9
10
|
import { TableManager } from './TableManager';
|
|
10
11
|
|
|
11
12
|
const storyUrl = storybookUrl('ui-react-ui-table-table--default', 9004);
|
|
13
|
+
const relationsStoryUrl = storybookUrl('ui-react-ui-table-relations--default', 9004);
|
|
12
14
|
|
|
13
15
|
// NOTE(ZaymonFC): This test suite relies on the faker seed being set to 0 in the story.
|
|
14
16
|
test.describe('Table', () => {
|
|
@@ -28,12 +30,12 @@ test.describe('Table', () => {
|
|
|
28
30
|
|
|
29
31
|
await table.grid.ready();
|
|
30
32
|
await table.sortColumn(0, 'descending');
|
|
31
|
-
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('
|
|
32
|
-
await expect(table.grid.cell(0, 9, 'grid')).toContainText('
|
|
33
|
+
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Sit.');
|
|
34
|
+
await expect(table.grid.cell(0, 9, 'grid')).toContainText('Aut.');
|
|
33
35
|
|
|
34
36
|
await table.sortColumn(0, 'ascending');
|
|
35
|
-
await expect(table.grid.cell(0, 0, 'grid')).toContainText('
|
|
36
|
-
await expect(table.grid.cell(0, 9, 'grid')).toHaveText('
|
|
37
|
+
await expect(table.grid.cell(0, 0, 'grid')).toContainText('Aut.');
|
|
38
|
+
await expect(table.grid.cell(0, 9, 'grid')).toHaveText('Sit.');
|
|
37
39
|
await page.close();
|
|
38
40
|
});
|
|
39
41
|
|
|
@@ -65,7 +67,7 @@ test.describe('Table', () => {
|
|
|
65
67
|
|
|
66
68
|
await table.grid.ready();
|
|
67
69
|
await table.deleteRow(0);
|
|
68
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
70
|
+
await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
|
|
69
71
|
await page.close();
|
|
70
72
|
});
|
|
71
73
|
|
|
@@ -80,8 +82,8 @@ test.describe('Table', () => {
|
|
|
80
82
|
// Delete action affects all selected rows.
|
|
81
83
|
await table.deleteRow(0);
|
|
82
84
|
|
|
83
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
84
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
85
|
+
await expect(page.getByRole('gridcell', { name: 'Sapiente.' })).toHaveCount(0);
|
|
86
|
+
await expect(page.getByRole('gridcell', { name: 'Beatae.' })).toHaveCount(0);
|
|
85
87
|
await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(0);
|
|
86
88
|
await page.close();
|
|
87
89
|
});
|
|
@@ -112,7 +114,6 @@ test.describe('Table', () => {
|
|
|
112
114
|
});
|
|
113
115
|
|
|
114
116
|
// 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
117
|
test('add column', async ({ browser, browserName }) => {
|
|
117
118
|
test.skip(browserName === 'webkit');
|
|
118
119
|
const { page } = await setupPage(browser, { url: storyUrl });
|
|
@@ -141,11 +142,11 @@ test.describe('Table', () => {
|
|
|
141
142
|
await table.grid.cell(4, 0, 'grid').click();
|
|
142
143
|
// TODO(wittjosiah): Surprisingly long delay needed here.
|
|
143
144
|
await page.keyboard.press('A', { delay: 1_000 });
|
|
144
|
-
await page.keyboard.press('
|
|
145
|
-
await page.getByRole('option', { name: '
|
|
145
|
+
await page.keyboard.press('u', { delay: 1_000 });
|
|
146
|
+
await page.getByRole('option', { name: 'Aut.' }).click();
|
|
146
147
|
|
|
147
148
|
// Assert that the value is shown in the cell.
|
|
148
|
-
await expect(page.getByRole('gridcell', { name: '
|
|
149
|
+
await expect(page.getByRole('gridcell', { name: 'Aut.' }).first()).toBeVisible();
|
|
149
150
|
|
|
150
151
|
// Create new object.
|
|
151
152
|
await table.grid.cell(4, 1, 'grid').click();
|
|
@@ -174,13 +175,103 @@ test.describe('Table', () => {
|
|
|
174
175
|
await page.getByTestId('table-switch').nth(7).click();
|
|
175
176
|
|
|
176
177
|
// Test that checks are durable in the data model by sorting.
|
|
177
|
-
await table.sortColumn(
|
|
178
|
+
await table.sortColumn(1, 'descending');
|
|
178
179
|
|
|
179
180
|
// Assert the first two switch checkboxes are checked.
|
|
180
181
|
await expect(page.getByTestId('table-switch').first()).toBeChecked();
|
|
181
182
|
await expect(page.getByTestId('table-switch').nth(1)).toBeChecked();
|
|
182
|
-
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('
|
|
183
|
-
await expect(table.grid.cell(0, 1, 'grid')).toHaveText('
|
|
183
|
+
await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Sapiente.');
|
|
184
|
+
await expect(table.grid.cell(0, 1, 'grid')).toHaveText('Beatae.');
|
|
185
|
+
|
|
186
|
+
await page.close();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('extant relations work as expected', async ({ browser, browserName }) => {
|
|
190
|
+
test.skip(browserName === 'webkit');
|
|
191
|
+
const { page } = await setupPage(browser, { url: relationsStoryUrl });
|
|
192
|
+
|
|
193
|
+
// Wait for the page to load
|
|
194
|
+
await page.locator('dx-grid > .dx-grid').nth(1).waitFor({ state: 'visible' });
|
|
195
|
+
|
|
196
|
+
// Find the dx-grid element for the contactModel (second table)
|
|
197
|
+
// The contactModel is used in the second Table.Main component in the story
|
|
198
|
+
const dxGrid = page.locator('dx-grid').nth(1);
|
|
199
|
+
await dxGrid.waitFor({ state: 'visible' });
|
|
200
|
+
|
|
201
|
+
// Scroll to the last column (column 8)
|
|
202
|
+
await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
|
|
203
|
+
dxGridElement.scrollToColumn(8);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Click on the cell at aria-rowindex=0 aria-colindex=8 to focus it
|
|
207
|
+
const targetCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="8"]');
|
|
208
|
+
await targetCell.click();
|
|
209
|
+
|
|
210
|
+
// Click again to engage edit mode
|
|
211
|
+
await page.keyboard.press('Enter');
|
|
212
|
+
await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
|
|
213
|
+
|
|
214
|
+
// Type the first few letters of an org name.
|
|
215
|
+
const orgName =
|
|
216
|
+
(await page
|
|
217
|
+
.locator('dx-grid')
|
|
218
|
+
.nth(0)
|
|
219
|
+
.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="0"] .dx-grid__cell__content')
|
|
220
|
+
.textContent()) ?? 'never';
|
|
221
|
+
await page.keyboard.type(orgName.substring(0, 4), { delay: 500 });
|
|
222
|
+
|
|
223
|
+
// Assert that there is an element with aria-selected on the page
|
|
224
|
+
await expect(page.locator('[role="option"][aria-selected]')).toBeVisible();
|
|
225
|
+
|
|
226
|
+
// Type the enter key
|
|
227
|
+
await page.keyboard.press('Enter');
|
|
228
|
+
|
|
229
|
+
// Assert that the cell element has the org name
|
|
230
|
+
await expect(targetCell).toHaveText(orgName);
|
|
231
|
+
|
|
232
|
+
await page.close();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('new relations work as expected', async ({ browser, browserName }) => {
|
|
236
|
+
test.skip(browserName === 'webkit');
|
|
237
|
+
const { page } = await setupPage(browser, { url: relationsStoryUrl });
|
|
238
|
+
|
|
239
|
+
// Wait for the page to load
|
|
240
|
+
await page.locator('dx-grid > .dx-grid').nth(1).waitFor({ state: 'visible' });
|
|
241
|
+
|
|
242
|
+
// Find the dx-grid element for the contactModel (second table)
|
|
243
|
+
// The contactModel is used in the second Table.Main component in the story
|
|
244
|
+
const dxGrid = page.locator('dx-grid').nth(1);
|
|
245
|
+
await dxGrid.waitFor({ state: 'visible' });
|
|
246
|
+
|
|
247
|
+
// Scroll to the last column (column 8)
|
|
248
|
+
await dxGrid.evaluate(async (dxGridElement: DxGrid) => {
|
|
249
|
+
dxGridElement.scrollToColumn(8);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Click on the cell at aria-rowindex=0 aria-colindex=8 to focus it
|
|
253
|
+
const targetCell = dxGrid.locator('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="8"]');
|
|
254
|
+
await targetCell.click();
|
|
255
|
+
|
|
256
|
+
// Click again to engage edit mode
|
|
257
|
+
await page.keyboard.press('Enter');
|
|
258
|
+
await page.getByTestId('grid.cell-editor').waitFor({ state: 'visible' });
|
|
259
|
+
|
|
260
|
+
// Type the first few letters of an org name.
|
|
261
|
+
const orgName = 'Sally';
|
|
262
|
+
await page.keyboard.type(orgName, { delay: 500 });
|
|
263
|
+
|
|
264
|
+
// Assert that there is an element with aria-selected on the page
|
|
265
|
+
await expect(page.locator('[role="option"][aria-selected]')).toBeVisible();
|
|
266
|
+
|
|
267
|
+
// Type the enter key
|
|
268
|
+
await page.keyboard.press('Enter');
|
|
269
|
+
|
|
270
|
+
// Click the save button in the popover
|
|
271
|
+
await page.getByTestId('save-button').click();
|
|
272
|
+
|
|
273
|
+
// Assert that the cell element has the org name
|
|
274
|
+
await expect(targetCell).toHaveText(orgName);
|
|
184
275
|
|
|
185
276
|
await page.close();
|
|
186
277
|
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { isMutable, toJsonSchema } from '@dxos/echo-schema';
|
|
8
|
+
import { useGlobalFilteredObjects } from '@dxos/plugin-search';
|
|
9
|
+
import { faker } from '@dxos/random';
|
|
10
|
+
import { useClient } from '@dxos/react-client';
|
|
11
|
+
import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
|
|
12
|
+
import { useClientProvider } from '@dxos/react-client/testing';
|
|
13
|
+
import { DataType, ProjectionModel } from '@dxos/schema';
|
|
14
|
+
|
|
15
|
+
import { type TableController } from '../components';
|
|
16
|
+
import { useAddRow, useTableModel } from '../hooks';
|
|
17
|
+
import { TablePresentation } from '../model';
|
|
18
|
+
|
|
19
|
+
faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Custom hook to create and manage a test table model for storybook demonstrations.
|
|
23
|
+
* Provides table data, schema, and handlers for table operations.
|
|
24
|
+
*/
|
|
25
|
+
export const useTestTableModel = () => {
|
|
26
|
+
const client = useClient();
|
|
27
|
+
const { space } = useClientProvider();
|
|
28
|
+
|
|
29
|
+
const views = useQuery(space, Filter.type(DataType.View));
|
|
30
|
+
const view = useMemo(() => views.at(0), [views]);
|
|
31
|
+
const schema = useSchema(client, space, view?.query.typename);
|
|
32
|
+
const jsonSchema = useMemo(() => (schema ? toJsonSchema(schema) : undefined), [schema]);
|
|
33
|
+
|
|
34
|
+
const projection = useMemo(() => {
|
|
35
|
+
if (schema && view?.projection) {
|
|
36
|
+
return new ProjectionModel(toJsonSchema(schema), view.projection);
|
|
37
|
+
}
|
|
38
|
+
}, [schema, view?.projection]);
|
|
39
|
+
|
|
40
|
+
const features = useMemo(
|
|
41
|
+
() => ({
|
|
42
|
+
selection: { enabled: true, mode: 'multiple' as const },
|
|
43
|
+
dataEditable: true,
|
|
44
|
+
schemaEditable: schema && isMutable(schema),
|
|
45
|
+
}),
|
|
46
|
+
[schema],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const objects = useQuery(space, schema ? Filter.type(schema) : Filter.nothing());
|
|
50
|
+
const filteredObjects = useGlobalFilteredObjects(objects);
|
|
51
|
+
|
|
52
|
+
const tableRef = useRef<TableController>(null);
|
|
53
|
+
const handleCellUpdate = useCallback((cell: any) => {
|
|
54
|
+
tableRef.current?.update?.(cell);
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
const handleRowOrderChange = useCallback(() => {
|
|
58
|
+
tableRef.current?.update?.();
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const addRow = useAddRow({ space, schema });
|
|
62
|
+
|
|
63
|
+
const handleDeleteRows = useCallback(
|
|
64
|
+
(_: number, objects: any[]) => {
|
|
65
|
+
for (const object of objects) {
|
|
66
|
+
space?.db.remove(object);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[space],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleDeleteColumn = useCallback(
|
|
73
|
+
(fieldId: string) => {
|
|
74
|
+
if (projection) {
|
|
75
|
+
projection.deleteFieldProjection(fieldId);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[projection],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const model = useTableModel({
|
|
82
|
+
view,
|
|
83
|
+
schema: jsonSchema,
|
|
84
|
+
projection,
|
|
85
|
+
features,
|
|
86
|
+
rows: filteredObjects,
|
|
87
|
+
onInsertRow: addRow,
|
|
88
|
+
onDeleteRows: handleDeleteRows,
|
|
89
|
+
onDeleteColumn: handleDeleteColumn,
|
|
90
|
+
onCellUpdate: handleCellUpdate,
|
|
91
|
+
onRowOrderChange: handleRowOrderChange,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const handleInsertRow = useCallback(() => {
|
|
95
|
+
model?.insertRow();
|
|
96
|
+
tableRef.current?.focusDraft?.();
|
|
97
|
+
}, [model, tableRef.current]);
|
|
98
|
+
|
|
99
|
+
const handleSaveView = useCallback(() => {
|
|
100
|
+
model?.saveView();
|
|
101
|
+
}, [model]);
|
|
102
|
+
|
|
103
|
+
const presentation = useMemo(() => {
|
|
104
|
+
if (model) {
|
|
105
|
+
return new TablePresentation(model);
|
|
106
|
+
}
|
|
107
|
+
}, [model]);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
schema,
|
|
111
|
+
view,
|
|
112
|
+
projection,
|
|
113
|
+
tableRef,
|
|
114
|
+
model,
|
|
115
|
+
presentation,
|
|
116
|
+
space,
|
|
117
|
+
client,
|
|
118
|
+
handleInsertRow,
|
|
119
|
+
handleSaveView,
|
|
120
|
+
handleDeleteRows,
|
|
121
|
+
handleDeleteColumn,
|
|
122
|
+
};
|
|
123
|
+
};
|
package/src/translations.ts
CHANGED
|
@@ -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': 'Record type',
|
|
18
|
+
'new schema': 'New record type',
|
|
19
19
|
'continue label': 'Continue',
|
|
20
20
|
'create stack section label': 'Create table',
|
|
21
21
|
'add row label': 'Add row',
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { Type } from '@dxos/echo';
|
|
8
|
+
import { JsonPath } from '@dxos/effect';
|
|
9
|
+
|
|
10
|
+
export const TableView = Schema.Struct({
|
|
11
|
+
sizes: Schema.Record({
|
|
12
|
+
key: JsonPath,
|
|
13
|
+
value: Schema.Number,
|
|
14
|
+
}).pipe(Schema.mutable),
|
|
15
|
+
}).pipe(Type.Obj({ typename: 'dxos.org/type/TableView', version: '0.1.0' }));
|
|
16
|
+
export type TableView = Schema.Schema.Type<typeof TableView>;
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Schema } from 'effect';
|
|
6
6
|
|
|
7
|
-
import { Obj
|
|
7
|
+
import { Obj } from '@dxos/echo';
|
|
8
8
|
import { getTypename, toJsonSchema } from '@dxos/echo-schema';
|
|
9
9
|
import type { JsonSchemaType, SortDirectionType } from '@dxos/echo-schema';
|
|
10
10
|
import {
|
|
11
|
+
type DataType,
|
|
12
|
+
ProjectionModel,
|
|
13
|
+
type SchemaPropertyDefinition,
|
|
11
14
|
createView,
|
|
12
15
|
getSchemaFromPropertyDefinitions,
|
|
13
|
-
ViewProjection,
|
|
14
|
-
type ViewType,
|
|
15
|
-
type SchemaPropertyDefinition,
|
|
16
16
|
} from '@dxos/schema';
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { TableView } from '../types';
|
|
19
19
|
|
|
20
20
|
// TODO(ZaymonFC): Upstream these extra fields to SchemaPropertyDefinition to enhance schema-tools schema creation.
|
|
21
21
|
type PropertyDisplayProps = {
|
|
@@ -61,32 +61,34 @@ export const makeDynamicTable = ({
|
|
|
61
61
|
typename: string;
|
|
62
62
|
jsonSchema: JsonSchemaType;
|
|
63
63
|
properties?: TablePropertyDefinition[];
|
|
64
|
-
}): {
|
|
64
|
+
}): { projection: ProjectionModel; view: DataType.View } => {
|
|
65
|
+
const table = Obj.make(TableView, { sizes: {} });
|
|
65
66
|
const view = createView({
|
|
66
|
-
name: 'dynamic-table',
|
|
67
67
|
typename,
|
|
68
68
|
jsonSchema,
|
|
69
|
+
presentation: table,
|
|
69
70
|
...(properties && { fields: properties.map((property) => property.name) }),
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
setProperties(view, projection, properties);
|
|
73
|
+
const projection = new ProjectionModel(jsonSchema, view.projection);
|
|
74
|
+
if (properties && projection.fields) {
|
|
75
|
+
setProperties(view, projection, table, properties);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
return {
|
|
79
|
-
table,
|
|
80
|
-
projection,
|
|
81
|
-
};
|
|
78
|
+
return { projection, view };
|
|
82
79
|
};
|
|
83
80
|
|
|
84
|
-
const setProperties = (
|
|
81
|
+
const setProperties = (
|
|
82
|
+
view: DataType.View,
|
|
83
|
+
projection: ProjectionModel,
|
|
84
|
+
table: TableView,
|
|
85
|
+
properties: TablePropertyDefinition[],
|
|
86
|
+
) => {
|
|
85
87
|
for (const property of properties) {
|
|
86
|
-
const field =
|
|
88
|
+
const field = projection.fields.find((field) => field.path === property.name);
|
|
87
89
|
if (field) {
|
|
88
90
|
if (property.size !== undefined) {
|
|
89
|
-
field.
|
|
91
|
+
table.sizes[field.path] = property.size;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
if (property.title !== undefined) {
|
package/src/util/schema.ts
CHANGED
|
@@ -40,7 +40,7 @@ const createButton = ({
|
|
|
40
40
|
.join(' ');
|
|
41
41
|
|
|
42
42
|
const positionClass = type === 'primary' ? 'inline-end-2' : 'inline-end-9';
|
|
43
|
-
return `<button ${attr} data-testid="${testId}" class="dx-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 ${positionClass}" ${dataAttrs} ${disabled ? 'disabled' : ''}><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
|
|
43
|
+
return `<button ${attr} data-testid="${testId}" class="dx-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 ${positionClass}" ${dataAttrs} ${disabled ? 'disabled' : ''} data-dx-grid-action="accessory"><svg data-size="4"><use href="/icons.svg#${icon}"/></svg></button>`;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const addColumnButton = {
|
|
@@ -130,6 +130,7 @@ const saveDraftRowButton = {
|
|
|
130
130
|
disabled,
|
|
131
131
|
data: {
|
|
132
132
|
'data-row-index': rowIndex.toString(),
|
|
133
|
+
'data-variant': 'primary',
|
|
133
134
|
},
|
|
134
135
|
testId: 'table-save-draft-row-button',
|
|
135
136
|
});
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
// Core types.
|
|
6
|
+
import { accessoryHandlesPointerdownAttrs } from '@dxos/lit-grid';
|
|
7
|
+
|
|
6
8
|
export type TableControl = 'checkbox' | 'switch';
|
|
7
9
|
|
|
8
10
|
export type ControlData =
|
|
@@ -41,7 +43,7 @@ const renderInput = (
|
|
|
41
43
|
preventToggle = false,
|
|
42
44
|
testId: string,
|
|
43
45
|
) => {
|
|
44
|
-
return `<input type="checkbox" class="${baseClass}" ${renderAttributes(attrs)} ${checked ? 'checked' : ''} ${preventToggle ? 'onclick="return false"' : ''} ${disabled ? 'disabled' : ''} data-testid="${testId}"/>`;
|
|
46
|
+
return `<input type="checkbox" class="${baseClass}" ${renderAttributes(attrs)} ${checked ? 'checked' : ''} ${preventToggle ? 'onclick="return false"' : ''} ${disabled ? 'disabled' : ''} data-testid="${testId}" data-dx-grid-action="accessory"/>`;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
export const CheckboxStory = ({
|
|
@@ -64,6 +66,7 @@ export const SwitchStory = ({ colIndex, rowIndex, checked = false, disabled = fa
|
|
|
64
66
|
[CONTROL_IDENTIFIERS.switch]: '',
|
|
65
67
|
'data-row-index': rowIndex.toString(),
|
|
66
68
|
'data-col-index': colIndex.toString(),
|
|
69
|
+
...accessoryHandlesPointerdownAttrs,
|
|
67
70
|
};
|
|
68
71
|
|
|
69
72
|
return renderInput(BASE_CLASSES.switch, attrs, checked, disabled, true, 'table-switch');
|
package/src/util/table.ts
CHANGED
|
@@ -2,126 +2,52 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Match } from 'effect';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type JsonPath,
|
|
12
|
-
type JsonProp,
|
|
13
|
-
Ref,
|
|
14
|
-
TypedObject,
|
|
15
|
-
TypeEnum,
|
|
16
|
-
} from '@dxos/echo-schema';
|
|
17
|
-
import { type Client, PublicKey } from '@dxos/react-client';
|
|
18
|
-
import { live, type Space } from '@dxos/react-client/echo';
|
|
19
|
-
import { createFieldId, createView, getSchemaProperties, ViewProjection, type ViewType } from '@dxos/schema';
|
|
7
|
+
import { Obj } from '@dxos/echo';
|
|
8
|
+
import { type JsonPath, type JsonSchemaType, toEffectSchema } from '@dxos/echo-schema';
|
|
9
|
+
import { type SimpleType } from '@dxos/effect';
|
|
10
|
+
import { type CreateViewFromSpaceProps, type DataType, createViewFromSpace, getSchemaProperties } from '@dxos/schema';
|
|
20
11
|
|
|
21
|
-
import {
|
|
12
|
+
import { TableView } from '../types';
|
|
22
13
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
client: Client;
|
|
26
|
-
space: Space;
|
|
27
|
-
table: TableType;
|
|
28
|
-
typename?: string;
|
|
29
|
-
initialRow?: boolean;
|
|
14
|
+
type CreateTableProps = Omit<CreateViewFromSpaceProps, 'presentation'> & {
|
|
15
|
+
sizes?: Record<string, number>;
|
|
30
16
|
};
|
|
31
17
|
|
|
32
|
-
export const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
18
|
+
export const createTable = async ({
|
|
19
|
+
sizes,
|
|
20
|
+
...props
|
|
21
|
+
}: CreateTableProps): Promise<{
|
|
22
|
+
jsonSchema: JsonSchemaType;
|
|
23
|
+
view: DataType.View;
|
|
24
|
+
schema: ReturnType<typeof toEffectSchema>;
|
|
25
|
+
}> => {
|
|
26
|
+
const table = Obj.make(TableView, { sizes: {} });
|
|
27
|
+
const { jsonSchema, view } = await createViewFromSpace({ ...props, presentation: table });
|
|
28
|
+
|
|
29
|
+
// Preset sizes.
|
|
30
|
+
const schema = toEffectSchema(jsonSchema);
|
|
31
|
+
const shouldIncludeId = props.fields?.find((field) => field === 'id') !== undefined;
|
|
32
|
+
const properties = getSchemaProperties(schema.ast, {}, shouldIncludeId);
|
|
33
|
+
for (const property of properties) {
|
|
34
|
+
if (sizes?.[property.name]) {
|
|
35
|
+
table.sizes[property.name as JsonPath] = sizes[property.name];
|
|
36
|
+
continue;
|
|
43
37
|
}
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
createView({
|
|
49
|
-
// TODO(ZaymonFC): Don't hardcode name?
|
|
50
|
-
name: 'View',
|
|
51
|
-
typename: schema.typename,
|
|
52
|
-
jsonSchema: schema.jsonSchema,
|
|
53
|
-
fields,
|
|
39
|
+
Match.type<SimpleType>().pipe(
|
|
40
|
+
Match.when('boolean', () => {
|
|
41
|
+
table.sizes[property.name as JsonPath] = 100;
|
|
54
42
|
}),
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return schema;
|
|
58
|
-
} else {
|
|
59
|
-
const [schema] = await space.db.schemaRegistry.register([createContactSchema()]);
|
|
60
|
-
const fields = ContactFields;
|
|
61
|
-
|
|
62
|
-
table.view = Ref.make(
|
|
63
|
-
createView({
|
|
64
|
-
name: 'View',
|
|
65
|
-
typename: schema.typename,
|
|
66
|
-
jsonSchema: schema.jsonSchema,
|
|
67
|
-
fields,
|
|
43
|
+
Match.when('number', () => {
|
|
44
|
+
table.sizes[property.name as JsonPath] = 100;
|
|
68
45
|
}),
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (initialRow) {
|
|
74
|
-
// TODO(burdon): Last (first) row should not be in db and should be managed by the model.
|
|
75
|
-
space.db.add(live(schema, {}));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return schema;
|
|
46
|
+
Match.orElse(() => {
|
|
47
|
+
// Noop.
|
|
48
|
+
}),
|
|
49
|
+
)(property.type);
|
|
79
50
|
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const createContactSchema = () =>
|
|
83
|
-
TypedObject({
|
|
84
|
-
typename: `example.com/type/${PublicKey.random().truncate()}`,
|
|
85
|
-
version: '0.1.0',
|
|
86
|
-
})({
|
|
87
|
-
name: Schema.optional(Schema.String).annotations({ title: 'Name' }),
|
|
88
|
-
active: Schema.optional(Schema.Boolean).annotations({ title: 'Active' }),
|
|
89
|
-
email: Schema.optional(Format.Email),
|
|
90
|
-
salary: Schema.optional(Format.Currency()).annotations({ title: 'Salary' }),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const ContactFields = ['name', 'email', 'salary', 'active'];
|
|
94
|
-
|
|
95
|
-
const createProjection = (schema: EchoSchema, view: ViewType): ViewProjection => {
|
|
96
|
-
const projection = new ViewProjection(schema.jsonSchema, view);
|
|
97
|
-
projection.setFieldProjection({
|
|
98
|
-
field: {
|
|
99
|
-
id: view.fields.find((f) => f.path === 'salary')!.id,
|
|
100
|
-
path: 'salary' as JsonPath,
|
|
101
|
-
size: 150,
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
projection.setFieldProjection({
|
|
105
|
-
field: {
|
|
106
|
-
id: view.fields.find((f) => f.path === 'active')!.id,
|
|
107
|
-
path: 'active' as JsonPath,
|
|
108
|
-
size: 100,
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
projection.setFieldProjection({
|
|
112
|
-
field: {
|
|
113
|
-
id: createFieldId(),
|
|
114
|
-
path: 'manager' as JsonPath,
|
|
115
|
-
referencePath: 'name' as JsonPath,
|
|
116
|
-
},
|
|
117
|
-
props: {
|
|
118
|
-
property: 'manager' as JsonProp,
|
|
119
|
-
type: TypeEnum.Ref,
|
|
120
|
-
format: FormatEnum.Ref,
|
|
121
|
-
referenceSchema: schema.typename,
|
|
122
|
-
title: 'Manager',
|
|
123
|
-
},
|
|
124
|
-
});
|
|
125
51
|
|
|
126
|
-
return
|
|
52
|
+
return { jsonSchema, schema, view };
|
|
127
53
|
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// src/types/table.ts
|
|
2
|
-
import { Schema } from "effect";
|
|
3
|
-
import { Type } from "@dxos/echo";
|
|
4
|
-
import { ObjectId, Ref, Expando, LabelAnnotation } from "@dxos/echo-schema";
|
|
5
|
-
import { ViewType } from "@dxos/schema";
|
|
6
|
-
var TableSchema = Schema.Struct({
|
|
7
|
-
id: ObjectId,
|
|
8
|
-
name: Schema.optional(Schema.String),
|
|
9
|
-
view: Schema.optional(Ref(ViewType)),
|
|
10
|
-
// TODO(burdon): Document why threads is included here?
|
|
11
|
-
threads: Schema.optional(Schema.Array(Ref(
|
|
12
|
-
Expando
|
|
13
|
-
/* ThreadType */
|
|
14
|
-
)))
|
|
15
|
-
}).pipe(LabelAnnotation.set([
|
|
16
|
-
"name"
|
|
17
|
-
]));
|
|
18
|
-
var TableType = TableSchema.pipe(Type.Obj({
|
|
19
|
-
typename: "dxos.org/type/Table",
|
|
20
|
-
version: "0.1.0"
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
export {
|
|
24
|
-
TableSchema,
|
|
25
|
-
TableType
|
|
26
|
-
};
|
|
27
|
-
//# sourceMappingURL=chunk-Q5QQXJUO.mjs.map
|