@dxos/react-ui-table 0.8.4-main.dedc0f3 → 0.8.4-main.ead640a

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 (88) hide show
  1. package/dist/lib/browser/{chunk-63UUF4IG.mjs → chunk-4K7JPALT.mjs} +5 -5
  2. package/dist/lib/browser/chunk-4K7JPALT.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-GPZSXPSA.mjs → chunk-OZTXLZGM.mjs} +25 -43
  4. package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +123 -269
  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 +8 -6
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/browser/types/index.mjs +1 -1
  11. package/dist/lib/node-esm/{chunk-K734DJB7.mjs → chunk-MK6OVUF6.mjs} +5 -5
  12. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-XUD6W42C.mjs → chunk-S56Z4V4X.mjs} +25 -43
  14. package/dist/lib/node-esm/chunk-S56Z4V4X.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +123 -269
  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 +8 -6
  19. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/types/index.mjs +1 -1
  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 +3 -3
  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 +71 -7
  26. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/Table.d.ts +5 -2
  28. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Table.stories.d.ts +5 -6
  30. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -5
  32. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  33. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -3
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +1 -2
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  38. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  39. package/dist/types/src/components/testing.d.ts +3 -3
  40. package/dist/types/src/components/testing.d.ts.map +1 -1
  41. package/dist/types/src/hooks/useAddRow.d.ts +1 -1
  42. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  43. package/dist/types/src/hooks/useTableModel.d.ts +1 -1
  44. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  45. package/dist/types/src/model/modal-controller.d.ts +0 -7
  46. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  47. package/dist/types/src/model/table-model.d.ts +1 -1
  48. package/dist/types/src/model/table-model.d.ts.map +1 -1
  49. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  50. package/dist/types/src/testing/useTestTableModel.d.ts +1 -1
  51. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  52. package/dist/types/src/types/schema.d.ts +3 -3
  53. package/dist/types/src/types/schema.d.ts.map +1 -1
  54. package/dist/types/src/util/dynamic-table.d.ts +3 -4
  55. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  56. package/dist/types/src/util/schema.d.ts +1 -1
  57. package/dist/types/src/util/schema.d.ts.map +1 -1
  58. package/dist/types/tsconfig.tsbuildinfo +1 -1
  59. package/package.json +50 -50
  60. package/src/components/Table/DynamicTable.stories.tsx +8 -7
  61. package/src/components/Table/DynamicTable.tsx +4 -7
  62. package/src/components/Table/Relations.stories.tsx +126 -11
  63. package/src/components/Table/Table.stories.tsx +33 -32
  64. package/src/components/Table/Table.tsx +13 -9
  65. package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
  66. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +17 -45
  67. package/src/components/TableCellEditor/TableCellEditor.tsx +7 -94
  68. package/src/components/TableCellEditor/index.ts +1 -0
  69. package/src/components/testing.ts +2 -2
  70. package/src/hooks/useAddRow.ts +1 -1
  71. package/src/hooks/useTableModel.ts +1 -1
  72. package/src/model/modal-controller.ts +0 -28
  73. package/src/model/table-model.test.ts +16 -6
  74. package/src/model/table-model.ts +2 -1
  75. package/src/model/table-presentation.test.ts +19 -17
  76. package/src/model/table-presentation.ts +4 -5
  77. package/src/model/table-sorting.ts +1 -1
  78. package/src/testing/useTestTableModel.ts +6 -4
  79. package/src/types/schema.ts +4 -4
  80. package/src/util/dynamic-table.ts +6 -7
  81. package/src/util/schema.ts +4 -4
  82. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
  83. package/dist/lib/browser/chunk-GPZSXPSA.mjs.map +0 -7
  84. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
  85. package/dist/lib/node-esm/chunk-XUD6W42C.mjs.map +0 -7
  86. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  87. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  88. package/src/components/Table/CreateRefPanel.tsx +0 -103
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-table",
3
- "version": "0.8.4-main.dedc0f3",
3
+ "version": "0.8.4-main.ead640a",
4
4
  "description": "A table component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -53,73 +53,73 @@
53
53
  "src"
54
54
  ],
55
55
  "dependencies": {
56
- "@codemirror/autocomplete": "^6.18.7",
56
+ "@codemirror/autocomplete": "^6.19.0",
57
57
  "@codemirror/state": "^6.5.2",
58
- "@codemirror/view": "^6.38.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.dedc0f3",
69
- "@dxos/context": "0.8.4-main.dedc0f3",
70
- "@dxos/debug": "0.8.4-main.dedc0f3",
71
- "@dxos/echo": "0.8.4-main.dedc0f3",
72
- "@dxos/echo-db": "0.8.4-main.dedc0f3",
73
- "@dxos/echo-schema": "0.8.4-main.dedc0f3",
74
- "@dxos/effect": "0.8.4-main.dedc0f3",
75
- "@dxos/invariant": "0.8.4-main.dedc0f3",
76
- "@dxos/keys": "0.8.4-main.dedc0f3",
77
- "@dxos/live-object": "0.8.4-main.dedc0f3",
78
- "@dxos/log": "0.8.4-main.dedc0f3",
79
- "@dxos/lit-grid": "0.8.4-main.dedc0f3",
80
- "@dxos/plugin-search": "0.8.4-main.dedc0f3",
81
- "@dxos/react-ui-attention": "0.8.4-main.dedc0f3",
82
- "@dxos/react-client": "0.8.4-main.dedc0f3",
83
- "@dxos/react-ui-editor": "0.8.4-main.dedc0f3",
84
- "@dxos/react-ui-form": "0.8.4-main.dedc0f3",
85
- "@dxos/react-ui-grid": "0.8.4-main.dedc0f3",
86
- "@dxos/react-ui-menu": "0.8.4-main.dedc0f3",
87
- "@dxos/random": "0.8.4-main.dedc0f3",
88
- "@dxos/react-ui-stack": "0.8.4-main.dedc0f3",
89
- "@dxos/react-ui-searchlist": "0.8.4-main.dedc0f3",
90
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.dedc0f3",
91
- "@dxos/react-ui-tag-picker": "0.8.4-main.dedc0f3",
92
- "@dxos/schema": "0.8.4-main.dedc0f3",
93
- "@dxos/react-ui-types": "0.8.4-main.dedc0f3",
94
- "@dxos/util": "0.8.4-main.dedc0f3"
68
+ "@dxos/async": "0.8.4-main.ead640a",
69
+ "@dxos/context": "0.8.4-main.ead640a",
70
+ "@dxos/debug": "0.8.4-main.ead640a",
71
+ "@dxos/echo": "0.8.4-main.ead640a",
72
+ "@dxos/echo-db": "0.8.4-main.ead640a",
73
+ "@dxos/effect": "0.8.4-main.ead640a",
74
+ "@dxos/invariant": "0.8.4-main.ead640a",
75
+ "@dxos/keys": "0.8.4-main.ead640a",
76
+ "@dxos/lit-grid": "0.8.4-main.ead640a",
77
+ "@dxos/live-object": "0.8.4-main.ead640a",
78
+ "@dxos/log": "0.8.4-main.ead640a",
79
+ "@dxos/random": "0.8.4-main.ead640a",
80
+ "@dxos/plugin-search": "0.8.4-main.ead640a",
81
+ "@dxos/react-client": "0.8.4-main.ead640a",
82
+ "@dxos/react-ui-attention": "0.8.4-main.ead640a",
83
+ "@dxos/react-ui-components": "0.8.4-main.ead640a",
84
+ "@dxos/react-ui-editor": "0.8.4-main.ead640a",
85
+ "@dxos/react-ui-grid": "0.8.4-main.ead640a",
86
+ "@dxos/react-ui-searchlist": "0.8.4-main.ead640a",
87
+ "@dxos/react-ui-menu": "0.8.4-main.ead640a",
88
+ "@dxos/react-ui-stack": "0.8.4-main.ead640a",
89
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.ead640a",
90
+ "@dxos/schema": "0.8.4-main.ead640a",
91
+ "@dxos/react-ui-types": "0.8.4-main.ead640a",
92
+ "@dxos/util": "0.8.4-main.ead640a",
93
+ "@dxos/react-ui-form": "0.8.4-main.ead640a"
95
94
  },
96
95
  "devDependencies": {
97
- "@effect-rx/rx-react": "0.38.0",
98
- "@effect/platform": "0.90.2",
96
+ "@effect-rx/rx-react": "0.42.4",
97
+ "@effect/platform": "0.92.1",
99
98
  "@types/lodash.orderby": "^4.6.9",
100
- "@types/react": "~18.2.0",
101
- "@types/react-dom": "~18.2.0",
102
- "effect": "3.17.7",
99
+ "@types/react": "~19.2.2",
100
+ "@types/react-dom": "~19.2.1",
101
+ "effect": "3.18.3",
103
102
  "fast-check": "^3.19.0",
104
- "react": "~18.2.0",
105
- "react-dom": "~18.2.0",
106
- "vite": "7.1.1",
107
- "@dxos/echo-signals": "0.8.4-main.dedc0f3",
108
- "@dxos/plugin-search": "0.8.4-main.dedc0f3",
109
- "@dxos/random": "0.8.4-main.dedc0f3",
110
- "@dxos/react-ui-theme": "0.8.4-main.dedc0f3",
111
- "@dxos/test-utils": "0.8.4-main.dedc0f3",
112
- "@dxos/react-ui": "0.8.4-main.dedc0f3",
113
- "@dxos/storybook-utils": "0.8.4-main.dedc0f3"
103
+ "react": "~19.2.0",
104
+ "react-dom": "~19.2.0",
105
+ "vite": "7.1.9",
106
+ "@dxos/echo-signals": "0.8.4-main.ead640a",
107
+ "@dxos/lit-ui": "0.8.4-main.ead640a",
108
+ "@dxos/plugin-search": "0.8.4-main.ead640a",
109
+ "@dxos/random": "0.8.4-main.ead640a",
110
+ "@dxos/react-ui": "0.8.4-main.ead640a",
111
+ "@dxos/react-ui-theme": "0.8.4-main.ead640a",
112
+ "@dxos/storybook-utils": "0.8.4-main.ead640a",
113
+ "@dxos/test-utils": "0.8.4-main.ead640a"
114
114
  },
115
115
  "peerDependencies": {
116
116
  "@effect-rx/rx-react": "^0.34.1",
117
117
  "@effect/platform": "^0.80.12",
118
118
  "effect": "^3.13.3",
119
- "react": "~18.2.0",
120
- "react-dom": "~18.2.0",
121
- "@dxos/react-ui": "0.8.4-main.dedc0f3",
122
- "@dxos/react-ui-theme": "0.8.4-main.dedc0f3"
119
+ "react": "^19.0.0",
120
+ "react-dom": "^19.0.0",
121
+ "@dxos/react-ui": "0.8.4-main.ead640a",
122
+ "@dxos/react-ui-theme": "0.8.4-main.ead640a"
123
123
  },
124
124
  "publishConfig": {
125
125
  "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 { FormatEnum, 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
16
  import { Testing } from '@dxos/schema/testing';
18
- import { withLayout, withTheme } from '@dxos/storybook-utils';
19
17
 
20
18
  import { type TableFeatures } from '../../model';
21
19
  import { translations } from '../../translations';
@@ -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;
@@ -144,7 +145,7 @@ export const WithEchoSchema: StoryObj = {
144
145
  types: [Testing.Contact],
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
151
  Obj.make(Testing.Contact, {
@@ -2,10 +2,10 @@
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
11
 
@@ -45,15 +45,12 @@ export const DynamicTable = ({
45
45
  ...props
46
46
  }: DynamicTableProps) => {
47
47
  // TODO(burdon): Remove variance from the props (should be normalized externally; possibly via hooks).
48
- const { typename, jsonSchema } = useMemo(
48
+ const { jsonSchema } = useMemo(
49
49
  () => getBaseSchema({ typename: name, properties, jsonSchema: _jsonSchema, schema }),
50
50
  [name, properties, _jsonSchema, schema],
51
51
  );
52
52
 
53
- const { projection, view } = useMemo(
54
- () => makeDynamicTable({ typename, jsonSchema, properties }),
55
- [typename, jsonSchema, properties],
56
- );
53
+ const { projection, view } = useMemo(() => makeDynamicTable({ jsonSchema, properties }), [jsonSchema, properties]);
57
54
 
58
55
  const tableRef = useRef<TableController>(null);
59
56
  const handleCellUpdate = useCallback((cell: any) => {
@@ -2,20 +2,23 @@
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';
9
-
10
- import { Type } from '@dxos/echo';
11
- import { type JsonSchemaType } from '@dxos/echo-schema';
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 JsonSchemaType } from '@dxos/echo/internal';
12
+ import { type DxGrid } from '@dxos/lit-grid';
13
+ import '@dxos/lit-ui/dx-tag-picker.pcss';
12
14
  import { faker } from '@dxos/random';
13
15
  import { useClient } from '@dxos/react-client';
14
16
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
15
17
  import { useAsyncEffect } from '@dxos/react-ui';
18
+ import { withTheme } from '@dxos/react-ui/testing';
19
+ import { translations as formTranslations } from '@dxos/react-ui-form';
16
20
  import { DataType } from '@dxos/schema';
17
21
  import { type ValueGenerator, createAsyncGenerator } from '@dxos/schema/testing';
18
- import { withLayout, withTheme } from '@dxos/storybook-utils';
19
22
 
20
23
  import { useTableModel } from '../../hooks';
21
24
  import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
@@ -86,6 +89,14 @@ const DefaultStory = () => {
86
89
  const client = useClient();
87
90
  const { model: orgModel, presentation: orgPresentation } = useTestModel(DataType.Organization, 50);
88
91
  const { model: contactModel, presentation: contactPresentation } = useTestModel(DataType.Person, 50);
92
+ const { space } = useClientProvider();
93
+
94
+ const handleCreate = useCallback(
95
+ (schema: Schema.Schema.AnyNoContext, values: any) => {
96
+ return client.spaces.default.db.add(Obj.make(schema, values));
97
+ },
98
+ [space],
99
+ );
89
100
 
90
101
  return (
91
102
  <div className='is-full bs-full grid grid-cols-2 divide-x divide-separator'>
@@ -94,8 +105,10 @@ const DefaultStory = () => {
94
105
  model={orgModel}
95
106
  schema={DataType.Organization}
96
107
  presentation={orgPresentation}
108
+ onCreate={handleCreate}
97
109
  client={client}
98
110
  ignoreAttention
111
+ testId='relations-0'
99
112
  />
100
113
  </TableComponent.Root>
101
114
  <TableComponent.Root>
@@ -103,8 +116,10 @@ const DefaultStory = () => {
103
116
  model={contactModel}
104
117
  schema={DataType.Person}
105
118
  presentation={contactPresentation}
119
+ onCreate={handleCreate}
106
120
  client={client}
107
121
  ignoreAttention
122
+ testId='relations-1'
108
123
  />
109
124
  </TableComponent.Root>
110
125
  </div>
@@ -114,20 +129,120 @@ const DefaultStory = () => {
114
129
  const meta = {
115
130
  title: 'ui/react-ui-table/Relations',
116
131
  render: DefaultStory,
117
- parameters: { translations, controls: { disable: true } },
118
132
  decorators: [
133
+ withTheme,
119
134
  withClientProvider({
120
135
  types: [DataType.View, DataType.Organization, DataType.Person, Table.Table],
121
136
  createIdentity: true,
122
137
  createSpace: true,
123
138
  }),
124
- withTheme,
125
- withLayout({ fullscreen: true }),
126
139
  ],
140
+ parameters: {
141
+ layout: 'fullscreen',
142
+ controls: { disable: true },
143
+ translations: [...translations, ...formTranslations],
144
+ },
127
145
  } satisfies Meta<typeof DefaultStory>;
128
146
 
129
147
  export default meta;
130
148
 
131
149
  type Story = StoryObj<typeof meta>;
132
150
 
133
- export const Default: Story = {};
151
+ export const Default: Story = {
152
+ // TODO(wittjosiah): This test is flaky, especially in CI.
153
+ // Most typical failure is `targetCell` returns `null`.
154
+ _play: async ({ canvasElement }: any) => {
155
+ const canvas = within(canvasElement);
156
+ const body = within(document.body);
157
+
158
+ // Get all dx-grid elements (should have 2 - one for each table)
159
+ const firstGrid = await canvas.findByTestId('relations-0', undefined, { timeout: 30_000 });
160
+ const secondGrid = await canvas.findByTestId('relations-1', undefined, { timeout: 30_000 });
161
+
162
+ // Focus on the second table (Person/contacts table)
163
+ await expect(secondGrid).toBeVisible();
164
+
165
+ // Scroll to the relations column (column 4) - this mimics the scrollToColumn call
166
+ (secondGrid.closest('dx-grid') as DxGrid).scrollToColumn(4);
167
+
168
+ // Wait for scroll to complete
169
+ await new Promise((resolve) => setTimeout(resolve, 1000));
170
+
171
+ // Find and click the target cell (first row, relations column)
172
+ const targetCell = secondGrid.querySelector('[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="4"]');
173
+ await expect(targetCell).toBeVisible();
174
+
175
+ // Click to focus the cell
176
+ await userEvent.click(targetCell as HTMLElement);
177
+ await expect(targetCell).toHaveFocus();
178
+ await userEvent.keyboard('{Enter}');
179
+
180
+ // Look for the combobox that should appear in edit mode
181
+ const combobox = await body.findByRole('combobox', undefined, { timeout: 5000 });
182
+ await userEvent.click(combobox);
183
+
184
+ const searchField = await body.findByPlaceholderText('Search…');
185
+ await userEvent.click(searchField);
186
+
187
+ // Get the first organization name from the first table to search for
188
+ const orgCell = firstGrid.querySelector(
189
+ '[data-dx-grid-plane="grid"] [aria-rowindex="0"][aria-colindex="0"] .dx-grid__cell__content',
190
+ ) as HTMLElement;
191
+
192
+ const orgName = orgCell.textContent;
193
+ // Type the first 4 characters to search
194
+ await userEvent.type(searchField, orgName.substring(0, 4));
195
+
196
+ // Look for an option to select
197
+ const option = await body.findAllByRole('option');
198
+ await expect(option[0]).toBeVisible();
199
+
200
+ // Press Enter to select
201
+ await userEvent.keyboard('{Enter}');
202
+
203
+ // Look for and click save button
204
+ const saveButton = await body.findByTestId('save-button');
205
+ await userEvent.click(saveButton);
206
+
207
+ // Verify the relation was set (cell should now contain the org name)
208
+ await expect(targetCell).toHaveTextContent(orgName.substring(0, 4));
209
+
210
+ // Test object creation (new relations) - equivalent to "new relations work as expected" test
211
+ // Find a different cell to test object creation (second row, relations column)
212
+ const newTargetCell = secondGrid.querySelector(
213
+ '[data-dx-grid-plane="grid"] [aria-rowindex="1"][aria-colindex="4"]',
214
+ );
215
+ await expect(newTargetCell).toBeTruthy();
216
+
217
+ // Click to focus the cell
218
+ await userEvent.click(newTargetCell as Element);
219
+
220
+ await userEvent.keyboard('{Enter}');
221
+
222
+ // Look for the combobox that should appear in edit mode
223
+ const newCombobox = await body.findByRole('combobox');
224
+ await userEvent.click(newCombobox);
225
+
226
+ const newSearchField = await body.findByPlaceholderText('Search…');
227
+ await userEvent.click(newSearchField);
228
+
229
+ // Type a new object name (this will create a new object)
230
+ const newOrgName = 'Salieri LLC';
231
+ await userEvent.type(newSearchField, newOrgName);
232
+
233
+ // Look for an option to select (should be the create new option)
234
+ const newOption = await body.findAllByRole('option');
235
+ await expect(newOption[0]).toBeVisible();
236
+
237
+ // Press Enter to select/create
238
+ await userEvent.keyboard('{Enter}');
239
+
240
+ // Look for and click save button
241
+ const createReferencedObjectForm = await body.findByTestId('create-referenced-object-form');
242
+ const saveObjectButton = await within(createReferencedObjectForm).findByTestId('save-button');
243
+ await userEvent.click(saveObjectButton);
244
+
245
+ // Verify the new object was created and relation was set
246
+ await expect(newTargetCell).toHaveTextContent(newOrgName);
247
+ },
248
+ } as any;
@@ -2,13 +2,11 @@
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 { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
9
7
  import React, { useCallback } from 'react';
10
8
 
11
- import { Filter, Obj, Query, Type } from '@dxos/echo';
9
+ import { Obj, type QueryAST, Type } from '@dxos/echo';
12
10
  import {
13
11
  EchoObject,
14
12
  FormatAnnotation,
@@ -16,17 +14,18 @@ import {
16
14
  GeneratorAnnotation,
17
15
  LabelAnnotation,
18
16
  PropertyMetaAnnotationId,
19
- } from '@dxos/echo-schema';
17
+ } from '@dxos/echo/internal';
18
+ import { live } from '@dxos/echo/internal';
20
19
  import { invariant } from '@dxos/invariant';
21
20
  import { faker } from '@dxos/random';
22
21
  import { PublicKey } from '@dxos/react-client';
23
- import { type Space, live } from '@dxos/react-client/echo';
22
+ import { type Space } from '@dxos/react-client/echo';
24
23
  import { withClientProvider } from '@dxos/react-client/testing';
24
+ import { withTheme } from '@dxos/react-ui/testing';
25
25
  import { ViewEditor } from '@dxos/react-ui-form';
26
26
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
27
- import { DataType, getSchemaFromPropertyDefinitions } from '@dxos/schema';
27
+ import { DataType, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
28
28
  import { Testing, createObjectFactory } from '@dxos/schema/testing';
29
- import { withLayout, withTheme } from '@dxos/storybook-utils';
30
29
 
31
30
  import { useTestTableModel } from '../../testing';
32
31
  import { translations } from '../../translations';
@@ -77,12 +76,12 @@ const StoryViewEditor = ({
77
76
  handleDeleteColumn: (fieldId: string) => void;
78
77
  }) => {
79
78
  const handleQueryChanged = useCallback(
80
- (typename: string) => {
79
+ (newQuery: QueryAST.Query) => {
81
80
  invariant(schema);
82
81
  invariant(Type.isMutable(schema));
83
- schema.updateTypename(typename);
82
+ schema.updateTypename(getTypenameFromQuery(newQuery));
84
83
  invariant(view);
85
- view.query = Query.select(Filter.typename(typename)).ast;
84
+ view.query.ast = newQuery;
86
85
  },
87
86
  [schema, view],
88
87
  );
@@ -160,21 +159,13 @@ type StoryProps = { rows?: number };
160
159
  const meta = {
161
160
  title: 'ui/react-ui-table/Table',
162
161
  render: DefaultStory,
163
- parameters: {
164
- translations,
165
- layout: 'fullscreen',
166
- controls: {
167
- disable: true,
168
- },
169
- },
170
162
  decorators: [
171
163
  withTheme,
172
- withLayout({ fullscreen: true }),
173
164
  withClientProvider({
174
165
  types: [DataType.View, Table.Table],
175
166
  createIdentity: true,
176
167
  createSpace: true,
177
- onSpaceCreated: async ({ client, space }) => {
168
+ onCreateSpace: async ({ client, space }) => {
178
169
  const [schema] = await space.db.schemaRegistry.register([TestSchema]);
179
170
  const { view } = await Table.makeView({ client, space, typename: schema.typename });
180
171
  view.projection.fields = [
@@ -196,6 +187,13 @@ const meta = {
196
187
  },
197
188
  }),
198
189
  ],
190
+ parameters: {
191
+ layout: 'fullscreen',
192
+ controls: {
193
+ disable: true,
194
+ },
195
+ translations,
196
+ },
199
197
  } satisfies Meta<typeof TableComponent>;
200
198
 
201
199
  export default meta;
@@ -206,13 +204,12 @@ export const Default: Story = {};
206
204
 
207
205
  export const StaticSchema: StoryObj = {
208
206
  render: DefaultStory,
209
- parameters: { translations },
210
207
  decorators: [
211
208
  withClientProvider({
212
209
  types: [DataType.View, Table.Table, Testing.Contact, Testing.Organization],
213
210
  createIdentity: true,
214
211
  createSpace: true,
215
- onSpaceCreated: async ({ client, space }) => {
212
+ onCreateSpace: async ({ client, space }) => {
216
213
  const { view } = await Table.makeView({ client, space, typename: Testing.Contact.typename });
217
214
  space.db.add(view);
218
215
 
@@ -223,9 +220,11 @@ export const StaticSchema: StoryObj = {
223
220
  ]);
224
221
  },
225
222
  }),
226
- withLayout({ fullscreen: true }),
227
- withTheme,
228
223
  ],
224
+ parameters: {
225
+ layout: 'fullscreen',
226
+ translations,
227
+ },
229
228
  };
230
229
 
231
230
  const ContactWithArrayOfEmails = Schema.Struct({
@@ -247,13 +246,12 @@ const ContactWithArrayOfEmails = Schema.Struct({
247
246
 
248
247
  export const ArrayOfObjects: StoryObj = {
249
248
  render: DefaultStory,
250
- parameters: { translations },
251
249
  decorators: [
252
250
  withClientProvider({
253
251
  types: [DataType.View, Table.Table, Testing.Contact, Testing.Organization, ContactWithArrayOfEmails],
254
252
  createIdentity: true,
255
253
  createSpace: true,
256
- onSpaceCreated: async ({ client, space }) => {
254
+ onCreateSpace: async ({ client, space }) => {
257
255
  const { view } = await Table.makeView({ client, space, typename: ContactWithArrayOfEmails.typename });
258
256
  space.db.add(view);
259
257
 
@@ -265,21 +263,22 @@ export const ArrayOfObjects: StoryObj = {
265
263
  ]);
266
264
  },
267
265
  }),
268
- withLayout({ fullscreen: true }),
269
- withTheme,
270
266
  ],
267
+ parameters: {
268
+ layout: 'fullscreen',
269
+ translations,
270
+ },
271
271
  };
272
272
 
273
273
  export const Tags: Meta<StoryProps> = {
274
274
  title: 'ui/react-ui-table/Table',
275
275
  render: DefaultStory,
276
- parameters: { translations },
277
276
  decorators: [
278
277
  withClientProvider({
279
278
  types: [DataType.View, Table.Table],
280
279
  createIdentity: true,
281
280
  createSpace: true,
282
- onSpaceCreated: async ({ client, space }) => {
281
+ onCreateSpace: async ({ client, space }) => {
283
282
  // Configure schema.
284
283
  const typename = 'example.com/SingleSelect';
285
284
  const selectOptions = [
@@ -321,7 +320,9 @@ export const Tags: Meta<StoryProps> = {
321
320
  });
322
321
  },
323
322
  }),
324
- withLayout({ fullscreen: true }),
325
- withTheme,
326
323
  ],
324
+ parameters: {
325
+ layout: 'fullscreen',
326
+ translations,
327
+ },
327
328
  };
@@ -2,7 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect/Schema';
5
+ import type * as Schema from 'effect/Schema';
6
+ import * as String from 'effect/String';
6
7
  import React, {
7
8
  type MouseEvent,
8
9
  type PropsWithChildren,
@@ -15,8 +16,8 @@ import React, {
15
16
  useState,
16
17
  } from 'react';
17
18
 
18
- import { type Client } from '@dxos/react-client';
19
19
  // TODO(wittjosiah): Remove dependency on react-client.
20
+ import { type Client } from '@dxos/react-client';
20
21
  import { useAttention } from '@dxos/react-ui-attention';
21
22
  import {
22
23
  type DxGridElement,
@@ -34,11 +35,10 @@ import { mx } from '@dxos/react-ui-theme';
34
35
 
35
36
  import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
36
37
  import { tableButtons, tableControls } from '../../util';
37
- import { type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
38
+ import { type OnCreateHandler, type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
38
39
 
39
40
  import { ColumnActionsMenu } from './ColumnActionsMenu';
40
41
  import { ColumnSettings } from './ColumnSettings';
41
- import { CreateRefPanel } from './CreateRefPanel';
42
42
  import { RowActionsMenu } from './RowActionsMenu';
43
43
 
44
44
  const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
@@ -80,15 +80,17 @@ export type TableController = {
80
80
  export type TableMainProps = {
81
81
  model?: TableModel;
82
82
  presentation?: TablePresentation;
83
- schema?: Schema.AnyNoContext;
83
+ schema?: Schema.Schema.AnyNoContext;
84
84
  client?: Client;
85
85
  // TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
86
86
  ignoreAttention?: boolean;
87
+ onCreate?: OnCreateHandler;
87
88
  onRowClick?: (row: any) => void;
89
+ testId?: string;
88
90
  };
89
91
 
90
92
  const TableMain = forwardRef<TableController, TableMainProps>(
91
- ({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
93
+ ({ model, presentation, ignoreAttention, schema, client, onCreate, onRowClick, testId }, forwardedRef) => {
92
94
  const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
93
95
  const { hasAttention } = useAttention(model?.id ?? 'table');
94
96
  const modals = useMemo(() => new ModalController(), []);
@@ -275,7 +277,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
275
277
  const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
276
278
  (increment, delta, cell) => {
277
279
  if (dxGrid && model) {
278
- if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1) {
280
+ if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1 && increment !== 'col') {
279
281
  handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
280
282
  } else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
281
283
  handleSaveDraftRow(cell.row);
@@ -320,7 +322,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
320
322
  void navigator.clipboard.readText().then((clipboardText) => {
321
323
  try {
322
324
  // Attempt to set the cell's content to clipboard content
323
- model.setCellData(cell, clipboardText);
325
+ model.setCellData(cell, String.trim(clipboardText).replace(/[\n\r]+/, ' '));
324
326
  handleSave();
325
327
  } catch {
326
328
  // If validation fails, emit a DxEditRequest event with initialContent from clipboard
@@ -356,6 +358,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
356
358
  try {
357
359
  model.setCellData(cell, undefined);
358
360
  event.preventDefault();
361
+ handleSave();
359
362
  } catch {
360
363
  // Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
361
364
  }
@@ -404,6 +407,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
404
407
  schema={schema}
405
408
  onFocus={handleFocus}
406
409
  onSave={handleSave}
410
+ onCreate={onCreate}
407
411
  client={client}
408
412
  />
409
413
  <Grid.Content
@@ -419,12 +423,12 @@ const TableMain = forwardRef<TableController, TableMainProps>(
419
423
  onClick={handleGridClick}
420
424
  onKeyDownCapture={handleKeyDown}
421
425
  onWheelCapture={handleWheel}
426
+ {...(testId && { 'data-testid': testId })}
422
427
  ref={setDxGrid}
423
428
  />
424
429
  <RowActionsMenu model={model} modals={modals} />
425
430
  <ColumnActionsMenu model={model} modals={modals} />
426
431
  <ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
427
- <CreateRefPanel model={model} modals={modals} />
428
432
  </Grid.Root>
429
433
  );
430
434
  },