@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.
Files changed (122) hide show
  1. package/dist/lib/browser/chunk-L2LSB6KV.mjs +18 -0
  2. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-ZAOSIG3L.mjs +1814 -0
  4. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +658 -2359
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +123 -0
  9. package/dist/lib/browser/testing/index.mjs.map +7 -0
  10. package/dist/lib/browser/types/index.mjs +3 -5
  11. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
  12. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +1815 -0
  14. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +658 -2359
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +124 -0
  19. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  20. package/dist/lib/node-esm/types/index.mjs +3 -5
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
  26. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
  28. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  30. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/{relations.stories.d.ts → Relations.stories.d.ts} +2 -2
  32. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  34. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Table.d.ts +1 -0
  36. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
  38. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  40. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
  41. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -7
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
  46. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  47. package/dist/types/src/components/testing.d.ts +3 -40
  48. package/dist/types/src/components/testing.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useTableModel.d.ts +8 -7
  50. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  51. package/dist/types/src/index.d.ts +0 -1
  52. package/dist/types/src/index.d.ts.map +1 -1
  53. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  54. package/dist/types/src/model/selection-model.d.ts +2 -1
  55. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  56. package/dist/types/src/model/table-model.d.ts +17 -16
  57. package/dist/types/src/model/table-model.d.ts.map +1 -1
  58. package/dist/types/src/model/table-presentation.d.ts +2 -1
  59. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  60. package/dist/types/src/model/table-sorting.d.ts +4 -4
  61. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  62. package/dist/types/src/testing/index.d.ts +2 -0
  63. package/dist/types/src/testing/index.d.ts.map +1 -0
  64. package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
  65. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
  66. package/dist/types/src/translations.d.ts +2 -2
  67. package/dist/types/src/types/index.d.ts +1 -1
  68. package/dist/types/src/types/index.d.ts.map +1 -1
  69. package/dist/types/src/types/schema.d.ts +8 -0
  70. package/dist/types/src/types/schema.d.ts.map +1 -0
  71. package/dist/types/src/util/dynamic-table.d.ts +3 -4
  72. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  73. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  74. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  75. package/dist/types/src/util/table.d.ts +9 -11
  76. package/dist/types/src/util/table.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +50 -36
  79. package/src/components/Table/ColumnActionsMenu.tsx +1 -1
  80. package/src/components/Table/ColumnSettings.tsx +15 -11
  81. package/src/components/Table/CreateRefPanel.tsx +6 -4
  82. package/src/components/Table/DynamicTable.stories.tsx +3 -2
  83. package/src/components/Table/DynamicTable.tsx +17 -10
  84. package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +36 -35
  85. package/src/components/Table/RowActionsMenu.tsx +3 -3
  86. package/src/components/Table/Table.stories.tsx +99 -144
  87. package/src/components/Table/Table.tsx +87 -71
  88. package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
  89. package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
  90. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -27
  91. package/src/components/TableCellEditor/TableCellEditor.tsx +42 -27
  92. package/src/components/TableCellEditor/extension.ts +2 -2
  93. package/src/components/TableToolbar/TableToolbar.tsx +2 -2
  94. package/src/components/testing.ts +7 -24
  95. package/src/hooks/useTableModel.ts +21 -17
  96. package/src/index.ts +0 -1
  97. package/src/model/modal-controller.ts +1 -1
  98. package/src/model/selection-model.ts +6 -3
  99. package/src/model/table-model.test.ts +8 -8
  100. package/src/model/table-model.ts +68 -62
  101. package/src/model/table-presentation.test.ts +8 -8
  102. package/src/model/table-presentation.ts +35 -13
  103. package/src/model/table-sorting.ts +11 -11
  104. package/src/playwright/smoke.spec.ts +105 -14
  105. package/src/testing/index.ts +5 -0
  106. package/src/testing/useTestTableModel.ts +123 -0
  107. package/src/translations.ts +2 -2
  108. package/src/types/index.ts +2 -2
  109. package/src/types/schema.ts +16 -0
  110. package/src/util/dynamic-table.ts +20 -18
  111. package/src/util/schema.ts +1 -1
  112. package/src/util/table-buttons.ts +2 -1
  113. package/src/util/table-controls.ts +4 -1
  114. package/src/util/table.ts +37 -111
  115. package/dist/lib/browser/chunk-Q5QQXJUO.mjs +0 -27
  116. package/dist/lib/browser/chunk-Q5QQXJUO.mjs.map +0 -7
  117. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs +0 -29
  118. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs.map +0 -7
  119. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  120. package/dist/types/src/types/table.d.ts +0 -56
  121. package/dist/types/src/types/table.d.ts.map +0 -1
  122. 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('Uwe Øvergård');
32
- await expect(table.grid.cell(0, 9, 'grid')).toContainText('Anita');
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('Anita');
36
- await expect(table.grid.cell(0, 9, 'grid')).toHaveText('Uwe Øvergård');
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: 'Anita Mayer' })).toHaveCount(0);
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: 'Anita Mayer' })).toHaveCount(0);
84
- await expect(page.getByRole('gridcell', { name: 'Phonthip Sigurjónsson' })).toHaveCount(0);
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('n', { delay: 1_000 });
145
- await page.getByRole('option', { name: 'Anita Mayer' }).click();
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: 'Anita Mayer' }).first()).toBeVisible();
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(3, 'descending');
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('Anita Mayer');
183
- await expect(table.grid.cell(0, 1, 'grid')).toHaveText('Uwe Øvergård');
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,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './useTestTableModel';
@@ -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
+ };
@@ -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': 'Schema',
18
- 'new schema': 'New schema',
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',
@@ -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 * from './schema';
@@ -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, Ref } from '@dxos/echo';
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 { TableType } from '../types';
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
- }): { table: TableType; projection: ViewProjection } => {
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 table = Obj.make(TableType, { name: 'dynamic-table', view: Ref.make(view) });
73
- const projection = new ViewProjection(jsonSchema, view);
74
- if (properties && view.fields) {
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 = (view: ViewType, projection: ViewProjection, properties: TablePropertyDefinition[]) => {
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 = view.fields.find((field) => field.path === property.name);
88
+ const field = projection.fields.find((field) => field.path === property.name);
87
89
  if (field) {
88
90
  if (property.size !== undefined) {
89
- field.size = property.size;
91
+ table.sizes[field.path] = property.size;
90
92
  }
91
93
 
92
94
  if (property.title !== undefined) {
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { Schema } from 'effect';
6
- import { isTypeLiteral, TypeLiteral } from 'effect/SchemaAST';
6
+ import { TypeLiteral, isTypeLiteral } from 'effect/SchemaAST';
7
7
 
8
8
  /**
9
9
  * Creates a narrowed schema from an original schema that only includes
@@ -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 { Schema } from 'effect';
5
+ import { Match } from 'effect';
6
6
 
7
- import {
8
- type EchoSchema,
9
- Format,
10
- FormatEnum,
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 { type TableType } from '../types';
12
+ import { TableView } from '../types';
22
13
 
23
- // TODO(ZaymonFC): We don't need the client anymore.
24
- type InitialiseTableProps = {
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 initializeTable = async ({
33
- client,
34
- space,
35
- table,
36
- typename,
37
- initialRow = true,
38
- }: InitialiseTableProps): Promise<Schema.Schema.AnyNoContext> => {
39
- if (typename) {
40
- const schema = await space.db.graph.getSchemaByTypename(typename, space.db);
41
- if (!schema) {
42
- throw new Error(`Schema not found: ${typename}`);
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
- const fields = getSchemaProperties(schema.ast).map((prop) => prop.name);
46
-
47
- table.view = Ref.make(
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
- createProjection(schema, table.view.target!);
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 projection;
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