@dxos/react-ui-table 0.8.4-main.2e9d522 → 0.8.4-main.3c1ae3b

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 (144) hide show
  1. package/dist/lib/browser/chunk-CJQ6T4LL.mjs +77 -0
  2. package/dist/lib/browser/chunk-CJQ6T4LL.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-5XLSHLOG.mjs → chunk-V7A7ZA5E.mjs} +576 -544
  4. package/dist/lib/browser/chunk-V7A7ZA5E.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +694 -760
  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 +16 -27
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/browser/types/index.mjs +3 -3
  11. package/dist/lib/node-esm/{chunk-Y66YLSTC.mjs → chunk-P73ALX3N.mjs} +576 -544
  12. package/dist/lib/node-esm/chunk-P73ALX3N.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs +78 -0
  14. package/dist/lib/node-esm/chunk-T6PXZQ6W.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +694 -760
  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 +16 -27
  19. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/types/index.mjs +3 -3
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +2 -3
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +3 -3
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.d.ts +5 -6
  26. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +37 -3
  28. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Relations.stories.d.ts +111 -0
  30. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  31. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  32. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/Table.d.ts +13 -9
  34. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Table.stories.d.ts +108 -4
  36. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  38. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +14 -9
  40. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  41. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +11 -18
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  46. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  47. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
  48. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  49. package/dist/types/src/components/testing.d.ts +3 -5
  50. package/dist/types/src/components/testing.d.ts.map +1 -1
  51. package/dist/types/src/hooks/index.d.ts +2 -1
  52. package/dist/types/src/hooks/index.d.ts.map +1 -1
  53. package/dist/types/src/hooks/useAddRow.d.ts +3 -2
  54. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
  56. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  57. package/dist/types/src/hooks/useTableModel.d.ts +5 -6
  58. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  59. package/dist/types/src/model/modal-controller.d.ts +0 -7
  60. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  61. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  62. package/dist/types/src/model/table-model.d.ts +14 -14
  63. package/dist/types/src/model/table-model.d.ts.map +1 -1
  64. package/dist/types/src/model/table-presentation.d.ts +2 -1
  65. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  66. package/dist/types/src/model/table-sorting.d.ts +9 -6
  67. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  68. package/dist/types/src/playwright/TableManager.d.ts +1 -1
  69. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  70. package/dist/types/src/testing/useTestTableModel.d.ts +19 -34
  71. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  72. package/dist/types/src/translations.d.ts +2 -2
  73. package/dist/types/src/types/Table.d.ts +28 -0
  74. package/dist/types/src/types/Table.d.ts.map +1 -0
  75. package/dist/types/src/types/index.d.ts +1 -1
  76. package/dist/types/src/types/index.d.ts.map +1 -1
  77. package/dist/types/src/util/dynamic-table.d.ts +10 -11
  78. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  79. package/dist/types/src/util/index.d.ts +0 -1
  80. package/dist/types/src/util/index.d.ts.map +1 -1
  81. package/dist/types/src/util/schema.d.ts +1 -1
  82. package/dist/types/src/util/schema.d.ts.map +1 -1
  83. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  84. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  85. package/dist/types/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +57 -51
  87. package/src/components/Table/ColumnActionsMenu.tsx +5 -2
  88. package/src/components/Table/ColumnSettings.tsx +3 -4
  89. package/src/components/Table/DynamicTable.stories.tsx +21 -19
  90. package/src/components/Table/DynamicTable.tsx +24 -18
  91. package/src/components/Table/Relations.stories.tsx +233 -0
  92. package/src/components/Table/RowActionsMenu.tsx +23 -15
  93. package/src/components/Table/Table.stories.tsx +104 -99
  94. package/src/components/Table/Table.tsx +338 -295
  95. package/src/components/TableCellEditor/CellValidationMessage.tsx +2 -2
  96. package/src/components/TableCellEditor/FormCellEditor.tsx +143 -39
  97. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +30 -66
  98. package/src/components/TableCellEditor/TableCellEditor.tsx +86 -236
  99. package/src/components/TableCellEditor/index.ts +1 -0
  100. package/src/components/TableToolbar/TableToolbar.tsx +34 -24
  101. package/src/components/testing.ts +4 -4
  102. package/src/hooks/index.ts +2 -1
  103. package/src/hooks/useAddRow.ts +7 -5
  104. package/src/hooks/useProjectionModel.ts +19 -0
  105. package/src/hooks/useTableModel.ts +13 -15
  106. package/src/model/modal-controller.ts +1 -29
  107. package/src/model/selection-model.ts +3 -2
  108. package/src/model/table-model.test.ts +21 -12
  109. package/src/model/table-model.ts +64 -58
  110. package/src/model/table-presentation.test.ts +25 -23
  111. package/src/model/table-presentation.ts +82 -41
  112. package/src/model/table-sorting.ts +32 -14
  113. package/src/playwright/TableManager.ts +5 -5
  114. package/src/playwright/smoke.spec.ts +132 -38
  115. package/src/testing/useTestTableModel.ts +32 -24
  116. package/src/translations.ts +2 -2
  117. package/src/types/Table.ts +90 -0
  118. package/src/types/index.ts +1 -1
  119. package/src/util/dynamic-table.ts +21 -27
  120. package/src/util/index.ts +0 -1
  121. package/src/util/schema.ts +4 -4
  122. package/src/util/table-buttons.ts +4 -3
  123. package/src/util/table-controls.ts +4 -1
  124. package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
  125. package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
  126. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
  127. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
  128. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
  129. package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +0 -7
  130. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  131. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  132. package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
  133. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  134. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  135. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  136. package/dist/types/src/types/schema.d.ts +0 -8
  137. package/dist/types/src/types/schema.d.ts.map +0 -1
  138. package/dist/types/src/util/table.d.ts +0 -11
  139. package/dist/types/src/util/table.d.ts.map +0 -1
  140. package/src/components/Table/CreateRefPanel.tsx +0 -86
  141. package/src/components/Table/relations.stories.tsx +0 -116
  142. package/src/components/TableCellEditor/extension.ts +0 -61
  143. package/src/types/schema.ts +0 -16
  144. package/src/util/table.ts +0 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-table",
3
- "version": "0.8.4-main.2e9d522",
3
+ "version": "0.8.4-main.3c1ae3b",
4
4
  "description": "A table component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -10,21 +10,25 @@
10
10
  "type": "module",
11
11
  "exports": {
12
12
  "./deprecated": {
13
+ "source": "./src/deprecated/index.ts",
13
14
  "types": "./dist/types/src/deprecated/index.d.ts",
14
15
  "browser": "./dist/lib/browser/deprecated/index.mjs",
15
16
  "node": "./dist/lib/node-esm/deprecated/index.mjs"
16
17
  },
17
18
  ".": {
19
+ "source": "./src/index.ts",
18
20
  "types": "./dist/types/src/index.d.ts",
19
21
  "browser": "./dist/lib/browser/index.mjs",
20
22
  "node": "./dist/lib/node-esm/index.mjs"
21
23
  },
22
24
  "./testing": {
25
+ "source": "./src/testing/index.ts",
23
26
  "types": "./dist/types/src/testing/index.d.ts",
24
27
  "browser": "./dist/lib/browser/testing/index.mjs",
25
28
  "node": "./dist/lib/node-esm/testing/index.mjs"
26
29
  },
27
30
  "./types": {
31
+ "source": "./src/types/index.ts",
28
32
  "types": "./dist/types/src/types/index.d.ts",
29
33
  "browser": "./dist/lib/browser/types/index.mjs",
30
34
  "node": "./dist/lib/node-esm/types/index.mjs"
@@ -49,72 +53,74 @@
49
53
  "src"
50
54
  ],
51
55
  "dependencies": {
52
- "@codemirror/autocomplete": "^6.18.1",
53
- "@codemirror/state": "^6.4.1",
54
- "@codemirror/view": "^6.34.1",
56
+ "@codemirror/autocomplete": "^6.19.0",
57
+ "@codemirror/state": "^6.5.2",
58
+ "@codemirror/view": "^6.38.4",
55
59
  "@fluentui/react-tabster": "^9.24.2",
56
60
  "@preact-signals/safe-react": "^0.9.0",
57
- "@preact/signals-core": "^1.9.0",
61
+ "@preact/signals-core": "^1.12.1",
58
62
  "@radix-ui/react-context": "1.1.1",
59
63
  "@radix-ui/react-primitive": "2.0.2",
60
64
  "@radix-ui/react-slot": "1.1.2",
61
65
  "@radix-ui/react-use-controllable-state": "1.1.0",
62
66
  "date-fns": "^3.3.1",
63
67
  "lodash.orderby": "^4.6.0",
64
- "@dxos/context": "0.8.4-main.2e9d522",
65
- "@dxos/echo": "0.8.4-main.2e9d522",
66
- "@dxos/debug": "0.8.4-main.2e9d522",
67
- "@dxos/echo-db": "0.8.4-main.2e9d522",
68
- "@dxos/echo-schema": "0.8.4-main.2e9d522",
69
- "@dxos/keys": "0.8.4-main.2e9d522",
70
- "@dxos/invariant": "0.8.4-main.2e9d522",
71
- "@dxos/lit-grid": "0.8.4-main.2e9d522",
72
- "@dxos/live-object": "0.8.4-main.2e9d522",
73
- "@dxos/log": "0.8.4-main.2e9d522",
74
- "@dxos/effect": "0.8.4-main.2e9d522",
75
- "@dxos/plugin-search": "0.8.4-main.2e9d522",
76
- "@dxos/random": "0.8.4-main.2e9d522",
77
- "@dxos/react-client": "0.8.4-main.2e9d522",
78
- "@dxos/react-ui-attention": "0.8.4-main.2e9d522",
79
- "@dxos/react-ui-editor": "0.8.4-main.2e9d522",
80
- "@dxos/react-ui-form": "0.8.4-main.2e9d522",
81
- "@dxos/react-ui-grid": "0.8.4-main.2e9d522",
82
- "@dxos/react-ui-menu": "0.8.4-main.2e9d522",
83
- "@dxos/react-ui-stack": "0.8.4-main.2e9d522",
84
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.2e9d522",
85
- "@dxos/react-ui-searchlist": "0.8.4-main.2e9d522",
86
- "@dxos/react-ui-tag-picker": "0.8.4-main.2e9d522",
87
- "@dxos/react-ui-types": "0.8.4-main.2e9d522",
88
- "@dxos/schema": "0.8.4-main.2e9d522",
89
- "@dxos/util": "0.8.4-main.2e9d522"
68
+ "@dxos/async": "0.8.4-main.3c1ae3b",
69
+ "@dxos/context": "0.8.4-main.3c1ae3b",
70
+ "@dxos/debug": "0.8.4-main.3c1ae3b",
71
+ "@dxos/echo": "0.8.4-main.3c1ae3b",
72
+ "@dxos/echo-db": "0.8.4-main.3c1ae3b",
73
+ "@dxos/effect": "0.8.4-main.3c1ae3b",
74
+ "@dxos/invariant": "0.8.4-main.3c1ae3b",
75
+ "@dxos/keys": "0.8.4-main.3c1ae3b",
76
+ "@dxos/lit-grid": "0.8.4-main.3c1ae3b",
77
+ "@dxos/log": "0.8.4-main.3c1ae3b",
78
+ "@dxos/live-object": "0.8.4-main.3c1ae3b",
79
+ "@dxos/random": "0.8.4-main.3c1ae3b",
80
+ "@dxos/plugin-search": "0.8.4-main.3c1ae3b",
81
+ "@dxos/react-client": "0.8.4-main.3c1ae3b",
82
+ "@dxos/react-ui-attention": "0.8.4-main.3c1ae3b",
83
+ "@dxos/react-ui-components": "0.8.4-main.3c1ae3b",
84
+ "@dxos/react-ui-editor": "0.8.4-main.3c1ae3b",
85
+ "@dxos/react-ui-form": "0.8.4-main.3c1ae3b",
86
+ "@dxos/react-ui-grid": "0.8.4-main.3c1ae3b",
87
+ "@dxos/react-ui-menu": "0.8.4-main.3c1ae3b",
88
+ "@dxos/react-ui-searchlist": "0.8.4-main.3c1ae3b",
89
+ "@dxos/react-ui-stack": "0.8.4-main.3c1ae3b",
90
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.3c1ae3b",
91
+ "@dxos/react-ui-types": "0.8.4-main.3c1ae3b",
92
+ "@dxos/schema": "0.8.4-main.3c1ae3b",
93
+ "@dxos/types": "0.8.4-main.3c1ae3b",
94
+ "@dxos/util": "0.8.4-main.3c1ae3b"
90
95
  },
91
96
  "devDependencies": {
92
- "@effect-rx/rx-react": "0.38.0",
93
- "@effect/platform": "0.89.0",
97
+ "@effect-atom/atom-react": "^0.3.4",
98
+ "@effect/platform": "0.92.1",
94
99
  "@types/lodash.orderby": "^4.6.9",
95
- "@types/react": "~18.2.0",
96
- "@types/react-dom": "~18.2.0",
97
- "effect": "3.17.0",
100
+ "@types/react": "~19.2.2",
101
+ "@types/react-dom": "~19.2.2",
102
+ "effect": "3.18.3",
98
103
  "fast-check": "^3.19.0",
99
- "react": "~18.2.0",
100
- "react-dom": "~18.2.0",
101
- "vite": "5.4.7",
102
- "@dxos/echo-signals": "0.8.4-main.2e9d522",
103
- "@dxos/plugin-search": "0.8.4-main.2e9d522",
104
- "@dxos/random": "0.8.4-main.2e9d522",
105
- "@dxos/react-ui": "0.8.4-main.2e9d522",
106
- "@dxos/react-ui-theme": "0.8.4-main.2e9d522",
107
- "@dxos/storybook-utils": "0.8.4-main.2e9d522",
108
- "@dxos/test-utils": "0.8.4-main.2e9d522"
104
+ "react": "~19.2.0",
105
+ "react-dom": "~19.2.0",
106
+ "vite": "7.1.9",
107
+ "@dxos/echo-signals": "0.8.4-main.3c1ae3b",
108
+ "@dxos/lit-ui": "0.8.4-main.3c1ae3b",
109
+ "@dxos/plugin-search": "0.8.4-main.3c1ae3b",
110
+ "@dxos/react-ui": "0.8.4-main.3c1ae3b",
111
+ "@dxos/random": "0.8.4-main.3c1ae3b",
112
+ "@dxos/react-ui-theme": "0.8.4-main.3c1ae3b",
113
+ "@dxos/storybook-utils": "0.8.4-main.3c1ae3b",
114
+ "@dxos/test-utils": "0.8.4-main.3c1ae3b"
109
115
  },
110
116
  "peerDependencies": {
111
- "@effect-rx/rx-react": "^0.34.1",
117
+ "@effect-atom/atom-react": "^0.3.4",
112
118
  "@effect/platform": "^0.80.12",
113
119
  "effect": "^3.13.3",
114
- "react": "~18.2.0",
115
- "react-dom": "~18.2.0",
116
- "@dxos/react-ui": "0.8.4-main.2e9d522",
117
- "@dxos/react-ui-theme": "0.8.4-main.2e9d522"
120
+ "react": "^19.0.0",
121
+ "react-dom": "^19.0.0",
122
+ "@dxos/react-ui": "0.8.4-main.3c1ae3b",
123
+ "@dxos/react-ui-theme": "0.8.4-main.3c1ae3b"
118
124
  },
119
125
  "publishConfig": {
120
126
  "access": "public"
@@ -6,10 +6,13 @@ import React from 'react';
6
6
 
7
7
  import { DropdownMenu, useTranslation } from '@dxos/react-ui';
8
8
 
9
- import { type TableModel, type ModalController } from '../../model';
9
+ import { type ModalController, type TableModel } from '../../model';
10
10
  import { translationKey } from '../../translations';
11
11
 
12
- type ColumnActionsMenuProps = { model: TableModel; modals: ModalController };
12
+ export type ColumnActionsMenuProps = {
13
+ model: TableModel;
14
+ modals: ModalController;
15
+ };
13
16
 
14
17
  export const ColumnActionsMenu = ({ model, modals }: ColumnActionsMenuProps) => {
15
18
  const { t } = useTranslation(translationKey);
@@ -4,16 +4,15 @@
4
4
 
5
5
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
6
 
7
- // TODO(burdon): Remove dependency on echo-db.
8
- import { type SchemaRegistry } from '@dxos/echo-db';
7
+ import { type SchemaRegistry } from '@dxos/echo';
9
8
  import { Popover } from '@dxos/react-ui';
10
9
  import { FieldEditor } from '@dxos/react-ui-form';
11
10
  import { type FieldType } from '@dxos/schema';
12
11
 
13
- import { type TableModel, type ModalController } from '../../model';
12
+ import { type ModalController, type TableModel } from '../../model';
14
13
 
15
14
  type ColumnSettingsProps = {
16
- registry?: SchemaRegistry;
15
+ registry?: SchemaRegistry.SchemaRegistry;
17
16
  model?: TableModel;
18
17
  modals: ModalController;
19
18
  onNewColumn: () => void;
@@ -2,32 +2,30 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { type StoryObj, type Meta } from '@storybook/react-vite';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useMemo, useState } from 'react';
9
7
 
10
8
  import { Obj } from '@dxos/echo';
11
- import { FormatEnum, type JsonSchemaType } from '@dxos/echo-schema';
9
+ import { Format, type JsonSchemaType } from '@dxos/echo/internal';
12
10
  import { faker } from '@dxos/random';
13
- import { useClient } from '@dxos/react-client';
14
11
  import { Filter, useQuery, useSchema } from '@dxos/react-client/echo';
15
12
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
13
+ import { withTheme } from '@dxos/react-ui/testing';
16
14
  import { type SchemaPropertyDefinition } from '@dxos/schema';
17
- import { Testing } from '@dxos/schema/testing';
18
- import { withLayout, withTheme } from '@dxos/storybook-utils';
15
+ import { TestSchema } from '@dxos/schema/testing';
19
16
 
20
- import { DynamicTable } from './DynamicTable';
21
17
  import { type TableFeatures } from '../../model';
22
18
  import { translations } from '../../translations';
23
19
 
20
+ import { DynamicTable } from './DynamicTable';
21
+
24
22
  faker.seed(0);
25
23
 
26
24
  const useTestPropertiesAndObjects = () => {
27
25
  const properties = useMemo<SchemaPropertyDefinition[]>(
28
26
  () => [
29
- { name: 'name', format: FormatEnum.String },
30
- { name: 'age', format: FormatEnum.Number },
27
+ { name: 'name', format: Format.TypeFormat.String },
28
+ { name: 'age', format: Format.TypeFormat.Number },
31
29
  ],
32
30
  [],
33
31
  );
@@ -56,15 +54,20 @@ const DynamicTableStory = () => {
56
54
  // Story definitions.
57
55
  //
58
56
 
59
- const meta: Meta = {
57
+ const meta = {
60
58
  title: 'ui/react-ui-table/DynamicTable',
61
59
  component: DynamicTable,
62
- parameters: { translations },
63
- decorators: [withLayout({ fullscreen: true }), withTheme],
64
- };
60
+ decorators: [withTheme],
61
+ parameters: {
62
+ layout: 'fullscreen',
63
+ translations,
64
+ },
65
+ } satisfies Meta<typeof DynamicTable>;
65
66
 
66
67
  export default meta;
67
68
 
69
+ type Story = StoryObj<typeof meta>;
70
+
68
71
  export const Default: StoryObj = {
69
72
  render: DynamicTableStory,
70
73
  };
@@ -126,9 +129,8 @@ export const WithJsonSchema: StoryObj = {
126
129
 
127
130
  export const WithEchoSchema: StoryObj = {
128
131
  render: () => {
129
- const client = useClient();
130
132
  const { space } = useClientProvider();
131
- const schema = useSchema(client, space, Testing.Contact.typename);
133
+ const schema = useSchema(space, TestSchema.Person.typename);
132
134
  const objects = useQuery(space, schema ? Filter.type(schema) : Filter.nothing());
133
135
  if (!schema) {
134
136
  return <div>Loading schema...</div>;
@@ -138,13 +140,13 @@ export const WithEchoSchema: StoryObj = {
138
140
  },
139
141
  decorators: [
140
142
  withClientProvider({
141
- types: [Testing.Contact],
143
+ types: [TestSchema.Person],
142
144
  createIdentity: true,
143
145
  createSpace: true,
144
- onSpaceCreated: async ({ space }) => {
146
+ onCreateSpace: async ({ space }) => {
145
147
  Array.from({ length: 10 }).forEach(() => {
146
148
  space.db.add(
147
- Obj.make(Testing.Contact, {
149
+ Obj.make(TestSchema.Person, {
148
150
  name: faker.person.fullName(),
149
151
  email: faker.internet.email(),
150
152
  }),
@@ -2,24 +2,26 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect';
6
- import React, { useRef, useMemo, useCallback } from 'react';
5
+ import React, { useCallback, useMemo, useRef } from 'react';
7
6
 
8
- import { type JsonSchemaType } from '@dxos/echo-schema';
7
+ import { type Type } from '@dxos/echo';
8
+ import { type JsonSchemaType } from '@dxos/echo/internal';
9
9
  import { type ThemedClassName, useDefaultValue } from '@dxos/react-ui';
10
10
  import { mx } from '@dxos/react-ui-theme';
11
+ import { ProjectionModel } from '@dxos/schema';
11
12
 
12
- import { Table, type TableController } from './Table';
13
13
  import { useTableModel } from '../../hooks';
14
14
  import { type TableFeatures, TablePresentation, type TableRowAction } from '../../model';
15
- import { getBaseSchema, makeDynamicTable, type TablePropertyDefinition } from '../../util';
15
+ import { type TablePropertyDefinition, getBaseSchema, makeDynamicTable } from '../../util';
16
+
17
+ import { Table, type TableController } from './Table';
16
18
 
17
- type DynamicTableProps = ThemedClassName<{
19
+ export type DynamicTableProps<T extends Type.Entity.Any = Type.Entity.Any> = ThemedClassName<{
20
+ schema?: T;
18
21
  name?: string; // TODO(burdon): Remove?
19
22
  rows: any[];
20
23
  properties?: TablePropertyDefinition[];
21
24
  jsonSchema?: JsonSchemaType;
22
- schema?: Schema.Schema.AnyNoContext;
23
25
  features?: Partial<TableFeatures>;
24
26
  rowActions?: TableRowAction[];
25
27
  onRowClick?: (row: any) => void;
@@ -31,28 +33,25 @@ type DynamicTableProps = ThemedClassName<{
31
33
  */
32
34
  // TODO(burdon): Instead of creating component variants, create helpers/hooks that normalize the props.
33
35
  // TODO(burdon): Warning: Cannot update a component (`DynamicTable`) while rendering a different component (`DynamicTable`).
34
- export const DynamicTable = ({
36
+ export const DynamicTable = <T extends Type.Entity.Any = Type.Entity.Any>({
35
37
  classNames,
38
+ schema,
36
39
  name = 'example.com/dynamic-table', // Rmove default or make random; this will lead to type collisions.
37
40
  rows,
38
41
  properties,
39
42
  jsonSchema: _jsonSchema,
40
- schema,
41
43
  rowActions,
42
44
  onRowClick,
43
45
  onRowAction,
44
46
  ...props
45
- }: DynamicTableProps) => {
47
+ }: DynamicTableProps<T>) => {
46
48
  // TODO(burdon): Remove variance from the props (should be normalized externally; possibly via hooks).
47
- const { typename, jsonSchema } = useMemo(
49
+ const { jsonSchema } = useMemo(
48
50
  () => getBaseSchema({ typename: name, properties, jsonSchema: _jsonSchema, schema }),
49
51
  [name, properties, _jsonSchema, schema],
50
52
  );
51
53
 
52
- const { projection, view } = useMemo(
53
- () => makeDynamicTable({ typename, jsonSchema, properties }),
54
- [typename, jsonSchema, properties],
55
- );
54
+ const { object, projection } = useMemo(() => makeDynamicTable({ jsonSchema, properties }), [jsonSchema, properties]);
56
55
 
57
56
  const tableRef = useRef<TableController>(null);
58
57
  const handleCellUpdate = useCallback((cell: any) => {
@@ -72,11 +71,18 @@ export const DynamicTable = ({
72
71
  }) as const,
73
72
  );
74
73
 
74
+ const projectionModel = useMemo(() => {
75
+ if (jsonSchema && projection) {
76
+ const model = new ProjectionModel(jsonSchema, projection);
77
+ model.normalizeView();
78
+ return model;
79
+ }
80
+ }, [jsonSchema, projection]);
81
+
75
82
  const model = useTableModel({
76
83
  rows,
77
- view,
78
- schema: jsonSchema,
79
- projection,
84
+ object,
85
+ projection: projectionModel,
80
86
  features,
81
87
  rowActions,
82
88
  onCellUpdate: handleCellUpdate,
@@ -0,0 +1,233 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import type * as Schema from 'effect/Schema';
7
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
8
+ import { expect, userEvent, within } from 'storybook/test';
9
+
10
+ import { Obj, Type } from '@dxos/echo';
11
+ import { type DxGrid } from '@dxos/lit-grid';
12
+ import '@dxos/lit-ui/dx-tag-picker.pcss';
13
+ import { faker } from '@dxos/random';
14
+ import { useClient } from '@dxos/react-client';
15
+ import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
16
+ import { useAsyncEffect } from '@dxos/react-ui';
17
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
18
+ import { translations as formTranslations } from '@dxos/react-ui-form';
19
+ import { View } from '@dxos/schema';
20
+ import { type ValueGenerator, createAsyncGenerator } from '@dxos/schema/testing';
21
+ import { Organization, Person } from '@dxos/types';
22
+
23
+ import { useProjectionModel, useTableModel } from '../../hooks';
24
+ import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
25
+ import { translations } from '../../translations';
26
+ import { Table } from '../../types';
27
+
28
+ import { Table as TableComponent } from './Table';
29
+
30
+ faker.seed(1);
31
+ const generator: ValueGenerator = faker as any;
32
+
33
+ // TODO(burdon): Many-to-many relations.
34
+ // TODO(burdon): Mutable and immutable views.
35
+ // TODO(burdon): Reconcile schemas types and utils (see API PR).
36
+ // TODO(burdon): Base type for T (with id); see ECHO API PR?
37
+ const useTestModel = <S extends Type.Obj.Any>(schema: S, count: number) => {
38
+ const { space } = useClientProvider();
39
+ const [object, setObject] = useState<Table.Table>();
40
+
41
+ const features = useMemo<TableFeatures>(
42
+ () => ({ schemaEditable: false, dataEditable: true, selection: { enabled: false } }),
43
+ [],
44
+ );
45
+
46
+ useAsyncEffect(async () => {
47
+ if (!space) {
48
+ return;
49
+ }
50
+
51
+ const { view, jsonSchema } = await View.makeFromSpace({ space, typename: Type.getTypename(schema) });
52
+ const object = Table.make({ view, jsonSchema });
53
+ setObject(object);
54
+ space.db.add(object);
55
+ }, [space, schema]);
56
+
57
+ const projection = useProjectionModel(schema, object);
58
+ const model = useTableModel<TableRow>({ object, projection, features });
59
+
60
+ useEffect(() => {
61
+ if (!model || !space) {
62
+ return;
63
+ }
64
+
65
+ const objectGenerator = createAsyncGenerator(generator, schema, { db: space?.db, force: true });
66
+ void objectGenerator.createObjects(count).then((objects) => {
67
+ model.setRows(objects);
68
+ });
69
+ }, [model, space]);
70
+
71
+ const presentation = useMemo(() => {
72
+ if (!model) {
73
+ return;
74
+ }
75
+
76
+ return new TablePresentation(model);
77
+ }, [model]);
78
+
79
+ return { model, presentation };
80
+ };
81
+
82
+ const DefaultStory = () => {
83
+ const client = useClient();
84
+ const { model: orgModel, presentation: orgPresentation } = useTestModel(Organization.Organization, 50);
85
+ const { model: contactModel, presentation: contactPresentation } = useTestModel(Person.Person, 50);
86
+ const { space } = useClientProvider();
87
+
88
+ const handleCreate = useCallback(
89
+ (schema: Schema.Schema.AnyNoContext, values: any) => {
90
+ return client.spaces.default.db.add(Obj.make(schema, values));
91
+ },
92
+ [space],
93
+ );
94
+
95
+ return (
96
+ <div className='is-full bs-full grid grid-cols-2 divide-x divide-separator'>
97
+ <TableComponent.Root>
98
+ <TableComponent.Main
99
+ model={orgModel}
100
+ schema={Organization.Organization}
101
+ presentation={orgPresentation}
102
+ onCreate={handleCreate}
103
+ ignoreAttention
104
+ testId='relations-0'
105
+ />
106
+ </TableComponent.Root>
107
+ <TableComponent.Root>
108
+ <TableComponent.Main
109
+ model={contactModel}
110
+ schema={Person.Person}
111
+ presentation={contactPresentation}
112
+ onCreate={handleCreate}
113
+ ignoreAttention
114
+ testId='relations-1'
115
+ />
116
+ </TableComponent.Root>
117
+ </div>
118
+ );
119
+ };
120
+
121
+ const meta = {
122
+ title: 'ui/react-ui-table/Relations',
123
+ render: DefaultStory,
124
+ decorators: [
125
+ withTheme,
126
+ // TODO(thure): Shouldn’t `layout: 'fullscreen'` below make this unnecessary?
127
+ withLayout({ classNames: 'fixed inset-0' }),
128
+ withClientProvider({
129
+ types: [View.View, Organization.Organization, Person.Person, Table.Table],
130
+ createIdentity: true,
131
+ createSpace: true,
132
+ }),
133
+ ],
134
+ parameters: {
135
+ layout: 'fullscreen',
136
+ controls: { disable: true },
137
+ translations: [...translations, ...formTranslations],
138
+ },
139
+ } satisfies Meta<typeof DefaultStory>;
140
+
141
+ export default meta;
142
+
143
+ type Story = StoryObj<typeof meta>;
144
+
145
+ export const Default: Story = {
146
+ play: async ({ canvasElement }: any) => {
147
+ const canvas = within(canvasElement);
148
+ const body = within(document.body);
149
+
150
+ // Get all dx-grid elements (should have 2 - one for each table)
151
+ const firstGrid = await canvas.findByTestId('relations-0', undefined, { timeout: 30_000 });
152
+ const secondGrid = await canvas.findByTestId('relations-1', undefined, { timeout: 30_000 });
153
+
154
+ // Focus on the second table (Person/contacts table)
155
+ await expect(secondGrid).toBeVisible();
156
+
157
+ // Scroll to the relations column (column 4) - this mimics the scrollToColumn call
158
+ (secondGrid.closest('dx-grid') as DxGrid).scrollToColumn(4);
159
+
160
+ // Find and click the target cell (first row, relations column)
161
+ const targetCell = await within(secondGrid).findByTestId('grid.4.0', undefined, { timeout: 10_000 });
162
+ await expect(targetCell).toBeVisible();
163
+
164
+ // Click to focus the cell
165
+ await userEvent.click(targetCell as HTMLElement);
166
+ await expect(targetCell).toHaveFocus();
167
+ await userEvent.keyboard('{Enter}');
168
+
169
+ // Look for the combobox that should appear in edit mode
170
+ const combobox = await body.findByRole('combobox', undefined, { timeout: 5000 });
171
+ await userEvent.click(combobox);
172
+
173
+ const searchField = await body.findByPlaceholderText('Search…');
174
+ await userEvent.click(searchField);
175
+
176
+ // Get the first organization name from the first table to search for
177
+ const orgCell = await within(firstGrid).findByTestId('grid.0.0', undefined, { timeout: 10_000 });
178
+
179
+ const orgName = (orgCell.querySelector('.dx-grid__cell__content') as HTMLElement).textContent;
180
+ // Type the first 4 characters to search
181
+ await userEvent.type(searchField, orgName.substring(0, 4));
182
+
183
+ // Look for an option to select
184
+ const option = await body.findAllByRole('option');
185
+ await expect(option[0]).toBeVisible();
186
+
187
+ // Press Enter to select
188
+ await userEvent.keyboard('{Enter}');
189
+
190
+ // Look for and click save button
191
+ const saveButton = await body.findByTestId('save-button');
192
+ await userEvent.click(saveButton);
193
+
194
+ // Verify the relation was set (cell should now contain the org name)
195
+ await expect(targetCell).toHaveTextContent(orgName.substring(0, 4));
196
+
197
+ // Test object creation (new relations) - equivalent to "new relations work as expected" test
198
+ // Find a different cell to test object creation (second row, relations column)
199
+ const newTargetCell = await within(secondGrid).findByTestId('grid.4.1', undefined, { timeout: 10_000 });
200
+ await expect(newTargetCell).toBeTruthy();
201
+
202
+ // Click to focus the cell
203
+ await userEvent.click(newTargetCell as Element);
204
+
205
+ await userEvent.keyboard('{Enter}');
206
+
207
+ // Look for the combobox that should appear in edit mode
208
+ const newCombobox = await body.findByRole('combobox');
209
+ await userEvent.click(newCombobox);
210
+
211
+ const newSearchField = await body.findByPlaceholderText('Search…');
212
+ await userEvent.click(newSearchField);
213
+
214
+ // Type a new object name (this will create a new object)
215
+ const newOrgName = 'Salieri LLC';
216
+ await userEvent.type(newSearchField, newOrgName);
217
+
218
+ // Look for an option to select (should be the create new option)
219
+ const newOption = await body.findAllByRole('option');
220
+ await expect(newOption[0]).toBeVisible();
221
+
222
+ // Press Enter to select/create
223
+ await userEvent.keyboard('{Enter}');
224
+
225
+ // Look for and click save button
226
+ const createReferencedObjectForm = await body.findByTestId('create-referenced-object-form');
227
+ const saveObjectButton = await within(createReferencedObjectForm).findByTestId('save-button');
228
+ await userEvent.click(saveObjectButton);
229
+
230
+ // Verify the new object was created and relation was set
231
+ await expect(newTargetCell).toHaveTextContent(newOrgName);
232
+ },
233
+ } as any;