@dxos/react-ui-table 0.7.1-staging.8709ba7 → 0.7.2-staging.6d26b2a

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 (55) hide show
  1. package/dist/lib/browser/{chunk-A5W4XWPD.mjs → chunk-EWGUNGJC.mjs} +8 -6
  2. package/dist/lib/browser/chunk-EWGUNGJC.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +22 -11
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types/index.mjs +1 -1
  7. package/dist/lib/node/{chunk-32IGMRME.cjs → chunk-A2N7XWW6.cjs} +11 -9
  8. package/dist/lib/node/chunk-A2N7XWW6.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +23 -12
  10. package/dist/lib/node/index.cjs.map +3 -3
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types/index.cjs +2 -2
  13. package/dist/lib/node/types/index.cjs.map +1 -1
  14. package/dist/lib/node-esm/{chunk-DWQ4KGLD.mjs → chunk-SABDOZ6I.mjs} +8 -6
  15. package/dist/lib/node-esm/chunk-SABDOZ6I.mjs.map +7 -0
  16. package/dist/lib/node-esm/index.mjs +22 -11
  17. package/dist/lib/node-esm/index.mjs.map +3 -3
  18. package/dist/lib/node-esm/meta.json +1 -1
  19. package/dist/lib/node-esm/types/index.mjs +1 -1
  20. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  21. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/testing.d.ts +3 -2
  23. package/dist/types/src/components/testing.d.ts.map +1 -1
  24. package/dist/types/src/playwright/TableManager.d.ts +17 -0
  25. package/dist/types/src/playwright/TableManager.d.ts.map +1 -0
  26. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  27. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  28. package/dist/types/src/playwright/smoke.spec.d.ts +2 -0
  29. package/dist/types/src/playwright/smoke.spec.d.ts.map +1 -0
  30. package/dist/types/src/types/table.d.ts +9 -7
  31. package/dist/types/src/types/table.d.ts.map +1 -1
  32. package/dist/types/src/util/grid.d.ts +3 -3
  33. package/dist/types/src/util/grid.d.ts.map +1 -1
  34. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  35. package/dist/types/src/util/table.d.ts +5 -2
  36. package/dist/types/src/util/table.d.ts.map +1 -1
  37. package/package.json +38 -39
  38. package/src/_deprecated/components/ColumnMenu/ColumnMenu.stories.tsx +1 -1
  39. package/src/_deprecated/components/ColumnMenu/ColumnSettings.stories.tsx +1 -1
  40. package/src/_deprecated/components/Table/Table.stories.tsx +1 -1
  41. package/src/components/Table/ColumnActionsMenu.tsx +11 -5
  42. package/src/components/Table/RowActionsMenu.tsx +1 -1
  43. package/src/components/Table/Table.stories.tsx +5 -3
  44. package/src/components/Table/Table.tsx +2 -2
  45. package/src/playwright/TableManager.ts +60 -0
  46. package/src/playwright/playwright.config.ts +18 -0
  47. package/src/playwright/smoke.spec.ts +105 -0
  48. package/src/types/table.ts +12 -3
  49. package/src/util/grid.ts +3 -3
  50. package/src/util/table-buttons.ts +9 -4
  51. package/src/util/table-controls.ts +1 -1
  52. package/src/util/table.ts +11 -3
  53. package/dist/lib/browser/chunk-A5W4XWPD.mjs.map +0 -7
  54. package/dist/lib/node/chunk-32IGMRME.cjs.map +0 -7
  55. package/dist/lib/node-esm/chunk-DWQ4KGLD.mjs.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-table",
3
- "version": "0.7.1-staging.8709ba7",
3
+ "version": "0.7.2-staging.6d26b2a",
4
4
  "description": "A table component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,32 +8,30 @@
8
8
  "author": "DXOS.org",
9
9
  "sideEffects": true,
10
10
  "exports": {
11
+ "./deprecated": {
12
+ "types": "./dist/types/src/_deprecated/index.d.ts",
13
+ "browser": "./dist/lib/browser/_deprecated/index.mjs",
14
+ "node": "./dist/lib/node-esm/_deprecated/index.mjs"
15
+ },
11
16
  ".": {
12
17
  "types": "./dist/types/src/index.d.ts",
13
18
  "browser": "./dist/lib/browser/index.mjs",
14
- "require": "./dist/lib/node/index.cjs",
15
- "import": "./dist/lib/node-esm/index.mjs"
19
+ "node": "./dist/lib/node-esm/index.mjs"
16
20
  },
17
21
  "./types": {
18
22
  "types": "./dist/types/src/types/index.d.ts",
19
23
  "browser": "./dist/lib/browser/types/index.mjs",
20
- "require": "./dist/lib/node/types/index.cjs",
21
- "import": "./dist/lib/node-esm/types/index.mjs"
22
- },
23
- "./deprecated": {
24
- "types": "./dist/types/src/_deprecated/index.d.ts",
25
- "browser": "./dist/lib/browser/_deprecated/index.mjs",
26
- "require": "./dist/lib/node/_deprecated/index.cjs",
27
- "import": "./dist/lib/node-esm/_deprecated/index.mjs"
24
+ "node": "./dist/lib/node-esm/types/index.mjs"
28
25
  }
29
26
  },
27
+ "types": "dist/types/src/index.d.ts",
30
28
  "typesVersions": {
31
29
  "*": {
32
30
  "deprecated": [
33
- "./dist/types/src/_deprecated/index.d.ts"
31
+ "dist/types/src/_deprecated/index.d.ts"
34
32
  ],
35
33
  "types": [
36
- "./dist/types/src/types/index.d.ts"
34
+ "dist/types/src/types/index.d.ts"
37
35
  ]
38
36
  }
39
37
  },
@@ -55,25 +53,25 @@
55
53
  "@tanstack/react-virtual": "^3.9.0",
56
54
  "date-fns": "^3.3.1",
57
55
  "lodash.orderby": "^4.6.0",
58
- "@dxos/context": "0.7.1-staging.8709ba7",
59
- "@dxos/echo-schema": "0.7.1-staging.8709ba7",
60
- "@dxos/debug": "0.7.1-staging.8709ba7",
61
- "@dxos/invariant": "0.7.1-staging.8709ba7",
62
- "@dxos/log": "0.7.1-staging.8709ba7",
63
- "@dxos/keys": "0.7.1-staging.8709ba7",
64
- "@dxos/lit-grid": "0.7.1-staging.8709ba7",
65
- "@dxos/plugin-search": "0.7.1-staging.8709ba7",
66
- "@dxos/plugin-space": "0.7.1-staging.8709ba7",
67
- "@dxos/react-ui-attention": "0.7.1-staging.8709ba7",
68
- "@dxos/react-client": "0.7.1-staging.8709ba7",
69
- "@dxos/react-ui-form": "0.7.1-staging.8709ba7",
70
- "@dxos/react-ui-searchlist": "0.7.1-staging.8709ba7",
71
- "@dxos/react-ui-grid": "0.7.1-staging.8709ba7",
72
- "@dxos/react-ui-stack": "0.7.1-staging.8709ba7",
73
- "@dxos/react-ui-syntax-highlighter": "0.7.1-staging.8709ba7",
74
- "@dxos/react-ui-types": "0.7.1-staging.8709ba7",
75
- "@dxos/util": "0.7.1-staging.8709ba7",
76
- "@dxos/schema": "0.7.1-staging.8709ba7"
56
+ "@dxos/context": "0.7.2-staging.6d26b2a",
57
+ "@dxos/debug": "0.7.2-staging.6d26b2a",
58
+ "@dxos/echo-schema": "0.7.2-staging.6d26b2a",
59
+ "@dxos/invariant": "0.7.2-staging.6d26b2a",
60
+ "@dxos/keys": "0.7.2-staging.6d26b2a",
61
+ "@dxos/lit-grid": "0.7.2-staging.6d26b2a",
62
+ "@dxos/log": "0.7.2-staging.6d26b2a",
63
+ "@dxos/plugin-search": "0.7.2-staging.6d26b2a",
64
+ "@dxos/plugin-space": "0.7.2-staging.6d26b2a",
65
+ "@dxos/react-client": "0.7.2-staging.6d26b2a",
66
+ "@dxos/react-ui-form": "0.7.2-staging.6d26b2a",
67
+ "@dxos/react-ui-attention": "0.7.2-staging.6d26b2a",
68
+ "@dxos/react-ui-grid": "0.7.2-staging.6d26b2a",
69
+ "@dxos/react-ui-searchlist": "0.7.2-staging.6d26b2a",
70
+ "@dxos/react-ui-stack": "0.7.2-staging.6d26b2a",
71
+ "@dxos/react-ui-syntax-highlighter": "0.7.2-staging.6d26b2a",
72
+ "@dxos/react-ui-types": "0.7.2-staging.6d26b2a",
73
+ "@dxos/schema": "0.7.2-staging.6d26b2a",
74
+ "@dxos/util": "0.7.2-staging.6d26b2a"
77
75
  },
78
76
  "devDependencies": {
79
77
  "@phosphor-icons/react": "^2.1.5",
@@ -84,18 +82,19 @@
84
82
  "react": "~18.2.0",
85
83
  "react-dom": "~18.2.0",
86
84
  "vite": "5.4.7",
87
- "@dxos/echo-signals": "0.7.1-staging.8709ba7",
88
- "@dxos/react-ui": "0.7.1-staging.8709ba7",
89
- "@dxos/random": "0.7.1-staging.8709ba7",
90
- "@dxos/storybook-utils": "0.7.1-staging.8709ba7",
91
- "@dxos/react-ui-theme": "0.7.1-staging.8709ba7"
85
+ "@dxos/random": "0.7.2-staging.6d26b2a",
86
+ "@dxos/echo-signals": "0.7.2-staging.6d26b2a",
87
+ "@dxos/react-ui-theme": "0.7.2-staging.6d26b2a",
88
+ "@dxos/react-ui": "0.7.2-staging.6d26b2a",
89
+ "@dxos/test-utils": "0.7.2-staging.6d26b2a",
90
+ "@dxos/storybook-utils": "0.7.2-staging.6d26b2a"
92
91
  },
93
92
  "peerDependencies": {
94
93
  "@phosphor-icons/react": "^2.1.5",
95
94
  "react": "~18.2.0",
96
95
  "react-dom": "~18.2.0",
97
- "@dxos/react-ui-theme": "0.7.1-staging.8709ba7",
98
- "@dxos/react-ui": "0.7.1-staging.8709ba7"
96
+ "@dxos/react-ui": "0.7.2-staging.6d26b2a",
97
+ "@dxos/react-ui-theme": "0.7.2-staging.6d26b2a"
99
98
  },
100
99
  "publishConfig": {
101
100
  "access": "public"
@@ -15,7 +15,7 @@ import { type ColumnSettingsProps } from './ColumnSettings';
15
15
  import { type ColumnDef } from '../../schema';
16
16
 
17
17
  export default {
18
- title: 'ui/react-ui-table/ColumnMenu',
18
+ title: 'ui/react-ui-table/deprecated/ColumnMenu',
19
19
  component: ColumnMenu,
20
20
  decorators: [withTheme],
21
21
  };
@@ -20,7 +20,7 @@ const DefaultStory = (props: ColumnSettingsProps) => (
20
20
  );
21
21
 
22
22
  export default {
23
- title: 'ui/react-ui-table/ColumnSettings',
23
+ title: 'ui/react-ui-table/deprecated/ColumnSettings',
24
24
  component: ColumnSettings,
25
25
  render: DefaultStory,
26
26
  decorators: [withTheme, withLayout()],
@@ -128,7 +128,7 @@ const MinimalTable = (props: any) => {
128
128
  };
129
129
 
130
130
  export default {
131
- title: 'ui/react-ui-table/Table',
131
+ title: 'ui/react-ui-table/deprecated/Table',
132
132
  component: MinimalTable,
133
133
  args: {
134
134
  header: true,
@@ -29,26 +29,32 @@ export const ColumnActionsMenu = ({ model }: ColumnActionsMenuProps) => {
29
29
  <DropdownMenu.Content>
30
30
  <DropdownMenu.Viewport>
31
31
  {(!isCurrentColumnSorted || currentSort?.direction === 'asc') && (
32
- <DropdownMenu.Item onClick={() => model.setSort(state.fieldId, 'desc')}>
32
+ <DropdownMenu.Item
33
+ data-testid='column-sort-descending'
34
+ onClick={() => model.setSort(state.fieldId, 'desc')}
35
+ >
33
36
  {t('column action sort descending')}
34
37
  </DropdownMenu.Item>
35
38
  )}
36
39
  {(!isCurrentColumnSorted || currentSort?.direction === 'desc') && (
37
- <DropdownMenu.Item onClick={() => model.setSort(state.fieldId, 'asc')}>
40
+ <DropdownMenu.Item
41
+ data-testid='column-sort-ascending'
42
+ onClick={() => model.setSort(state.fieldId, 'asc')}
43
+ >
38
44
  {t('column action sort ascending')}
39
45
  </DropdownMenu.Item>
40
46
  )}
41
47
  {isCurrentColumnSorted && (
42
- <DropdownMenu.Item onClick={() => model.clearSort()}>
48
+ <DropdownMenu.Item data-testid='column-clear-sort' onClick={() => model.clearSort()}>
43
49
  {t('column action clear sorting')}
44
50
  </DropdownMenu.Item>
45
51
  )}
46
52
  {model.getColumnCount() > 1 && (
47
- <DropdownMenu.Item onClick={() => model.deleteColumn(state.fieldId)}>
53
+ <DropdownMenu.Item data-testid='column-delete' onClick={() => model.deleteColumn(state.fieldId)}>
48
54
  {t('column action delete')}
49
55
  </DropdownMenu.Item>
50
56
  )}
51
- <DropdownMenu.Item onClick={() => model.modalController.openColumnSettings()}>
57
+ <DropdownMenu.Item data-testid='column-settings' onClick={() => model.modalController.openColumnSettings()}>
52
58
  {t('column action settings')}
53
59
  </DropdownMenu.Item>
54
60
  </DropdownMenu.Viewport>
@@ -25,7 +25,7 @@ export const RowActionsMenu = ({ model }: RowActionsMenuProps) => {
25
25
  <DropdownMenu.VirtualTrigger virtualRef={model.modalController.trigger} />
26
26
  <DropdownMenu.Content>
27
27
  <DropdownMenu.Viewport>
28
- <DropdownMenu.Item onClick={() => model.deleteRow(state.rowIndex)}>
28
+ <DropdownMenu.Item data-testid='row-menu-delete' onClick={() => model.deleteRow(state.rowIndex)}>
29
29
  {t(hasSelection ? 'bulk delete row label' : 'delete row label')}
30
30
  </DropdownMenu.Item>
31
31
  </DropdownMenu.Viewport>
@@ -27,6 +27,8 @@ import { initializeTable } from '../../util';
27
27
  import { Toolbar } from '../Toolbar';
28
28
  import { createItems, createTable, type SimulatorProps, useSimulator } from '../testing';
29
29
 
30
+ faker.seed(0);
31
+
30
32
  //
31
33
  // Story components.
32
34
  //
@@ -189,7 +191,7 @@ const TablePerformanceStory = (props: StoryProps) => {
189
191
  //
190
192
 
191
193
  const meta: Meta<StoryProps> = {
192
- title: 'plugins/plugin-table/Table',
194
+ title: 'ui/react-ui-table/Table',
193
195
  component: Table.Main as any,
194
196
  render: DefaultStory,
195
197
  parameters: { translations },
@@ -200,8 +202,8 @@ const meta: Meta<StoryProps> = {
200
202
  createSpace: true,
201
203
  onSpaceCreated: async ({ space }) => {
202
204
  const table = space.db.add(create(TableType, {}));
203
- const schema = initializeTable({ space, table });
204
- Array.from({ length: 30 }).map(() => {
205
+ const schema = initializeTable({ space, table, initialRow: false });
206
+ Array.from({ length: 10 }).map(() => {
205
207
  return space.db.add(
206
208
  create(schema, {
207
209
  name: faker.person.fullName(),
@@ -48,8 +48,8 @@ const TableRoot = ({ children, role }: TableRootProps) => {
48
48
  className={mx(
49
49
  'relative border-bs !border-separator',
50
50
  role === 'section'
51
- ? 'attention-surface overflow-hidden [&_.dx-grid]:max-is-max'
52
- : 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:max-is-max [&_.dx-grid]:bs-0 [&_.dx-grid]:max-bs-max',
51
+ ? 'attention-surface overflow-hidden [&_.dx-grid]:max-is-[--dx-grid-content-inline-size]'
52
+ : 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:bs-0 [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
53
53
  )}
54
54
  >
55
55
  {children}
@@ -0,0 +1,60 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Page } from '@playwright/test';
6
+
7
+ import { DxGridManager } from '@dxos/lit-grid/testing';
8
+
9
+ const TABLE_SELECTORS = {
10
+ columnSettingsButton: 'table-column-settings-button',
11
+ rowMenuButton: 'table-row-menu-button',
12
+ newColumnButton: 'table-new-column-button',
13
+ selection: 'table-selection',
14
+ columnSort: {
15
+ ascending: 'column-sort-ascending',
16
+ descending: 'column-sort-descending',
17
+ },
18
+ columnDelete: 'column-delete',
19
+ rowDelete: 'row-menu-delete',
20
+ } as const;
21
+
22
+ export class TableManager {
23
+ private readonly _grid: DxGridManager;
24
+
25
+ constructor(private readonly page: Page) {
26
+ this._grid = new DxGridManager(page);
27
+ }
28
+
29
+ public get grid(): DxGridManager {
30
+ return this._grid;
31
+ }
32
+
33
+ public async deleteColumn(index: number): Promise<void> {
34
+ await this.page.getByTestId(TABLE_SELECTORS.columnSettingsButton).nth(index).click();
35
+ await this.page.getByTestId(TABLE_SELECTORS.columnDelete).click();
36
+ }
37
+
38
+ public async deleteRow(index = 0): Promise<void> {
39
+ await this.page.getByTestId(TABLE_SELECTORS.rowMenuButton).nth(index).click();
40
+ await this.page.getByTestId(TABLE_SELECTORS.rowDelete).click();
41
+ }
42
+
43
+ public async toggleSelectAll(): Promise<void> {
44
+ await this.page.getByTestId(TABLE_SELECTORS.selection).nth(0).click();
45
+ }
46
+
47
+ public async sortColumn(index: number, direction: 'ascending' | 'descending'): Promise<void> {
48
+ await this.page.getByTestId(TABLE_SELECTORS.columnSettingsButton).nth(index).click();
49
+ await this.page.getByTestId(TABLE_SELECTORS.columnSort[direction]).click();
50
+ }
51
+
52
+ public async addColumn({ label, format }: { label: string; format: 'number' }): Promise<void> {
53
+ await this.page.getByTestId(TABLE_SELECTORS.newColumnButton).click();
54
+ await this.page.getByRole('combobox').click();
55
+ await this.page.getByLabel(`format ${format}`).click();
56
+ await this.page.getByPlaceholder('Property label.').click();
57
+ await this.page.getByPlaceholder('Property label.').fill(label);
58
+ await this.page.getByRole('button', { name: 'button save' }).click();
59
+ }
60
+ }
@@ -0,0 +1,18 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { nxE2EPreset } from '@nx/playwright/preset';
6
+ import { defineConfig } from '@playwright/test';
7
+
8
+ import { e2ePreset } from '@dxos/test-utils/playwright';
9
+
10
+ export default defineConfig({
11
+ ...nxE2EPreset(__filename, { testDir: __dirname }),
12
+ ...e2ePreset(__dirname),
13
+ webServer: {
14
+ command: 'pnpm -w nx storybook-e2e stories',
15
+ port: 9009,
16
+ reuseExistingServer: !process.env.CI,
17
+ },
18
+ });
@@ -0,0 +1,105 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { expect, test } from '@playwright/test';
6
+
7
+ import { setupPage } from '@dxos/test-utils/playwright';
8
+
9
+ import { TableManager } from './TableManager';
10
+
11
+ const storyId = 'ui-react-ui-table-table';
12
+ const storyUrl = `http://localhost:9009/iframe.html?id=${storyId}&viewMode=story`;
13
+
14
+ // NOTE(ZaymonFC): This test suite relies on the faker seed being set to 0 in the story.
15
+ test.describe('Table', () => {
16
+ test('Loads', async ({ browser }) => {
17
+ const { page } = await setupPage(browser, { url: storyUrl });
18
+ const table = new TableManager(page);
19
+
20
+ // (name, email, salary, manager) * 10 rows.
21
+ await table.grid.ready();
22
+ await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(40);
23
+ await page.close();
24
+ });
25
+
26
+ test('sort', async ({ browser }) => {
27
+ const { page } = await setupPage(browser, { url: storyUrl });
28
+ const table = new TableManager(page);
29
+
30
+ await table.grid.ready();
31
+ await table.sortColumn(0, 'descending');
32
+ await expect(table.grid.cell(0, 0, 'grid')).toHaveText('Uwe Øvergård');
33
+ await expect(table.grid.cell(0, 9, 'grid')).toContainText('Anita');
34
+
35
+ await table.sortColumn(0, 'ascending');
36
+ await expect(table.grid.cell(0, 0, 'grid')).toContainText('Anita');
37
+ await expect(table.grid.cell(0, 9, 'grid')).toHaveText('Uwe Øvergård');
38
+ await page.close();
39
+ });
40
+
41
+ test('delete row', async ({ browser }) => {
42
+ const { page } = await setupPage(browser, { url: storyUrl });
43
+ const table = new TableManager(page);
44
+
45
+ await table.grid.ready();
46
+ await table.deleteRow(0);
47
+ await expect(page.getByRole('gridcell', { name: 'Anita Mayer' })).toHaveCount(0);
48
+ await page.close();
49
+ });
50
+
51
+ test('delete row--select all', async ({ browser }) => {
52
+ const { page } = await setupPage(browser, { url: storyUrl });
53
+ const table = new TableManager(page);
54
+
55
+ await table.grid.ready();
56
+ await table.toggleSelectAll();
57
+
58
+ // Delete action affects all selected rows.
59
+ await table.deleteRow(0);
60
+
61
+ await expect(page.getByRole('gridcell', { name: 'Anita Mayer' })).toHaveCount(0);
62
+ await expect(page.getByRole('gridcell', { name: 'Phonthip Sigurjónsson' })).toHaveCount(0);
63
+ await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(0);
64
+ await page.close();
65
+ });
66
+
67
+ test('delete column', async ({ browser }) => {
68
+ const { page } = await setupPage(browser, { url: storyUrl });
69
+ const table = new TableManager(page);
70
+
71
+ await table.grid.ready();
72
+ await table.deleteColumn(0);
73
+ await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(30);
74
+
75
+ await table.deleteColumn(0);
76
+ await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(20);
77
+
78
+ await table.deleteColumn(0);
79
+ await expect(table.grid.cellsWithinPlane('grid')).toHaveCount(10);
80
+
81
+ await page.getByTestId('table-column-settings-button').nth(0).click();
82
+
83
+ // Ensure that the delete menu item is not visible when there is only one column left.
84
+ await expect(page.getByTestId('column-delete')).toHaveCount(0);
85
+ await page.close();
86
+ });
87
+
88
+ // Rest of add column test remains the same as it's a more complex flow
89
+ test('add column', async ({ browser }) => {
90
+ const { page } = await setupPage(browser, { url: storyUrl });
91
+ const table = new TableManager(page);
92
+
93
+ await table.grid.ready();
94
+ const newColumnLabel = 'TEST LABEL';
95
+
96
+ await table.addColumn({ label: newColumnLabel, format: 'number' });
97
+
98
+ // Delete first two columns to get the new column into view
99
+ await table.deleteColumn(0);
100
+ await table.deleteColumn(0);
101
+
102
+ await expect(page.getByRole('gridcell', { name: newColumnLabel })).toBeVisible();
103
+ await page.close();
104
+ });
105
+ });
@@ -6,8 +6,17 @@ import { ref, S, TypedObject } from '@dxos/echo-schema';
6
6
  import { ThreadType } from '@dxos/plugin-space';
7
7
  import { ViewType } from '@dxos/schema';
8
8
 
9
- export class TableType extends TypedObject({ typename: 'dxos.org/type/Table', version: '0.1.0' })({
9
+ // TODO(burdon): Factor out echo dependency and define pattern for react-ui-xxx.
10
+ const TableSchema = S.Struct({
11
+ id: S.String,
10
12
  name: S.optional(S.String),
11
13
  view: S.optional(ref(ViewType)),
12
- threads: S.optional(S.mutable(S.Array(ref(ThreadType)))),
13
- }) {}
14
+ threads: S.optional(S.Array(ref(ThreadType))),
15
+ });
16
+
17
+ type _TableType = S.Schema.Type<typeof TableSchema>;
18
+
19
+ export class TableType extends TypedObject({
20
+ typename: 'dxos.org/type/Table',
21
+ version: '0.1.0',
22
+ })(TableSchema.fields) {}
package/src/util/grid.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type DxGridCellIndex } from '@dxos/react-ui-grid';
5
+ import { type DxGridPlaneCellIndex } from '@dxos/react-ui-grid';
6
6
 
7
7
  // TODO(burdon): Factor out to grid.
8
8
  export type GridCell = { col: number; row: number };
@@ -10,14 +10,14 @@ export type GridCell = { col: number; row: number };
10
10
  /**
11
11
  * Generates a string key for a cell based on its column and row indices.
12
12
  */
13
- export const fromGridCell = ({ col, row }: GridCell): DxGridCellIndex => {
13
+ export const fromGridCell = ({ col, row }: GridCell): DxGridPlaneCellIndex => {
14
14
  return `${col},${row}` as const; // TODO(burdon): ":" delim.
15
15
  };
16
16
 
17
17
  /**
18
18
  * Converts a cell key string back into column and row coordinates.
19
19
  */
20
- export const toGridCell = (cell: DxGridCellIndex): GridCell => {
20
+ export const toGridCell = (cell: DxGridPlaneCellIndex): GridCell => {
21
21
  const [col, row] = cell.split(',').map(Number);
22
22
  return { row, col };
23
23
  };
@@ -20,29 +20,34 @@ const createButtonHtml = ({
20
20
  button,
21
21
  value = '',
22
22
  disabled = false,
23
+ testId,
23
24
  }: {
24
25
  button: TableButton;
25
26
  value?: string;
26
27
  disabled?: boolean;
28
+ testId: string;
27
29
  }): string => {
28
30
  const buttonClasses = 'ch-button is-6 pli-0.5 min-bs-0 absolute inset-block-1 inline-end-2' as const;
29
- return `<button class="${buttonClasses}" ${TABLE_ATTRS[button]}="${value}" ${disabled ? 'disabled' : ''}><svg><use href="/icons.svg#${ICONS[button]}"/></svg></button>`;
31
+ return `<button class="${buttonClasses}" ${TABLE_ATTRS[button]}="${value}" data-testid="${testId}" ${disabled ? 'disabled' : ''}><svg><use href="/icons.svg#${ICONS[button]}"/></svg></button>`;
30
32
  };
31
33
 
32
34
  export const tableButtons = {
33
35
  columnSettings: {
34
36
  attr: TABLE_ATTRS.columnSettings,
35
37
  icon: ICONS.columnSettings,
36
- render: ({ fieldId }: { fieldId: string }) => createButtonHtml({ button: 'columnSettings', value: fieldId }),
38
+ render: ({ fieldId }: { fieldId: string }) =>
39
+ createButtonHtml({ button: 'columnSettings', value: fieldId, testId: 'table-column-settings-button' }),
37
40
  },
38
41
  rowMenu: {
39
42
  attr: TABLE_ATTRS.rowMenu,
40
43
  icon: ICONS.rowMenu,
41
- render: ({ rowIndex }: { rowIndex: number }) => createButtonHtml({ button: 'rowMenu', value: rowIndex.toString() }),
44
+ render: ({ rowIndex }: { rowIndex: number }) =>
45
+ createButtonHtml({ button: 'rowMenu', value: rowIndex.toString(), testId: 'table-row-menu-button' }),
42
46
  },
43
47
  addColumn: {
44
48
  attr: TABLE_ATTRS.newColumn,
45
49
  icon: ICONS.newColumn,
46
- render: ({ disabled }: { disabled?: boolean } = {}) => createButtonHtml({ button: 'newColumn', disabled }),
50
+ render: ({ disabled }: { disabled?: boolean } = {}) =>
51
+ createButtonHtml({ button: 'newColumn', disabled, testId: 'table-new-column-button' }),
47
52
  },
48
53
  } as const;
@@ -16,7 +16,7 @@ export const renderCheckbox = ({
16
16
  }: RenderCheckboxProps): string => {
17
17
  const baseClasses = 'absolute inset-block-[6px] inline-end-[6px] ch-checkbox' as const;
18
18
  const { attributes } = tableControls.checkbox;
19
- return `<input type="checkbox" class="${baseClasses}" ${attributes.checkbox}="${rowIndex}" ${header ? attributes.header : ''} ${checked ? 'checked' : ''} ${disabled ? 'disabled' : ''}/>`;
19
+ return `<input type="checkbox" class="${baseClasses}" ${attributes.checkbox}="${rowIndex}" ${header ? attributes.header : ''} ${checked ? 'checked' : ''} ${disabled ? 'disabled' : ''} data-testid="table-selection"/>`;
20
20
  };
21
21
 
22
22
  export const tableControls = {
package/src/util/table.ts CHANGED
@@ -20,9 +20,15 @@ import { createFieldId, createView, ViewProjection } from '@dxos/schema';
20
20
 
21
21
  import { type TableType } from '../types';
22
22
 
23
+ type InitialiseTableProps = {
24
+ space: Space;
25
+ table: TableType;
26
+ initialRow?: boolean;
27
+ };
28
+
23
29
  // TODO(burdon): Pass in type.
24
30
  // TODO(burdon): User should determine typename.
25
- export const initializeTable = ({ space, table }: { space: Space; table: TableType }): MutableSchema => {
31
+ export const initializeTable = ({ space, table, initialRow = true }: InitialiseTableProps): MutableSchema => {
26
32
  log.info('initializeTable', { table });
27
33
 
28
34
  const ContactSchema = TypedObject({
@@ -70,8 +76,10 @@ export const initializeTable = ({ space, table }: { space: Space; table: TableTy
70
76
  },
71
77
  });
72
78
 
73
- // TODO(burdon): Last (first) row should not be in db and should be managed by the model.
74
- space.db.add(create(contactSchema, {}));
79
+ if (initialRow) {
80
+ // TODO(burdon): Last (first) row should not be in db and should be managed by the model.
81
+ space.db.add(create(contactSchema, {}));
82
+ }
75
83
 
76
84
  return contactSchema;
77
85
  };
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types/table.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { ref, S, TypedObject } from '@dxos/echo-schema';\nimport { ThreadType } from '@dxos/plugin-space';\nimport { ViewType } from '@dxos/schema';\n\nexport class TableType extends TypedObject({ typename: 'dxos.org/type/Table', version: '0.1.0' })({\n name: S.optional(S.String),\n view: S.optional(ref(ViewType)),\n threads: S.optional(S.mutable(S.Array(ref(ThreadType)))),\n}) {}\n"],
5
- "mappings": ";AAIA,SAASA,KAAKC,GAAGC,mBAAmB;AACpC,SAASC,kBAAkB;AAC3B,SAASC,gBAAgB;AAElB,IAAMC,YAAN,cAAwBC,YAAY;EAAEC,UAAU;EAAuBC,SAAS;AAAQ,CAAA,EAAG;EAChGC,MAAMC,EAAEC,SAASD,EAAEE,MAAM;EACzBC,MAAMH,EAAEC,SAASG,IAAIC,QAAAA,CAAAA;EACrBC,SAASN,EAAEC,SAASD,EAAEO,QAAQP,EAAEQ,MAAMJ,IAAIK,UAAAA,CAAAA,CAAAA,CAAAA;AAC5C,CAAA,EAAA;AAAI;",
6
- "names": ["ref", "S", "TypedObject", "ThreadType", "ViewType", "TableType", "TypedObject", "typename", "version", "name", "S", "optional", "String", "view", "ref", "ViewType", "threads", "mutable", "Array", "ThreadType"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types/table.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { ref, S, TypedObject } from '@dxos/echo-schema';\nimport { ThreadType } from '@dxos/plugin-space';\nimport { ViewType } from '@dxos/schema';\n\nexport class TableType extends TypedObject({ typename: 'dxos.org/type/Table', version: '0.1.0' })({\n name: S.optional(S.String),\n view: S.optional(ref(ViewType)),\n threads: S.optional(S.mutable(S.Array(ref(ThreadType)))),\n}) {}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;AAIA,yBAAoC;AACpC,0BAA2B;AAC3B,oBAAyB;AAElB,IAAMA,YAAN,kBAAwBC,gCAAY;EAAEC,UAAU;EAAuBC,SAAS;AAAQ,CAAA,EAAG;EAChGC,MAAMC,qBAAEC,SAASD,qBAAEE,MAAM;EACzBC,MAAMH,qBAAEC,aAASG,wBAAIC,sBAAAA,CAAAA;EACrBC,SAASN,qBAAEC,SAASD,qBAAEO,QAAQP,qBAAEQ,UAAMJ,wBAAIK,8BAAAA,CAAAA,CAAAA,CAAAA;AAC5C,CAAA,EAAA;AAAI;",
6
- "names": ["TableType", "TypedObject", "typename", "version", "name", "S", "optional", "String", "view", "ref", "ViewType", "threads", "mutable", "Array", "ThreadType"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types/table.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { ref, S, TypedObject } from '@dxos/echo-schema';\nimport { ThreadType } from '@dxos/plugin-space';\nimport { ViewType } from '@dxos/schema';\n\nexport class TableType extends TypedObject({ typename: 'dxos.org/type/Table', version: '0.1.0' })({\n name: S.optional(S.String),\n view: S.optional(ref(ViewType)),\n threads: S.optional(S.mutable(S.Array(ref(ThreadType)))),\n}) {}\n"],
5
- "mappings": ";;;AAIA,SAASA,KAAKC,GAAGC,mBAAmB;AACpC,SAASC,kBAAkB;AAC3B,SAASC,gBAAgB;AAElB,IAAMC,YAAN,cAAwBC,YAAY;EAAEC,UAAU;EAAuBC,SAAS;AAAQ,CAAA,EAAG;EAChGC,MAAMC,EAAEC,SAASD,EAAEE,MAAM;EACzBC,MAAMH,EAAEC,SAASG,IAAIC,QAAAA,CAAAA;EACrBC,SAASN,EAAEC,SAASD,EAAEO,QAAQP,EAAEQ,MAAMJ,IAAIK,UAAAA,CAAAA,CAAAA,CAAAA;AAC5C,CAAA,EAAA;AAAI;",
6
- "names": ["ref", "S", "TypedObject", "ThreadType", "ViewType", "TableType", "TypedObject", "typename", "version", "name", "S", "optional", "String", "view", "ref", "ViewType", "threads", "mutable", "Array", "ThreadType"]
7
- }