@dxos/react-ui-form 0.8.4-main.d05539e30a → 0.8.4-main.d9fc60f731

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 (154) hide show
  1. package/dist/lib/browser/index.mjs +1335 -561
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/translations.mjs +2 -1
  5. package/dist/lib/browser/translations.mjs.map +2 -2
  6. package/dist/lib/node-esm/index.mjs +1335 -561
  7. package/dist/lib/node-esm/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/meta.json +1 -1
  9. package/dist/lib/node-esm/translations.mjs +2 -1
  10. package/dist/lib/node-esm/translations.mjs.map +2 -2
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +2 -2
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +2 -1
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.d.ts +35 -9
  16. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/Form.stories.d.ts +76 -16
  18. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/FormField.d.ts +14 -4
  20. package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/FormFieldComponent.d.ts +22 -10
  22. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/FormFieldSet.d.ts +8 -2
  24. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/FormTooltipsContext.d.ts +19 -0
  26. package/dist/types/src/components/Form/FormTooltipsContext.d.ts.map +1 -0
  27. package/dist/types/src/components/Form/Layout/FormLayout.d.ts +50 -0
  28. package/dist/types/src/components/Form/Layout/FormLayout.d.ts.map +1 -0
  29. package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts +119 -0
  30. package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts +2 -0
  32. package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts +2 -0
  34. package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/Layout/annotation.d.ts +30 -0
  36. package/dist/types/src/components/Form/Layout/annotation.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/Layout/index.d.ts +4 -0
  38. package/dist/types/src/components/Form/Layout/index.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/Layout/parser.d.ts +31 -0
  40. package/dist/types/src/components/Form/Layout/parser.d.ts.map +1 -0
  41. package/dist/types/src/components/Form/Layout/parser.test.d.ts +2 -0
  42. package/dist/types/src/components/Form/Layout/parser.test.d.ts.map +1 -0
  43. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
  44. package/dist/types/src/components/Form/fields/DateField.d.ts +4 -0
  45. package/dist/types/src/components/Form/fields/DateField.d.ts.map +1 -0
  46. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +1 -1
  47. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
  48. package/dist/types/src/components/Form/fields/InlineRefField.d.ts +13 -0
  49. package/dist/types/src/components/Form/fields/InlineRefField.d.ts.map +1 -0
  50. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +1 -1
  51. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -1
  52. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +2 -1
  53. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -1
  54. package/dist/types/src/components/Form/fields/RefField.d.ts +1 -1
  55. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
  56. package/dist/types/src/components/Form/fields/SelectField.d.ts +4 -1
  57. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -1
  58. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +1 -1
  59. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
  60. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +2 -1
  61. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
  62. package/dist/types/src/components/Form/fields/TupleField.d.ts +1 -1
  63. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
  64. package/dist/types/src/components/Form/fields/index.d.ts +2 -0
  65. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -1
  66. package/dist/types/src/components/Form/index.d.ts +3 -0
  67. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  68. package/dist/types/src/components/Form/meta-tags.d.ts +30 -0
  69. package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -0
  70. package/dist/types/src/components/Form/meta-tags.test.d.ts +2 -0
  71. package/dist/types/src/components/Form/meta-tags.test.d.ts.map +1 -0
  72. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +3 -4
  73. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -1
  74. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +2 -2
  75. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  76. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +2 -1
  77. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  78. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
  79. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +2 -1
  80. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -1
  81. package/dist/types/src/components/ObjectTree/ObjectTree.d.ts +9 -0
  82. package/dist/types/src/components/ObjectTree/ObjectTree.d.ts.map +1 -0
  83. package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts +87 -0
  84. package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts.map +1 -0
  85. package/dist/types/src/components/ObjectTree/index.d.ts +2 -0
  86. package/dist/types/src/components/ObjectTree/index.d.ts.map +1 -0
  87. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
  88. package/dist/types/src/components/Settings/Settings.stories.d.ts +2 -1
  89. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
  90. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +7 -8
  91. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  92. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +4 -4
  93. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  94. package/dist/types/src/components/index.d.ts +1 -0
  95. package/dist/types/src/components/index.d.ts.map +1 -1
  96. package/dist/types/src/components/testing.d.ts +9 -0
  97. package/dist/types/src/components/testing.d.ts.map +1 -1
  98. package/dist/types/src/translations.d.ts +2 -1
  99. package/dist/types/src/translations.d.ts.map +1 -1
  100. package/dist/types/src/util/properties.d.ts +11 -0
  101. package/dist/types/src/util/properties.d.ts.map +1 -1
  102. package/dist/types/tsconfig.tsbuildinfo +1 -1
  103. package/package.json +37 -32
  104. package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -2
  105. package/src/components/FieldEditor/FieldEditor.test.tsx +4 -1
  106. package/src/components/FieldEditor/FieldEditor.tsx +19 -14
  107. package/src/components/Form/Form.stories.tsx +21 -27
  108. package/src/components/Form/Form.tsx +106 -19
  109. package/src/components/Form/FormField.tsx +85 -28
  110. package/src/components/Form/FormFieldComponent.tsx +75 -20
  111. package/src/components/Form/FormFieldSet.tsx +32 -3
  112. package/src/components/Form/FormTooltipsContext.ts +25 -0
  113. package/src/components/Form/Layout/FormLayout.stories.test.tsx +51 -0
  114. package/src/components/Form/Layout/FormLayout.stories.tsx +422 -0
  115. package/src/components/Form/Layout/FormLayout.test.ts +59 -0
  116. package/src/components/Form/Layout/FormLayout.tsx +251 -0
  117. package/src/components/Form/Layout/annotation.ts +38 -0
  118. package/src/components/Form/Layout/index.ts +7 -0
  119. package/src/components/Form/Layout/parser.test.ts +120 -0
  120. package/src/components/Form/Layout/parser.ts +167 -0
  121. package/src/components/Form/fields/ArrayField.tsx +61 -38
  122. package/src/components/Form/fields/DateField.tsx +101 -0
  123. package/src/components/Form/fields/GeoPointField.tsx +4 -1
  124. package/src/components/Form/fields/InlineRefField.tsx +146 -0
  125. package/src/components/Form/fields/MarkdownField.tsx +3 -3
  126. package/src/components/Form/fields/RefField.tsx +29 -23
  127. package/src/components/Form/fields/SelectField.tsx +17 -5
  128. package/src/components/Form/fields/SelectOptionField.tsx +4 -3
  129. package/src/components/Form/fields/TextAreaField.tsx +1 -1
  130. package/src/components/Form/fields/TupleField.tsx +2 -1
  131. package/src/components/Form/fields/index.ts +2 -0
  132. package/src/components/Form/index.ts +3 -0
  133. package/src/components/Form/meta-tags.test.ts +47 -0
  134. package/src/components/Form/meta-tags.ts +33 -0
  135. package/src/components/ObjectForm/ObjectForm.tsx +21 -26
  136. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +2 -2
  137. package/src/components/ObjectPicker/ObjectPicker.tsx +2 -2
  138. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +14 -26
  139. package/src/components/ObjectProperties/ObjectProperties.test.tsx +20 -10
  140. package/src/components/ObjectProperties/ObjectProperties.tsx +26 -30
  141. package/src/components/ObjectTree/ObjectTree.stories.tsx +134 -0
  142. package/src/components/ObjectTree/ObjectTree.tsx +190 -0
  143. package/src/components/ObjectTree/index.ts +5 -0
  144. package/src/components/Settings/Settings.tsx +2 -1
  145. package/src/components/Settings/SettingsItem.tsx +1 -1
  146. package/src/components/ViewEditor/ViewEditor.stories.tsx +35 -40
  147. package/src/components/ViewEditor/ViewEditor.test.tsx +11 -8
  148. package/src/components/ViewEditor/ViewEditor.tsx +29 -32
  149. package/src/components/index.ts +1 -0
  150. package/src/components/testing.tsx +4 -4
  151. package/src/translations.ts +2 -1
  152. package/src/util/properties.test.ts +67 -9
  153. package/src/util/properties.ts +36 -4
  154. package/src/vite-env.d.ts +10 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-form",
3
- "version": "0.8.4-main.d05539e30a",
3
+ "version": "0.8.4-main.d9fc60f731",
4
4
  "description": "Data components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -34,54 +34,59 @@
34
34
  "src"
35
35
  ],
36
36
  "dependencies": {
37
+ "@codemirror/lang-xml": "^6.1.0",
37
38
  "@effect-atom/atom-react": "^0.5.0",
38
39
  "@fluentui/react-tabster": "9.26.11",
39
40
  "@radix-ui/react-context": "1.1.1",
40
41
  "@radix-ui/react-use-controllable-state": "1.1.0",
41
42
  "date-fns": "^3.6.0",
42
43
  "tabster": "^8.5.5",
43
- "@dxos/async": "0.8.4-main.d05539e30a",
44
- "@dxos/echo": "0.8.4-main.d05539e30a",
45
- "@dxos/echo-db": "0.8.4-main.d05539e30a",
46
- "@dxos/echo-atom": "0.8.4-main.d05539e30a",
47
- "@dxos/keys": "0.8.4-main.d05539e30a",
48
- "@dxos/echo-react": "0.8.4-main.d05539e30a",
49
- "@dxos/lit-ui": "0.8.4-main.d05539e30a",
50
- "@dxos/log": "0.8.4-main.d05539e30a",
51
- "@dxos/react-client": "0.8.4-main.d05539e30a",
52
- "@dxos/react-ui-editor": "0.8.4-main.d05539e30a",
53
- "@dxos/react-ui-list": "0.8.4-main.d05539e30a",
54
- "@dxos/react-ui-components": "0.8.4-main.d05539e30a",
55
- "@dxos/react-ui-pickers": "0.8.4-main.d05539e30a",
56
- "@dxos/schema": "0.8.4-main.d05539e30a",
57
- "@dxos/react-ui-search": "0.8.4-main.d05539e30a",
58
- "@dxos/types": "0.8.4-main.d05539e30a",
59
- "@dxos/ui-editor": "0.8.4-main.d05539e30a",
60
- "@dxos/util": "0.8.4-main.d05539e30a",
61
- "@dxos/effect": "0.8.4-main.d05539e30a",
62
- "@dxos/invariant": "0.8.4-main.d05539e30a"
44
+ "@dxos/async": "0.8.4-main.d9fc60f731",
45
+ "@dxos/echo-db": "0.8.4-main.d9fc60f731",
46
+ "@dxos/echo-atom": "0.8.4-main.d9fc60f731",
47
+ "@dxos/echo": "0.8.4-main.d9fc60f731",
48
+ "@dxos/echo-protocol": "0.8.4-main.d9fc60f731",
49
+ "@dxos/echo-react": "0.8.4-main.d9fc60f731",
50
+ "@dxos/invariant": "0.8.4-main.d9fc60f731",
51
+ "@dxos/keys": "0.8.4-main.d9fc60f731",
52
+ "@dxos/effect": "0.8.4-main.d9fc60f731",
53
+ "@dxos/lit-ui": "0.8.4-main.d9fc60f731",
54
+ "@dxos/react-client": "0.8.4-main.d9fc60f731",
55
+ "@dxos/log": "0.8.4-main.d9fc60f731",
56
+ "@dxos/react-ui-components": "0.8.4-main.d9fc60f731",
57
+ "@dxos/react-ui-editor": "0.8.4-main.d9fc60f731",
58
+ "@dxos/react-ui-search": "0.8.4-main.d9fc60f731",
59
+ "@dxos/react-ui-list": "0.8.4-main.d9fc60f731",
60
+ "@dxos/schema": "0.8.4-main.d9fc60f731",
61
+ "@dxos/ui-editor": "0.8.4-main.d9fc60f731",
62
+ "@dxos/types": "0.8.4-main.d9fc60f731",
63
+ "@dxos/react-ui-pickers": "0.8.4-main.d9fc60f731",
64
+ "@dxos/util": "0.8.4-main.d9fc60f731"
63
65
  },
64
66
  "devDependencies": {
65
67
  "@types/react": "~19.2.7",
66
68
  "@types/react-dom": "~19.2.3",
67
- "effect": "3.20.0",
69
+ "effect": "3.21.2",
68
70
  "fast-check": "^3.19.0",
69
71
  "react": "~19.2.3",
70
72
  "react-dom": "~19.2.3",
71
- "vite": "^8.0.13",
72
- "@dxos/random": "0.8.4-main.d05539e30a",
73
- "@dxos/react-client": "0.8.4-main.d05539e30a",
74
- "@dxos/react-ui": "0.8.4-main.d05539e30a",
75
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.d05539e30a",
76
- "@dxos/ui-theme": "0.8.4-main.d05539e30a",
77
- "@dxos/storybook-utils": "0.8.4-main.d05539e30a"
73
+ "vite": "^8.0.14",
74
+ "@dxos/effect-proto": "0.8.4-main.d9fc60f731",
75
+ "@dxos/protocols": "0.8.4-main.d9fc60f731",
76
+ "@dxos/random": "0.8.4-main.d9fc60f731",
77
+ "@dxos/react-client": "0.8.4-main.d9fc60f731",
78
+ "@dxos/react-ui": "0.8.4-main.d9fc60f731",
79
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.d9fc60f731",
80
+ "@dxos/storybook-utils": "0.8.4-main.d9fc60f731",
81
+ "@dxos/ui-theme": "0.8.4-main.d9fc60f731",
82
+ "@dxos/ui-types": "0.8.4-main.d9fc60f731"
78
83
  },
79
84
  "peerDependencies": {
80
- "effect": "3.20.0",
85
+ "effect": "3.21.2",
81
86
  "react": "~19.2.3",
82
87
  "react-dom": "~19.2.3",
83
- "@dxos/ui-theme": "0.8.4-main.d05539e30a",
84
- "@dxos/react-ui": "0.8.4-main.d05539e30a"
88
+ "@dxos/ui-theme": "0.8.4-main.d9fc60f731",
89
+ "@dxos/react-ui": "0.8.4-main.d9fc60f731"
85
90
  },
86
91
  "publishConfig": {
87
92
  "access": "public"
@@ -5,7 +5,7 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useEffect, useMemo } from 'react';
7
7
 
8
- import { Filter, Query } from '@dxos/echo';
8
+ import { Filter, Query, Type } from '@dxos/echo';
9
9
  import { createEchoSchema } from '@dxos/echo/testing';
10
10
  import { log } from '@dxos/log';
11
11
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
@@ -24,7 +24,7 @@ export type FieldEditorDebugObjects = {
24
24
 
25
25
  const useTestProjection = () => {
26
26
  return useMemo(() => {
27
- const schema = createEchoSchema(Example);
27
+ const schema = createEchoSchema(Type.getSchema(Example));
28
28
  const view = ViewModel.make({
29
29
  name: 'Test',
30
30
  query: Query.select(Filter.type(Example)),
@@ -15,7 +15,10 @@ import { type FieldEditorDebugObjects } from './FieldEditor.stories';
15
15
  const { Default } = composeStories(stories);
16
16
 
17
17
  describe('FieldEditor', () => {
18
- afterEach(() => {
18
+ afterEach(async () => {
19
+ // Flush pending React scheduler work before teardown to prevent
20
+ // "window is not defined" errors from setImmediate callbacks firing after happy-dom cleanup.
21
+ await act(async () => {});
19
22
  cleanup();
20
23
  });
21
24
 
@@ -4,8 +4,8 @@
4
4
 
5
5
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
6
 
7
- import { Obj, type SchemaRegistry, type View } from '@dxos/echo';
8
- import { type EchoSchema, Format, FormatEnums, formatToType } from '@dxos/echo/internal';
7
+ import { Filter, Obj, type Registry, Type, type View } from '@dxos/echo';
8
+ import { Format, FormatEnums, formatToType } from '@dxos/echo/internal';
9
9
  import { type SchemaProperty } from '@dxos/effect';
10
10
  import { log } from '@dxos/log';
11
11
  import { useAsyncEffect, useTranslation } from '@dxos/react-ui';
@@ -24,7 +24,7 @@ import { Form, type FormFieldMap, type FormRootProps, SelectField, SelectOptionF
24
24
  export type FieldEditorProps = {
25
25
  projection: ProjectionModel;
26
26
  field: View.FieldType;
27
- registry?: SchemaRegistry.SchemaRegistry;
27
+ registry?: Registry.Registry;
28
28
  view?: Obj.Unknown;
29
29
  onSave: () => void;
30
30
  onCancel?: () => void;
@@ -38,24 +38,26 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
38
38
  const [props, setProps] = useState<PropertyType>(projection.getFieldProjection(field.id).props);
39
39
  useEffect(() => setProps(projection.getFieldProjection(field.id).props), [field, projection]);
40
40
 
41
- const [schemas, setSchemas] = useState<EchoSchema[]>([]);
41
+ const [schemas, setSchemas] = useState<Type.Type[]>([]);
42
42
  useAsyncEffect(async () => {
43
43
  if (!registry) {
44
44
  return;
45
45
  }
46
46
 
47
- const subscription = registry.query().subscribe((query) => setSchemas(query.results), { fire: true });
47
+ const subscription = registry
48
+ .query(Filter.type(Type.Type))
49
+ .subscribe((query) => setSchemas(query.results), { fire: true });
48
50
 
49
51
  // TODO(dmaretskyi): This shouldn't be needed.
50
- const schemas = await registry.query().run();
52
+ const schemas = await registry.query(Filter.type(Type.Type)).run();
51
53
  setSchemas(schemas);
52
54
 
53
55
  return () => subscription?.();
54
56
  }, [registry]);
55
57
 
56
- const [referenceSchema, setReferenceSchema] = useState<EchoSchema>();
58
+ const [referenceSchema, setReferenceSchema] = useState<Type.Type>();
57
59
  useEffect(() => {
58
- setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
60
+ setReferenceSchema(schemas.find((schema) => Type.getTypename(schema) === props?.referenceSchema));
59
61
  }, [schemas, props?.referenceSchema]);
60
62
 
61
63
  // TODO(burdon): Need to wrap otherwise throws error:
@@ -76,9 +78,12 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
76
78
  ['referenceSchema' satisfies keyof PropertyType]: (props) => (
77
79
  <SelectField
78
80
  {...props}
79
- options={schemas.map((schema) => ({
80
- value: schema.typename,
81
- }))}
81
+ options={schemas
82
+ .map((schema) => Type.getTypename(schema))
83
+ .filter((typename): typename is string => typename != null)
84
+ .map((typename) => ({
85
+ value: typename,
86
+ }))}
82
87
  />
83
88
  ),
84
89
  ['referencePath' satisfies keyof PropertyType]: (props) => (
@@ -86,7 +91,7 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
86
91
  {...props}
87
92
  options={
88
93
  referenceSchema
89
- ? getFormProperties(referenceSchema.ast)
94
+ ? getFormProperties(Type.getSchema(referenceSchema).ast)
90
95
  .sort((a, b) => a.name.toString().localeCompare(b.name.toString()))
91
96
  .map((p) => ({ value: p.name.toString() }))
92
97
  : []
@@ -112,8 +117,8 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
112
117
  });
113
118
 
114
119
  setReferenceSchema((prev) => {
115
- if (_props.referenceSchema !== prev?.typename) {
116
- const newSchema = schemas.find((schema) => schema.typename === _props.referenceSchema);
120
+ if (_props.referenceSchema !== (prev ? Type.getTypename(prev) : undefined)) {
121
+ const newSchema = schemas.find((schema) => Type.getTypename(schema) === _props.referenceSchema);
117
122
  if (newSchema) {
118
123
  return newSchema;
119
124
  }
@@ -6,7 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import React, { useCallback, useState } from 'react';
8
8
 
9
- import { Annotation, Format, Obj, Ref, Tag, Type } from '@dxos/echo';
9
+ import { DXN, Annotation, Format, Obj, Ref, Tag, Type } from '@dxos/echo';
10
10
  import { type AnyProperties } from '@dxos/echo/internal';
11
11
  import { log } from '@dxos/log';
12
12
  import { useSpaces } from '@dxos/react-client/echo';
@@ -21,14 +21,9 @@ import { type ExcludeId, Form, type FormRootProps, omitId } from './Form';
21
21
 
22
22
  const Organization = Schema.Struct({
23
23
  name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
24
- }).pipe(
25
- Type.object({
26
- typename: 'com.example.type.organization',
27
- version: '0.1.0',
28
- }),
29
- );
24
+ }).pipe(Type.makeObject(DXN.make('com.example.type.organization', '0.1.0')));
30
25
 
31
- export interface Organization extends Schema.Schema.Type<typeof Organization> {}
26
+ export type Organization = Type.InstanceType<typeof Organization>;
32
27
 
33
28
  const Person = Schema.Struct({
34
29
  name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
@@ -51,6 +46,9 @@ const Person = Schema.Struct({
51
46
  status: Schema.optional(Schema.Literal('active', 'inactive').annotations({ title: 'Status' })),
52
47
  notes: Schema.optional(Format.Text.annotations({ title: 'Notes' })),
53
48
  location: Schema.optional(Format.GeoPoint.annotations({ title: 'Location' })),
49
+ birthday: Schema.optional(Format.DateOnly.annotations({ title: 'Birthday' })),
50
+ meetingAt: Schema.optional(Format.DateTime.annotations({ title: 'Next meeting' })),
51
+ reminderAt: Schema.optional(Format.TimeOnly.annotations({ title: 'Reminder time' })),
54
52
  tasks: Schema.optional(Schema.Array(Schema.String).annotations({ title: 'Tasks' })),
55
53
  locations: Schema.optional(Schema.Array(Format.GeoPoint).annotations({ title: 'Locations' })),
56
54
  identities: Schema.optional(
@@ -63,22 +61,15 @@ const Person = Schema.Struct({
63
61
  title: 'Identities',
64
62
  }),
65
63
  ),
66
- }).pipe(
67
- Type.object({
68
- typename: 'org.dxos.type.person', // TODO(burdon): Change all types to /schema
69
- version: '0.1.0',
70
- }),
71
- );
64
+ }).pipe(Type.makeObject(DXN.make('org.dxos.type.person', '0.1.0')));
72
65
 
73
- export interface Person extends Schema.Schema.Type<typeof Person> {}
66
+ export type Person = Type.InstanceType<typeof Person>;
74
67
 
75
68
  type DefaultStoryProps<T extends AnyProperties> = {
76
69
  schema?: Schema.Schema<T>;
77
- debug?: boolean;
78
70
  } & FormRootProps<T>;
79
71
 
80
72
  const DefaultStory = <T extends AnyProperties = AnyProperties>({
81
- debug,
82
73
  schema,
83
74
  values: valuesProp,
84
75
  ...props
@@ -86,7 +77,6 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
86
77
  const [values, setValues] = useState<Partial<T>>(valuesProp ?? {});
87
78
  const spaces = useSpaces();
88
79
  const space = spaces[0];
89
-
90
80
  const handleSave = useCallback<NonNullable<FormRootProps<T>['onSave']>>((values) => {
91
81
  log.info('save', { values, meta });
92
82
  setValues(values);
@@ -105,7 +95,6 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
105
95
  <Tooltip.Provider>
106
96
  <TestLayout json={{ values, schema: schema?.ast }}>
107
97
  <Form.Root
108
- debug={debug}
109
98
  schema={schema}
110
99
  defaultValues={values}
111
100
  db={space.db}
@@ -113,7 +102,7 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
113
102
  onCancel={handleCancel}
114
103
  {...props}
115
104
  >
116
- <Form.Viewport>
105
+ <Form.Viewport scroll>
117
106
  <Form.Content>
118
107
  <Form.Section label='Section' description='This is a section' />
119
108
  <Form.FieldSet />
@@ -155,36 +144,41 @@ export default meta;
155
144
 
156
145
  type Story<T extends AnyProperties> = StoryObj<DefaultStoryProps<T>>;
157
146
 
147
+ const PersonSchema = Type.getSchema(Person);
148
+
158
149
  const values: Partial<Person> = {
159
150
  name: 'Alice',
160
151
  location: [40.7128, -74.006],
161
152
  tasks: ['task 1', 'task 2'],
153
+ birthday: '1990-05-12',
154
+ meetingAt: '2026-06-01T15:30:00.000Z',
155
+ reminderAt: '09:00:00',
162
156
  };
163
157
 
164
- export const Default: Story<ExcludeId<typeof Person>> = {
158
+ export const Default: Story<ExcludeId<typeof PersonSchema>> = {
165
159
  args: {
166
- schema: omitId(Person),
160
+ schema: omitId(PersonSchema),
167
161
  values,
168
162
  autoSave: true,
169
163
  },
170
164
  };
171
165
 
172
- export const Readonly: Story<ExcludeId<typeof Person>> = {
166
+ export const Readonly: Story<ExcludeId<typeof PersonSchema>> = {
173
167
  args: {
174
- schema: omitId(Person),
168
+ schema: omitId(PersonSchema),
175
169
  values,
176
170
  readonly: true,
177
171
  },
178
172
  };
179
173
 
180
- export const Static: Story<ExcludeId<typeof Person>> = {
174
+ export const Static: Story<ExcludeId<typeof PersonSchema>> = {
181
175
  args: {
182
- schema: omitId(Person),
176
+ schema: omitId(PersonSchema),
183
177
  values,
184
178
  layout: 'static',
185
179
  },
186
180
  };
187
181
 
188
- export const Empty: Story<ExcludeId<typeof Person>> = {
182
+ export const Empty: Story<ExcludeId<typeof PersonSchema>> = {
189
183
  args: {},
190
184
  };
@@ -8,18 +8,22 @@ import * as Schema from 'effect/Schema';
8
8
  import * as SchemaAST from 'effect/SchemaAST';
9
9
  import React, { type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
10
10
 
11
- import { Annotation as EchoAnnotation } from '@dxos/echo';
11
+ import { Annotation as EchoAnnotation, Type } from '@dxos/echo';
12
12
  import { type AnyProperties } from '@dxos/echo/internal';
13
13
  import { createJsonPath, getValue as getValue$ } from '@dxos/effect';
14
14
  import {
15
+ Column,
16
+ type ColumnRootProps,
15
17
  IconButton,
16
18
  type IconButtonProps,
19
+ Input,
17
20
  ScrollArea,
18
21
  type ThemedClassName,
19
22
  useMergeRefs,
20
23
  useTranslation,
21
24
  } from '@dxos/react-ui';
22
- import { composable, composableProps, mx, withColumn } from '@dxos/ui-theme';
25
+ import { composable, composableProps, withColumn } from '@dxos/react-ui';
26
+ import { mx } from '@dxos/ui-theme';
23
27
 
24
28
  import { translationKey } from '#translations';
25
29
 
@@ -35,15 +39,31 @@ import {
35
39
  FormFieldSet as NaturalFormFieldSet,
36
40
  type FormFieldSetProps as NaturalFormFieldSetProps,
37
41
  } from './FormFieldSet';
42
+ import { FormTooltipsContext } from './FormTooltipsContext';
43
+ import { FormLayout as NaturalFormLayout } from './Layout';
44
+ import { type FormLayoutProps as NaturalFormLayoutProps } from './Layout/FormLayout';
38
45
 
39
46
  // TODO(burdon): Move styles to form.ts (as with ui-theme).
40
47
 
41
48
  // TODO(burdon): Reconcile with @dxos/echo.
42
- export type ExcludeId<S extends Schema.Schema.AnyNoContext> = Omit<Schema.Schema.Type<S>, 'id'>;
49
+ export type ExcludeId<S extends Schema.Schema.AnyNoContext | Type.AnyEntity> = Omit<
50
+ S extends Type.AnyEntity
51
+ ? Type.InstanceType<S>
52
+ : S extends Schema.Schema.AnyNoContext
53
+ ? Schema.Schema.Type<S>
54
+ : never,
55
+ 'id'
56
+ >;
43
57
 
44
58
  // TODO(burdon): Move to @dxos/schema (re-export here).
45
- export const omitId = <S extends Schema.Schema.AnyNoContext>(schema: S): Schema.Schema<ExcludeId<S>, ExcludeId<S>> =>
46
- schema.pipe(Schema.omit('id')) as any;
59
+ export const omitId = <S extends Schema.Schema.AnyNoContext | Type.AnyEntity>(
60
+ schemaOrType: S,
61
+ ): Schema.Schema<ExcludeId<S>, ExcludeId<S>> => {
62
+ const schema = Type.isType(schemaOrType)
63
+ ? Type.getSchema(schemaOrType)
64
+ : (schemaOrType as Schema.Schema.AnyNoContext);
65
+ return schema.pipe(Schema.omit('id')) as any;
66
+ };
47
67
 
48
68
  /**
49
69
  * Drop fields annotated with `FormInputAnnotation.set(false)` from a schema so
@@ -70,9 +90,10 @@ type FormContextValue<T extends AnyProperties = any> = {
70
90
  form: FormHandler<T>;
71
91
 
72
92
  /**
73
- * Show debug info.
93
+ * Show field tooltips (currently: JSON path on each label). Defaults to
94
+ * `true`; pass `false` to suppress.
74
95
  */
75
- debug?: boolean;
96
+ tooltips?: boolean;
76
97
 
77
98
  /**
78
99
  * Testing.
@@ -169,9 +190,11 @@ const FormRoot = <T extends AnyProperties = AnyProperties>({
169
190
  const form = useFormHandler({ schema, values, onSave, onCancel, ...props });
170
191
 
171
192
  return (
172
- <FormContextProvider form={form} {...props}>
173
- {children}
174
- </FormContextProvider>
193
+ <FormTooltipsContext.Provider value={props.tooltips ?? true}>
194
+ <FormContextProvider form={form} {...props}>
195
+ {children}
196
+ </FormContextProvider>
197
+ </FormTooltipsContext.Provider>
175
198
  );
176
199
  };
177
200
 
@@ -183,15 +206,32 @@ FormRoot.displayName = 'Form.Root';
183
206
 
184
207
  const FORM_VIEWPORT_NAME = 'Form.Viewport';
185
208
 
186
- type FormViewportProps = {};
209
+ type FormViewportProps = { scroll?: boolean; gutter?: ColumnRootProps['gutter'] };
210
+
211
+ // The viewing window: owns the gutter Column (chrome/side-padding). Content-height by default;
212
+ // `scroll` makes it fill its parent and scroll (the gutter then hosts the scrollbar).
213
+ const FormViewport = composable<HTMLDivElement, FormViewportProps>(
214
+ ({ children, scroll, gutter = 'sm', ...props }, forwardedRef) => {
215
+ // Span the full width when nested inside another Column grid (e.g. Card.Root) instead of
216
+ // landing in a single narrow track.
217
+ const span = '[.dx-column-root_&]:col-span-full';
218
+ if (scroll) {
219
+ return (
220
+ <Column.Root gutter={gutter} classNames={['dx-expander', span]}>
221
+ <ScrollArea.Root {...composableProps(props)} orientation='vertical' centered padding thin ref={forwardedRef}>
222
+ <ScrollArea.Viewport>{children}</ScrollArea.Viewport>
223
+ </ScrollArea.Root>
224
+ </Column.Root>
225
+ );
226
+ }
187
227
 
188
- const FormViewport = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {
189
- return (
190
- <ScrollArea.Root {...composableProps(props)} orientation='vertical' centered padding thin ref={forwardedRef}>
191
- <ScrollArea.Viewport>{children}</ScrollArea.Viewport>
192
- </ScrollArea.Root>
193
- );
194
- });
228
+ return (
229
+ <Column.Root {...composableProps(props)} gutter={gutter} classNames={['w-full min-w-0', span]} ref={forwardedRef}>
230
+ {children}
231
+ </Column.Root>
232
+ );
233
+ },
234
+ );
195
235
 
196
236
  FormViewport.displayName = FORM_VIEWPORT_NAME;
197
237
 
@@ -203,6 +243,7 @@ const FORM_CONTENT_NAME = 'Form.Content';
203
243
 
204
244
  type FormContentProps = ThemedClassName<PropsWithChildren<{}>>;
205
245
 
246
+ // The viewed body: centered in the viewport's gutter. Pure body — the gutter Column is owned by `Form.Viewport`.
206
247
  const FormContent = composable<HTMLDivElement, FormContentProps>(({ children, ...props }, forwardedRef) => {
207
248
  const { form, testId } = useFormContext(FORM_CONTENT_NAME);
208
249
  const localRef = useRef<HTMLDivElement>(null);
@@ -213,7 +254,7 @@ const FormContent = composable<HTMLDivElement, FormContentProps>(({ children, ..
213
254
  <div
214
255
  {...composableProps(props, {
215
256
  role: 'form',
216
- classNames: mx(withColumn.center(), 'flex flex-col w-full pb-form-gap'),
257
+ classNames: mx(withColumn.center(), 'flex flex-col w-full'),
217
258
  })}
218
259
  data-testid={testId}
219
260
  ref={mergedRef}
@@ -241,6 +282,26 @@ const FormFieldSet = (props: FormFieldSetProps) => {
241
282
 
242
283
  FormFieldSet.displayName = FORM_FIELDSET_NAME;
243
284
 
285
+ //
286
+ // Layout
287
+ //
288
+
289
+ const FORM_LAYOUT_NAME = 'Form.Layout';
290
+
291
+ type FormLayoutProps = Omit<NaturalFormLayoutProps, 'schema'> & { schema?: NaturalFormLayoutProps['schema'] };
292
+
293
+ const FormLayout = ({ schema, ...props }: FormLayoutProps) => {
294
+ const { form, ...contextProps } = useFormContext(FORM_LAYOUT_NAME);
295
+ const resolvedSchema = schema ?? form.schema;
296
+ if (!resolvedSchema) {
297
+ return null;
298
+ }
299
+
300
+ return <NaturalFormLayout schema={resolvedSchema} {...contextProps} {...props} />;
301
+ };
302
+
303
+ FormLayout.displayName = FORM_LAYOUT_NAME;
304
+
244
305
  //
245
306
  // Actions
246
307
  //
@@ -358,6 +419,29 @@ const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
358
419
 
359
420
  FormSubmit.displayName = FORM_SUBMIT_NAME;
360
421
 
422
+ //
423
+ // Error
424
+ //
425
+
426
+ const FORM_ERROR_NAME = 'Form.Error';
427
+
428
+ type FormErrorProps = ThemedClassName<PropsWithChildren>;
429
+
430
+ /** Form-level error/validation message (e.g. a failed submit), styled via the error valence. */
431
+ const FormError = ({ children, classNames }: FormErrorProps) => {
432
+ if (!children) {
433
+ return null;
434
+ }
435
+
436
+ return (
437
+ <Input.Root validationValence='error'>
438
+ <Input.Validation classNames={classNames}>{children}</Input.Validation>
439
+ </Input.Root>
440
+ );
441
+ };
442
+
443
+ FormError.displayName = FORM_ERROR_NAME;
444
+
361
445
  //
362
446
  // Form
363
447
  // https://www.radix-ui.com/primitives/docs/guides/composition
@@ -369,9 +453,11 @@ export const Form = {
369
453
  Content: FormContent,
370
454
  Section: FormSection,
371
455
  FieldSet: FormFieldSet,
456
+ Layout: FormLayout,
372
457
  Label: FormFieldLabel,
373
458
  Actions: FormActions,
374
459
  Submit: FormSubmit,
460
+ Error: FormError,
375
461
  };
376
462
 
377
463
  export { useFormContext, useFormValues, useFormFieldState };
@@ -382,6 +468,7 @@ export type {
382
468
  FormContentProps,
383
469
  FormSectionProps,
384
470
  FormFieldSetProps,
471
+ FormLayoutProps,
385
472
  FormFieldLabelProps,
386
473
  FormActionsProps,
387
474
  FormSubmitProps,