@dxos/react-ui-table 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d

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