@dxos/react-ui-form 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a

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 (211) hide show
  1. package/dist/lib/browser/index.mjs +1883 -1539
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +1883 -1539
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/annotations.d.ts +47 -0
  8. package/dist/types/src/annotations.d.ts.map +1 -0
  9. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  10. package/dist/types/src/components/Form/Form.d.ts +12 -4
  11. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/Form.stories.d.ts +46 -65
  13. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/Form/Form.theme.d.ts +252 -0
  15. package/dist/types/src/components/Form/Form.theme.d.ts.map +1 -0
  16. package/dist/types/src/components/Form/Form.theme.test.d.ts +2 -0
  17. package/dist/types/src/components/Form/Form.theme.test.d.ts.map +1 -0
  18. package/dist/types/src/components/Form/FormControls.d.ts +17 -8
  19. package/dist/types/src/components/Form/FormControls.d.ts.map +1 -1
  20. package/dist/types/src/components/Form/FormField/FormField.d.ts +16 -2
  21. package/dist/types/src/components/Form/FormField/FormField.d.ts.map +1 -1
  22. package/dist/types/src/components/Form/FormField/FormFieldHeader.d.ts +27 -0
  23. package/dist/types/src/components/Form/FormField/FormFieldHeader.d.ts.map +1 -0
  24. package/dist/types/src/components/Form/FormField/FormRow.d.ts +85 -0
  25. package/dist/types/src/components/Form/FormField/FormRow.d.ts.map +1 -0
  26. package/dist/types/src/components/Form/FormField/{FormFieldWrapper.stories.d.ts → FormRow.stories.d.ts} +4 -1
  27. package/dist/types/src/components/Form/FormField/FormRow.stories.d.ts.map +1 -0
  28. package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.d.ts.map +1 -1
  29. package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts +1 -0
  30. package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.d.ts +12 -0
  32. package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/index.d.ts +2 -0
  34. package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/index.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/FormField/fields/AutofillField/AutofillField.d.ts +12 -0
  36. package/dist/types/src/components/Form/FormField/fields/AutofillField/AutofillField.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/FormField/fields/AutofillField/index.d.ts +2 -0
  38. package/dist/types/src/components/Form/FormField/fields/AutofillField/index.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.d.ts.map +1 -1
  40. package/dist/types/src/components/Form/FormField/fields/ComboboxField/ComboboxField.d.ts +12 -0
  41. package/dist/types/src/components/Form/FormField/fields/ComboboxField/ComboboxField.d.ts.map +1 -0
  42. package/dist/types/src/components/Form/FormField/fields/ComboboxField/index.d.ts +2 -0
  43. package/dist/types/src/components/Form/FormField/fields/ComboboxField/index.d.ts.map +1 -0
  44. package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.d.ts +1 -1
  45. package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.d.ts.map +1 -1
  46. package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.d.ts +1 -1
  47. package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.d.ts.map +1 -1
  48. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.d.ts +11 -0
  49. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.d.ts.map +1 -1
  50. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.test.d.ts +2 -0
  51. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.test.d.ts.map +1 -0
  52. package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.d.ts +4 -0
  53. package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.d.ts.map +1 -0
  54. package/dist/types/src/components/{Settings/Settings.stories.d.ts → Form/FormField/fields/PasswordField/PasswordField.stories.d.ts} +1 -2
  55. package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.d.ts.map +1 -0
  56. package/dist/types/src/components/Form/FormField/fields/PasswordField/index.d.ts +2 -0
  57. package/dist/types/src/components/Form/FormField/fields/PasswordField/index.d.ts.map +1 -0
  58. package/dist/types/src/components/Form/FormField/fields/RefField/InlineRefField.d.ts.map +1 -1
  59. package/dist/types/src/components/Form/FormField/fields/RefField/RefField.d.ts.map +1 -1
  60. package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.d.ts +18 -2
  61. package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.d.ts.map +1 -1
  62. package/dist/types/src/components/Form/FormField/fields/index.d.ts +4 -0
  63. package/dist/types/src/components/Form/FormField/fields/index.d.ts.map +1 -1
  64. package/dist/types/src/components/Form/FormField/index.d.ts +4 -3
  65. package/dist/types/src/components/Form/FormField/index.d.ts.map +1 -1
  66. package/dist/types/src/components/Form/FormField/presentation.d.ts +22 -0
  67. package/dist/types/src/components/Form/FormField/presentation.d.ts.map +1 -0
  68. package/dist/types/src/components/Form/FormFieldSet/FormFieldSet.d.ts +8 -5
  69. package/dist/types/src/components/Form/FormFieldSet/FormFieldSet.d.ts.map +1 -1
  70. package/dist/types/src/components/Form/FormFieldSet/FormFieldSetContainer.d.ts +22 -0
  71. package/dist/types/src/components/Form/FormFieldSet/FormFieldSetContainer.d.ts.map +1 -0
  72. package/dist/types/src/components/Form/FormFieldSet/index.d.ts +1 -0
  73. package/dist/types/src/components/Form/FormFieldSet/index.d.ts.map +1 -1
  74. package/dist/types/src/components/Form/FormLayout/FormLayout.d.ts +2 -0
  75. package/dist/types/src/components/Form/FormLayout/FormLayout.d.ts.map +1 -1
  76. package/dist/types/src/components/Form/FormLayout/FormLayout.stories.d.ts +5 -5
  77. package/dist/types/src/components/Form/FormLayout/FormLayout.stories.d.ts.map +1 -1
  78. package/dist/types/src/components/Form/FormLayout/index.d.ts +1 -1
  79. package/dist/types/src/components/Form/FormLayout/index.d.ts.map +1 -1
  80. package/dist/types/src/components/Form/meta-tags.d.ts +1 -12
  81. package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -1
  82. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  83. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +2 -1
  84. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
  85. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts +2 -2
  86. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts.map +1 -1
  87. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  88. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +2 -2
  89. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  90. package/dist/types/src/components/index.d.ts +0 -1
  91. package/dist/types/src/components/index.d.ts.map +1 -1
  92. package/dist/types/src/hooks/index.d.ts +1 -0
  93. package/dist/types/src/hooks/index.d.ts.map +1 -1
  94. package/dist/types/src/hooks/useAsyncFieldEffect.d.ts +24 -0
  95. package/dist/types/src/hooks/useAsyncFieldEffect.d.ts.map +1 -0
  96. package/dist/types/src/hooks/useFormContext.d.ts +8 -3
  97. package/dist/types/src/hooks/useFormContext.d.ts.map +1 -1
  98. package/dist/types/src/hooks/useFormHandler.d.ts +8 -2
  99. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
  100. package/dist/types/src/index.d.ts +1 -0
  101. package/dist/types/src/index.d.ts.map +1 -1
  102. package/dist/types/src/testing/TestLayout.d.ts +1 -1
  103. package/dist/types/src/testing/TestLayout.d.ts.map +1 -1
  104. package/dist/types/src/testing/schema.d.ts +13 -16
  105. package/dist/types/src/testing/schema.d.ts.map +1 -1
  106. package/dist/types/src/types.d.ts +50 -1
  107. package/dist/types/src/types.d.ts.map +1 -1
  108. package/dist/types/src/util/index.d.ts +0 -1
  109. package/dist/types/src/util/index.d.ts.map +1 -1
  110. package/dist/types/src/util/omit.d.ts +1 -1
  111. package/dist/types/src/util/omit.d.ts.map +1 -1
  112. package/dist/types/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +41 -40
  114. package/src/annotations.ts +63 -0
  115. package/src/components/FieldEditor/FieldEditor.tsx +5 -2
  116. package/src/components/Form/DESIGN.md +24 -0
  117. package/src/components/Form/Form.stories.tsx +257 -13
  118. package/src/components/Form/Form.theme.test.ts +33 -0
  119. package/src/components/Form/Form.theme.ts +86 -0
  120. package/src/components/Form/Form.tsx +15 -9
  121. package/src/components/Form/FormControls.tsx +94 -37
  122. package/src/components/Form/FormField/FormField.tsx +83 -32
  123. package/src/components/Form/FormField/FormFieldHeader.tsx +68 -0
  124. package/src/components/Form/FormField/{FormFieldWrapper.stories.tsx → FormRow.stories.tsx} +34 -9
  125. package/src/components/Form/FormField/FormRow.tsx +298 -0
  126. package/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.tsx +25 -9
  127. package/src/components/Form/FormField/fields/ArrayField/ArrayField.tsx +83 -24
  128. package/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.tsx +59 -0
  129. package/src/components/Form/FormField/fields/AsyncSelectField/index.ts +5 -0
  130. package/src/components/Form/FormField/fields/AutofillField/AutofillField.tsx +67 -0
  131. package/src/components/Form/FormField/fields/AutofillField/index.ts +5 -0
  132. package/src/components/Form/FormField/fields/BooleanField/BooleanField.tsx +5 -6
  133. package/src/components/Form/FormField/fields/ComboboxField/ComboboxField.tsx +104 -0
  134. package/src/components/Form/FormField/fields/ComboboxField/index.ts +5 -0
  135. package/src/components/Form/FormField/fields/DateField/DateField.tsx +9 -9
  136. package/src/components/Form/FormField/fields/GeoPointField/GeoPointField.tsx +17 -20
  137. package/src/components/Form/FormField/fields/MarkdownField/MarkdownField.stories.tsx +2 -2
  138. package/src/components/Form/FormField/fields/MarkdownField/MarkdownField.tsx +39 -40
  139. package/src/components/Form/FormField/fields/NumberField/NumberField.test.ts +29 -0
  140. package/src/components/Form/FormField/fields/NumberField/NumberField.tsx +60 -21
  141. package/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.tsx +53 -0
  142. package/src/components/Form/FormField/fields/PasswordField/PasswordField.tsx +41 -0
  143. package/src/components/Form/FormField/fields/PasswordField/index.ts +5 -0
  144. package/src/components/Form/FormField/fields/RefField/InlineRefField.tsx +16 -10
  145. package/src/components/Form/FormField/fields/RefField/RefField.test.ts +8 -0
  146. package/src/components/Form/FormField/fields/RefField/RefField.tsx +50 -23
  147. package/src/components/Form/FormField/fields/SelectField/SelectField.tsx +104 -47
  148. package/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.tsx +3 -3
  149. package/src/components/Form/FormField/fields/TextAreaField/TextAreaField.tsx +3 -3
  150. package/src/components/Form/FormField/fields/TextField/TextField.tsx +3 -3
  151. package/src/components/Form/FormField/fields/TupleField/TupleField.tsx +1 -1
  152. package/src/components/Form/FormField/fields/index.ts +4 -0
  153. package/src/components/Form/FormField/index.ts +5 -3
  154. package/src/components/Form/FormField/presentation.tsx +32 -0
  155. package/src/components/Form/FormFieldSet/FormFieldSet.tsx +49 -70
  156. package/src/components/Form/FormFieldSet/FormFieldSetContainer.tsx +93 -0
  157. package/src/components/Form/FormFieldSet/index.ts +1 -0
  158. package/src/components/Form/FormLayout/FormLayout.stories.tsx +7 -5
  159. package/src/components/Form/FormLayout/FormLayout.tsx +24 -14
  160. package/src/components/Form/FormLayout/index.ts +1 -1
  161. package/src/components/Form/meta-tags.test.ts +1 -1
  162. package/src/components/ObjectForm/ObjectForm.tsx +2 -2
  163. package/src/components/ObjectPicker/ObjectPicker.tsx +1 -0
  164. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +1 -1
  165. package/src/components/ObjectProperties/ObjectProperties.tsx +12 -8
  166. package/src/components/RefEditor/RefEditor.stories.tsx +2 -2
  167. package/src/components/RefEditor/RefEditor.tsx +1 -1
  168. package/src/components/ViewEditor/ViewEditor.stories.tsx +2 -2
  169. package/src/components/ViewEditor/ViewEditor.test.tsx +8 -2
  170. package/src/components/ViewEditor/ViewEditor.tsx +3 -3
  171. package/src/components/index.ts +0 -1
  172. package/src/hooks/index.ts +1 -0
  173. package/src/hooks/useAsyncFieldEffect.ts +81 -0
  174. package/src/hooks/useFormContext.ts +6 -3
  175. package/src/hooks/useFormHandler.ts +11 -6
  176. package/src/index.ts +1 -0
  177. package/src/testing/TestLayout.tsx +15 -12
  178. package/src/testing/schema.ts +43 -43
  179. package/src/types.ts +53 -1
  180. package/src/util/index.ts +0 -1
  181. package/src/util/omit.ts +25 -11
  182. package/src/util/properties.test.ts +110 -28
  183. package/src/util/refs.ts +1 -1
  184. package/dist/types/src/components/Form/FormField/FieldHeader.d.ts +0 -19
  185. package/dist/types/src/components/Form/FormField/FieldHeader.d.ts.map +0 -1
  186. package/dist/types/src/components/Form/FormField/FormFieldWrapper.d.ts +0 -47
  187. package/dist/types/src/components/Form/FormField/FormFieldWrapper.d.ts.map +0 -1
  188. package/dist/types/src/components/Form/FormField/FormFieldWrapper.stories.d.ts.map +0 -1
  189. package/dist/types/src/components/Settings/Settings.d.ts +0 -51
  190. package/dist/types/src/components/Settings/Settings.d.ts.map +0 -1
  191. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +0 -1
  192. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +0 -43
  193. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +0 -1
  194. package/dist/types/src/components/Settings/SettingsItem.d.ts +0 -16
  195. package/dist/types/src/components/Settings/SettingsItem.d.ts.map +0 -1
  196. package/dist/types/src/components/Settings/SettingsSelectField.d.ts +0 -18
  197. package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +0 -1
  198. package/dist/types/src/components/Settings/index.d.ts +0 -4
  199. package/dist/types/src/components/Settings/index.d.ts.map +0 -1
  200. package/dist/types/src/util/field-type.d.ts +0 -19
  201. package/dist/types/src/util/field-type.d.ts.map +0 -1
  202. package/src/components/Form/FormField/FieldHeader.tsx +0 -36
  203. package/src/components/Form/FormField/FormFieldWrapper.tsx +0 -179
  204. package/src/components/Settings/AUDIT.md +0 -51
  205. package/src/components/Settings/Settings.stories.tsx +0 -104
  206. package/src/components/Settings/Settings.tsx +0 -81
  207. package/src/components/Settings/SettingsFieldSet.tsx +0 -257
  208. package/src/components/Settings/SettingsItem.tsx +0 -58
  209. package/src/components/Settings/SettingsSelectField.tsx +0 -59
  210. package/src/components/Settings/index.ts +0 -7
  211. package/src/util/field-type.ts +0 -60
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-form",
3
- "version": "0.9.1-main.c7dcc2e112",
3
+ "version": "0.9.1-staging.ee54ba693a",
4
4
  "description": "Data components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -44,52 +44,53 @@
44
44
  "@radix-ui/react-use-controllable-state": "1.1.0",
45
45
  "date-fns": "^3.6.0",
46
46
  "tabster": "^8.5.5",
47
- "@dxos/echo": "0.9.1-main.c7dcc2e112",
48
- "@dxos/echo-protocol": "0.9.1-main.c7dcc2e112",
49
- "@dxos/echo-react": "0.9.1-main.c7dcc2e112",
50
- "@dxos/async": "0.9.1-main.c7dcc2e112",
51
- "@dxos/effect": "0.9.1-main.c7dcc2e112",
52
- "@dxos/invariant": "0.9.1-main.c7dcc2e112",
53
- "@dxos/keys": "0.9.1-main.c7dcc2e112",
54
- "@dxos/lit-ui": "0.9.1-main.c7dcc2e112",
55
- "@dxos/react-ui-components": "0.9.1-main.c7dcc2e112",
56
- "@dxos/log": "0.9.1-main.c7dcc2e112",
57
- "@dxos/react-client": "0.9.1-main.c7dcc2e112",
58
- "@dxos/react-ui-editor": "0.9.1-main.c7dcc2e112",
59
- "@dxos/react-ui-list": "0.9.1-main.c7dcc2e112",
60
- "@dxos/react-ui-pickers": "0.9.1-main.c7dcc2e112",
61
- "@dxos/react-ui-search": "0.9.1-main.c7dcc2e112",
62
- "@dxos/schema": "0.9.1-main.c7dcc2e112",
63
- "@dxos/types": "0.9.1-main.c7dcc2e112",
64
- "@dxos/ui": "0.9.1-main.c7dcc2e112",
65
- "@dxos/echo-client": "0.9.1-main.c7dcc2e112",
66
- "@dxos/ui-editor": "0.9.1-main.c7dcc2e112",
67
- "@dxos/util": "0.9.1-main.c7dcc2e112"
47
+ "@dxos/async": "0.9.1-staging.ee54ba693a",
48
+ "@dxos/echo-doc": "0.9.1-staging.ee54ba693a",
49
+ "@dxos/echo": "0.9.1-staging.ee54ba693a",
50
+ "@dxos/echo-protocol": "0.9.1-staging.ee54ba693a",
51
+ "@dxos/echo-react": "0.9.1-staging.ee54ba693a",
52
+ "@dxos/effect": "0.9.1-staging.ee54ba693a",
53
+ "@dxos/invariant": "0.9.1-staging.ee54ba693a",
54
+ "@dxos/keys": "0.9.1-staging.ee54ba693a",
55
+ "@dxos/lit-ui": "0.9.1-staging.ee54ba693a",
56
+ "@dxos/log": "0.9.1-staging.ee54ba693a",
57
+ "@dxos/react-client": "0.9.1-staging.ee54ba693a",
58
+ "@dxos/react-ui-components": "0.9.1-staging.ee54ba693a",
59
+ "@dxos/react-ui-editor": "0.9.1-staging.ee54ba693a",
60
+ "@dxos/react-ui-list": "0.9.1-staging.ee54ba693a",
61
+ "@dxos/react-ui-markdown": "0.9.1-staging.ee54ba693a",
62
+ "@dxos/react-ui-pickers": "0.9.1-staging.ee54ba693a",
63
+ "@dxos/schema": "0.9.1-staging.ee54ba693a",
64
+ "@dxos/types": "0.9.1-staging.ee54ba693a",
65
+ "@dxos/react-ui-search": "0.9.1-staging.ee54ba693a",
66
+ "@dxos/ui": "0.9.1-staging.ee54ba693a",
67
+ "@dxos/util": "0.9.1-staging.ee54ba693a",
68
+ "@dxos/ui-editor": "0.9.1-staging.ee54ba693a"
68
69
  },
69
70
  "devDependencies": {
70
- "@types/react": "~19.2.7",
71
+ "@types/react": "~19.2.17",
71
72
  "@types/react-dom": "~19.2.3",
72
- "effect": "3.21.3",
73
+ "effect": "3.21.4",
73
74
  "fast-check": "^3.19.0",
74
- "react": "~19.2.3",
75
- "react-dom": "~19.2.3",
75
+ "react": "~19.2.7",
76
+ "react-dom": "~19.2.7",
76
77
  "vite": "^8.0.16",
77
- "@dxos/protocols": "0.9.1-main.c7dcc2e112",
78
- "@dxos/random": "0.9.1-main.c7dcc2e112",
79
- "@dxos/react-client": "0.9.1-main.c7dcc2e112",
80
- "@dxos/react-ui": "0.9.1-main.c7dcc2e112",
81
- "@dxos/react-ui-syntax-highlighter": "0.9.1-main.c7dcc2e112",
82
- "@dxos/storybook-utils": "0.9.1-main.c7dcc2e112",
83
- "@dxos/ui-theme": "0.9.1-main.c7dcc2e112",
84
- "@dxos/ui-types": "0.9.1-main.c7dcc2e112",
85
- "@dxos/effect-proto": "0.9.1-main.c7dcc2e112"
78
+ "@dxos/protocols": "0.9.1-staging.ee54ba693a",
79
+ "@dxos/react-client": "0.9.1-staging.ee54ba693a",
80
+ "@dxos/effect-proto": "0.9.1-staging.ee54ba693a",
81
+ "@dxos/random": "0.9.1-staging.ee54ba693a",
82
+ "@dxos/react-ui": "0.9.1-staging.ee54ba693a",
83
+ "@dxos/react-ui-syntax-highlighter": "0.9.1-staging.ee54ba693a",
84
+ "@dxos/ui-theme": "0.9.1-staging.ee54ba693a",
85
+ "@dxos/ui-types": "0.9.1-staging.ee54ba693a",
86
+ "@dxos/storybook-utils": "0.9.1-staging.ee54ba693a"
86
87
  },
87
88
  "peerDependencies": {
88
- "effect": "3.21.3",
89
- "react": "~19.2.3",
90
- "react-dom": "~19.2.3",
91
- "@dxos/react-ui": "0.9.1-main.c7dcc2e112",
92
- "@dxos/ui-theme": "0.9.1-main.c7dcc2e112"
89
+ "effect": "3.21.4",
90
+ "react": "~19.2.7",
91
+ "react-dom": "~19.2.7",
92
+ "@dxos/ui-theme": "0.9.1-staging.ee54ba693a",
93
+ "@dxos/react-ui": "0.9.1-staging.ee54ba693a"
93
94
  },
94
95
  "publishConfig": {
95
96
  "access": "public"
@@ -0,0 +1,63 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import type * as Effect from 'effect/Effect';
6
+
7
+ import { createAnnotationHelper } from '@dxos/echo/internal';
8
+
9
+ /** One selectable option produced by an {@link OptionsLookup}. */
10
+ export type OptionsLookupEntry = { value: string; label?: string; secondaryLabel?: string; icon?: string };
11
+
12
+ /**
13
+ * Loads a select field's options dynamically from a declared subset of the form values, so a select can
14
+ * depend on a sibling field (e.g. publications for a typed handle). `deps` are the field names the loader
15
+ * reads; the form re-runs it only when one of those values changes. The returned Effect must be
16
+ * self-contained (`R = never`): it provides its own dependencies (network layer, etc.) in the closure.
17
+ * Construct via {@link optionsLookup} for typed `deps`/`values`. Pass `{ combobox: true }` to render
18
+ * an editable combobox (type-to-search) instead of a constrained select.
19
+ */
20
+ export type OptionsLookup = {
21
+ readonly deps: readonly string[];
22
+ readonly load: (values: any) => Effect.Effect<readonly OptionsLookupEntry[], unknown>;
23
+ readonly combobox?: boolean;
24
+ };
25
+
26
+ export const OptionsLookupAnnotationId = Symbol.for('@dxos/schema/annotation/OptionsLookup');
27
+ export const OptionsLookupAnnotation = createAnnotationHelper<OptionsLookup>(OptionsLookupAnnotationId);
28
+
29
+ /**
30
+ * Builds an {@link OptionsLookup} typed against a schema's value type `Values`: `deps` is checked
31
+ * against its field names, and `load` receives only those fields, narrowed. Pass `{ combobox: true }` to
32
+ * render an editable combobox.
33
+ */
34
+ export const optionsLookup =
35
+ <Values>() =>
36
+ <const Deps extends readonly (keyof Values & string)[]>(
37
+ deps: Deps,
38
+ load: (values: Pick<Values, Deps[number]>) => Effect.Effect<readonly OptionsLookupEntry[], unknown>,
39
+ options?: { combobox?: boolean },
40
+ ): OptionsLookup => ({ deps, load, combobox: options?.combobox });
41
+
42
+ /**
43
+ * Derives a (text) field's value from a declared subset of the form values, so a field can be pre-filled
44
+ * from a sibling (e.g. a feed name fetched from a typed URL). `deps` are the field names the derivation
45
+ * reads; the form re-runs it only when one of those changes. The returned Effect must be self-contained
46
+ * (`R = never`); `undefined` means "no value to fill". The form pre-fills only while the user has not
47
+ * typed their own value, so a manual edit is never clobbered. Construct via {@link autofill}.
48
+ */
49
+ export type Autofill = {
50
+ readonly deps: readonly string[];
51
+ readonly derive: (values: any) => Effect.Effect<string | undefined, unknown>;
52
+ };
53
+
54
+ export const AutofillAnnotationId = Symbol.for('@dxos/schema/annotation/Autofill');
55
+ export const AutofillAnnotation = createAnnotationHelper<Autofill>(AutofillAnnotationId);
56
+
57
+ /** Builds an {@link Autofill} typed against a schema's value type `Values` (see {@link optionsLookup}). */
58
+ export const autofill =
59
+ <Values>() =>
60
+ <const Deps extends readonly (keyof Values & string)[]>(
61
+ deps: Deps,
62
+ derive: (values: Pick<Values, Deps[number]>) => Effect.Effect<string | undefined, unknown>,
63
+ ): Autofill => ({ deps, derive });
@@ -58,7 +58,10 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
58
58
 
59
59
  const [referenceSchema, setReferenceSchema] = useState<Type.Type>();
60
60
  useEffect(() => {
61
- setReferenceSchema(schemas.find((schema) => Type.getTypename(schema) === props?.referenceSchema));
61
+ // A React state setter invokes a function argument as an updater. A class-based `Type.Type` is
62
+ // itself a function, so it must be stored via an updater lambda — passing it directly makes React
63
+ // call it as `Organization(prev)`, throwing "Class constructor … cannot be invoked without 'new'".
64
+ setReferenceSchema(() => schemas.find((schema) => Type.getTypename(schema) === props?.referenceSchema));
62
65
  }, [schemas, props?.referenceSchema]);
63
66
 
64
67
  // TODO(burdon): Need to wrap otherwise throws error:
@@ -191,7 +194,7 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
191
194
  readonly={readonly}
192
195
  schema={fieldSchema}
193
196
  values={props}
194
- exclude={propIsNotType}
197
+ filter={propIsNotType}
195
198
  sort={['property', 'format']}
196
199
  onValuesChanged={handleValuesChanged}
197
200
  onValidate={handleValidate}
@@ -0,0 +1,24 @@
1
+ # `@dxos/react-ui-form` — Design
2
+
3
+ Schema-driven forms. Give `Form.Root` an Effect `schema` + `values`; the field set renders typed
4
+ controls automatically. Bespoke controls drop in via `Form.Row` or a per-path `fieldMap`.
5
+
6
+ ## Component hierarchy
7
+
8
+ ```text
9
+ Form.Root context: schema, values, validation, onValuesChanged
10
+ └─ Form.Viewport scroll chrome
11
+ └─ Form.Content
12
+ ├─ Form.Section titled group of fields
13
+ │ ├─ Form.FieldSet schema-driven field set
14
+ │ │ └─ FormFieldSetContainer group wrapper: FormFieldHeader (label + collapse) over the body
15
+ │ │ └─ FormField one per schema property; dispatches by type:
16
+ │ │ ├─ ArrayField array → FormFieldHeader + item rows
17
+ │ │ ├─ Form.FieldSet nested object → recurses (its own FormFieldSetContainer)
18
+ │ │ └─ Form.Row scalar → label + description + the control (<field>)
19
+ │ └─ Form.Row OR a hand-written row (label + description + any control)
20
+ ├─ Form.Layout alternative to FieldSet: lays fields out per a parsed layout spec
21
+ └─ Form.Actions
22
+ └─ Form.Submit
23
+ Form.Error
24
+ ```
@@ -3,35 +3,38 @@
3
3
  //
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Effect from 'effect/Effect';
6
7
  import * as Schema from 'effect/Schema';
7
- import React, { useCallback, useState } from 'react';
8
+ import React, { useCallback, useMemo, useState } from 'react';
8
9
 
9
- import { Obj, Tag, Type } from '@dxos/echo';
10
+ import { Annotation, Filter, Format, Obj, Ref, Tag, Type } from '@dxos/echo';
10
11
  import { type AnyProperties } from '@dxos/echo/internal';
11
12
  import { log } from '@dxos/log';
12
- import { useSpaces } from '@dxos/react-client/echo';
13
+ import { useQuery, useSpaces } from '@dxos/react-client/echo';
13
14
  import { withClientProvider } from '@dxos/react-client/testing';
14
15
  import { Tooltip } from '@dxos/react-ui';
15
16
  import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
17
+ import { Text } from '@dxos/schema';
16
18
 
17
19
  import { translations } from '#translations';
18
20
 
21
+ import { AutofillAnnotation, OptionsLookupAnnotation, autofill, optionsLookup } from '../../annotations';
19
22
  import { Organization, Person, TestLayout } from '../../testing';
20
23
  import { type ExcludeId, omitId } from '../../util';
21
24
  import { Form, type FormRootProps } from './Form';
22
25
 
23
- type DefaultStoryProps<T extends AnyProperties> = {
24
- schema?: Schema.Schema<T>;
25
- } & FormRootProps<T>;
26
+ type StoryArgs<T extends AnyProperties> = FormRootProps<T> & { json?: boolean };
26
27
 
27
28
  const DefaultStory = <T extends AnyProperties = AnyProperties>({
28
29
  schema,
29
30
  values: valuesProp,
31
+ json = true,
30
32
  ...props
31
- }: DefaultStoryProps<T>) => {
33
+ }: StoryArgs<T>) => {
32
34
  const [values, setValues] = useState<Partial<T>>(valuesProp ?? {});
33
35
  const spaces = useSpaces();
34
36
  const space = spaces[0];
37
+
35
38
  const handleSave = useCallback<NonNullable<FormRootProps<T>['onSave']>>((values) => {
36
39
  log.info('save', { values, meta });
37
40
  setValues(values);
@@ -48,7 +51,7 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
48
51
 
49
52
  return (
50
53
  <Tooltip.Provider>
51
- <TestLayout json={{ values, schema: schema?.ast }}>
54
+ <TestLayout json={json ? { values, schema: schema?.ast } : undefined}>
52
55
  <Form.Root
53
56
  schema={schema}
54
57
  defaultValues={values}
@@ -59,7 +62,7 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
59
62
  >
60
63
  <Form.Viewport scroll>
61
64
  <Form.Content>
62
- <Form.Section label='Section' description='This is a section' />
65
+ <Form.Section title='Section' description='This is a [section description](https://dxos.org).' />
63
66
  <Form.FieldSet />
64
67
  <Form.Actions />
65
68
  </Form.Content>
@@ -80,11 +83,12 @@ const meta = {
80
83
  withClientProvider({
81
84
  createIdentity: true,
82
85
  createSpace: true,
83
- types: [Tag.Tag, Organization, Person],
86
+ types: [Tag.Tag, Organization, Person, Text.Text],
84
87
  onCreateSpace: ({ space }) => {
85
88
  [
86
89
  ...Array.from({ length: 3 }).map((_, i) => Obj.make(Tag.Tag, { label: `Tag ${i}` })),
87
90
  ...Array.from({ length: 50 }).map((_, i) => Obj.make(Organization, { name: `Organization ${i}` })),
91
+ Obj.make(Text.Text, { content: '# Brief\n\nEdit this **inline** markdown text.' }),
88
92
  ].map((obj) => space.db.add(obj));
89
93
  },
90
94
  }),
@@ -93,12 +97,10 @@ const meta = {
93
97
  layout: 'fullscreen',
94
98
  translations,
95
99
  },
96
- } satisfies Meta<DefaultStoryProps<any>>;
100
+ } satisfies Meta<StoryArgs<any>>;
97
101
 
98
102
  export default meta;
99
103
 
100
- type Story<T extends AnyProperties> = StoryObj<DefaultStoryProps<T>>;
101
-
102
104
  const PersonSchema = Type.getSchema(Person);
103
105
 
104
106
  const values: Partial<Person> = {
@@ -110,6 +112,8 @@ const values: Partial<Person> = {
110
112
  reminderAt: '09:00:00',
111
113
  };
112
114
 
115
+ type Story<T extends AnyProperties> = StoryObj<StoryArgs<T>>;
116
+
113
117
  /**
114
118
  * Build a data-entry surface by handing an Effect schema to `Form.Root` — the form derives its fields,
115
119
  * types, validation, and selects from the schema, so you don't hand-wire one control per property.
@@ -147,6 +151,246 @@ export const Static: Story<ExcludeId<typeof PersonSchema>> = {
147
151
  },
148
152
  };
149
153
 
154
+ const SettingsSchema = Schema.mutable(
155
+ Schema.Struct({
156
+ viewMode: Schema.Literal('preview', 'readonly', 'source').annotations({
157
+ title: 'Default view mode',
158
+ description: 'Set whether documents open in editing or read-only mode.',
159
+ }),
160
+ toolbar: Schema.optional(
161
+ Schema.Boolean.annotations({
162
+ title: 'Show toolbar',
163
+ description: 'Display a formatting toolbar above the editor.',
164
+ }),
165
+ ),
166
+ fontSize: Schema.optional(
167
+ Schema.Number.annotations({ title: 'Font size', description: 'Editor font size, in pixels.' }),
168
+ ),
169
+ }),
170
+ );
171
+
172
+ export const Variants: Story<Schema.Schema.Type<typeof SettingsSchema>> = {
173
+ render: (args) => (
174
+ <div className='grid grid-cols-2 w-full'>
175
+ <DefaultStory {...args} />
176
+ <DefaultStory {...args} variant='settings' />
177
+ </div>
178
+ ),
179
+ args: {
180
+ json: false,
181
+ schema: SettingsSchema,
182
+ values: {
183
+ viewMode: 'preview',
184
+ toolbar: true,
185
+ fontSize: 14,
186
+ },
187
+ },
188
+ };
189
+
190
+ const InlineMarkdownTextSchema = Schema.mutable(
191
+ Schema.Struct({
192
+ text: Schema.String,
193
+ instructions: Ref.Ref(Text.Text).pipe(
194
+ Format.FormatAnnotation.set(Format.TypeFormat.Markdown),
195
+ Annotation.FormInlineAnnotation.set(true),
196
+ Schema.annotations({
197
+ title: 'Instructions',
198
+ description: 'Ref to a Text object with both markdown and inline-ref annotations.',
199
+ }),
200
+ ),
201
+ }),
202
+ );
203
+
204
+ const InlineMarkdownTextStory = (args: StoryArgs<any>) => {
205
+ const spaces = useSpaces();
206
+ const space = spaces[0];
207
+ const [text] = useQuery(space?.db, Filter.type(Text.Text));
208
+ const values = useMemo(() => (text ? { instructions: Ref.make(text) } : undefined), [text]);
209
+
210
+ if (!space || !values) {
211
+ return <Loading />;
212
+ }
213
+
214
+ return (
215
+ <DefaultStory
216
+ {...args}
217
+ schema={InlineMarkdownTextSchema}
218
+ values={values}
219
+ onCreate={(_type, props) => space.db.add(Obj.make(Text.Text, { content: '', ...props }))}
220
+ />
221
+ );
222
+ };
223
+
224
+ /**
225
+ * Exercises a `Ref<Text>` field carrying both `Format.TypeFormat.Markdown` and
226
+ * `FormInlineAnnotation` — the markdown editor should render inline rather than
227
+ * opening a ref picker or nested struct form.
228
+ */
229
+ export const InlineMarkdownText: Story<any> = {
230
+ render: (args) => <InlineMarkdownTextStory {...args} />,
231
+ args: {
232
+ autoSave: true,
233
+ },
234
+ };
235
+
150
236
  export const Empty: Story<ExcludeId<typeof PersonSchema>> = {
151
237
  args: {},
152
238
  };
239
+
240
+ // Demonstrates the value-driven dynamic-field annotations (here backed by in-memory Effects with an
241
+ // artificial delay rather than network calls, so the story is deterministic): a select whose options
242
+ // derive from the `query` field, and a text field auto-filled from the `url` field. Each annotation
243
+ // declares the fields it depends on, so it only re-runs when one of those changes. Structural validation
244
+ // is the schema's own job (`Format.URL`).
245
+ const isValidUrl = Schema.is(Format.URL);
246
+
247
+ // Base struct (no dynamic annotations) — its value type drives the typed `deps`/`values` below.
248
+ const DynamicFieldsBase = Schema.Struct({
249
+ query: Schema.String.annotations({ title: 'Query', description: 'Type to load the choices below.' }),
250
+ choice: Schema.optional(Schema.String),
251
+ tag: Schema.optional(Schema.String),
252
+ url: Format.URL.annotations({ title: 'URL', description: 'A valid URL auto-fills the name below.' }),
253
+ name: Schema.optional(Schema.String),
254
+ });
255
+ type DynamicFieldsValues = Schema.Schema.Type<typeof DynamicFieldsBase>;
256
+
257
+ const TAG_VOCABULARY = ['react', 'effect', 'schema', 'echo', 'composer'];
258
+
259
+ const DynamicFieldsSchema = Schema.mutable(
260
+ Schema.Struct({
261
+ ...DynamicFieldsBase.fields,
262
+ choice: Schema.optional(
263
+ Schema.String.pipe(
264
+ OptionsLookupAnnotation.set(
265
+ optionsLookup<DynamicFieldsValues>()(['query'], ({ query }) =>
266
+ (query && query.length > 0
267
+ ? Effect.succeed(
268
+ [1, 2, 3].map((index) => ({ value: `${query}-${index}`, label: `${query} choice ${index}` })),
269
+ )
270
+ : Effect.succeed([])
271
+ ).pipe(Effect.delay('600 millis')),
272
+ ),
273
+ ),
274
+ Schema.annotations({ title: 'Choice', description: 'Options load from the query (after a delay).' }),
275
+ ),
276
+ ),
277
+ // Combobox: the field's own value is the query; the typed text is the auto-selected first option.
278
+ tag: Schema.optional(
279
+ Schema.String.pipe(
280
+ OptionsLookupAnnotation.set(
281
+ optionsLookup<DynamicFieldsValues>()(
282
+ // No deps: the pool is fetched once; the combobox filters it by the typed text client-side.
283
+ [],
284
+ () =>
285
+ Effect.succeed(TAG_VOCABULARY.map((value) => ({ value, label: value }))).pipe(Effect.delay('400 millis')),
286
+ { combobox: true },
287
+ ),
288
+ ),
289
+ Schema.annotations({ title: 'Tag', description: 'Combobox: type to filter; your text stays selectable.' }),
290
+ ),
291
+ ),
292
+ name: Schema.optional(
293
+ Schema.String.pipe(
294
+ // Only derive a value once the URL is structurally valid, and only after the artificial wait —
295
+ // an incomplete/invalid URL produces nothing.
296
+ AutofillAnnotation.set(
297
+ autofill<DynamicFieldsValues>()(['url'], ({ url }) =>
298
+ isValidUrl(url)
299
+ ? Effect.succeed(`Feed for ${url}`).pipe(Effect.delay('800 millis'))
300
+ : Effect.succeed(undefined),
301
+ ),
302
+ ),
303
+ Schema.annotations({ title: 'Name', description: 'Auto-filled from a valid URL (editable).' }),
304
+ ),
305
+ ),
306
+ }),
307
+ );
308
+
309
+ /**
310
+ * Exercises the dynamic-field annotations: `OptionsLookupAnnotation` (select options loaded from a
311
+ * sibling) and `AutofillAnnotation` (value derived from a sibling, gated on validity and delayed). Each
312
+ * runs a self-contained Effect from the current form values.
313
+ */
314
+ export const DynamicFields: Story<Schema.Schema.Type<typeof DynamicFieldsSchema>> = {
315
+ args: {
316
+ json: true,
317
+ schema: DynamicFieldsSchema,
318
+ },
319
+ };
320
+
321
+ // Discriminated-union create form mirroring the magazine create-feed schema: a `type` selector picks the
322
+ // member, whose fields then render — `standard-site` (handle combobox → publication select) or `rss`
323
+ // (URL → auto-filled name). Backed by in-memory Effects so the story is deterministic. Regression harness
324
+ // for union create forms (e.g. the `omitId` union-flattening bug).
325
+ const HANDLE_SUGGESTIONS = ['dxos.org', 'alice.bsky.social', 'bob.example.com'];
326
+
327
+ const StandardSiteCreateBase = Schema.Struct({
328
+ type: Schema.Literal('standard-site'),
329
+ handle: Schema.String.annotations({ title: 'Handle', description: 'atproto handle, e.g. dxos.org.' }),
330
+ // No `name`: the feed name is taken from the selected publication.
331
+ publication: Schema.String.annotations({ title: 'Publication', description: 'Choose a publication.' }),
332
+ });
333
+ type StandardSiteValues = Schema.Schema.Type<typeof StandardSiteCreateBase>;
334
+
335
+ const StandardSiteCreate = Schema.Struct({
336
+ ...StandardSiteCreateBase.fields,
337
+ handle: StandardSiteCreateBase.fields.handle.pipe(
338
+ OptionsLookupAnnotation.set(
339
+ optionsLookup<StandardSiteValues>()(
340
+ ['handle'],
341
+ () =>
342
+ Effect.succeed(HANDLE_SUGGESTIONS.map((value) => ({ value, label: value }))).pipe(Effect.delay('300 millis')),
343
+ { combobox: true },
344
+ ),
345
+ ),
346
+ ),
347
+ publication: StandardSiteCreateBase.fields.publication.pipe(
348
+ OptionsLookupAnnotation.set(
349
+ optionsLookup<StandardSiteValues>()(['handle'], ({ handle }) =>
350
+ Effect.succeed(
351
+ handle
352
+ ? [
353
+ { value: `at://${handle}/pub/blog`, label: `${handle} — Blog` },
354
+ { value: `at://${handle}/pub/notes`, label: `${handle} — Notes` },
355
+ ]
356
+ : [],
357
+ ).pipe(Effect.delay('300 millis')),
358
+ ),
359
+ ),
360
+ ),
361
+ });
362
+
363
+ const RssCreateBase = Schema.Struct({
364
+ type: Schema.Literal('rss'),
365
+ url: Format.URL.annotations({ title: 'URL', description: 'RSS feed URL.' }),
366
+ name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
367
+ });
368
+ type RssValues = Schema.Schema.Type<typeof RssCreateBase>;
369
+
370
+ const RssCreate = Schema.Struct({
371
+ ...RssCreateBase.fields,
372
+ name: Schema.optional(
373
+ Schema.String.pipe(
374
+ AutofillAnnotation.set(
375
+ autofill<RssValues>()(['url'], ({ url }) =>
376
+ isValidUrl(url)
377
+ ? Effect.succeed(`Feed for ${url}`).pipe(Effect.delay('500 millis'))
378
+ : Effect.succeed(undefined),
379
+ ),
380
+ ),
381
+ ).annotations({ title: 'Name' }),
382
+ ),
383
+ });
384
+
385
+ const CreateFeedSchema = Schema.Union(StandardSiteCreate, RssCreate);
386
+
387
+ /**
388
+ * A discriminated-union create form: selecting `type` reveals that member's fields (combobox + select for
389
+ * `standard-site`, URL + autofill for `rss`). Mirrors the magazine create-feed schema.
390
+ */
391
+ export const DiscriminatedUnion: Story<Schema.Schema.Type<typeof CreateFeedSchema>> = {
392
+ args: {
393
+ json: true,
394
+ schema: CreateFeedSchema,
395
+ },
396
+ };
@@ -0,0 +1,33 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { formTheme } from './Form.theme';
8
+
9
+ describe('formTheme', () => {
10
+ test('default variant adds no chrome to the field', ({ expect }) => {
11
+ const styles = formTheme.styles({ variant: 'default' });
12
+ expect(styles.field()).toBeFalsy();
13
+ expect(formTheme.behavior.default.showDescription).toBe(false);
14
+ });
15
+
16
+ test('settings variant frames the field and shows the description', ({ expect }) => {
17
+ const styles = formTheme.styles({ variant: 'settings' });
18
+ // Assert stable invariants (bordered, grid-framed, prominent label, right-aligned control),
19
+ // not the specific grid mechanism, so the test survives layout tweaks.
20
+ expect(styles.field()).toContain('border');
21
+ expect(styles.field()).toContain('grid');
22
+ expect(styles.fieldLabel()).toContain('[grid-area:header]');
23
+ expect(styles.fieldLabelText()).toContain('text-lg');
24
+ expect(styles.fieldControl()).toContain('justify-end');
25
+ expect(formTheme.behavior.settings.showDescription).toBe(true);
26
+ });
27
+
28
+ test('per-slot override wins', ({ expect }) => {
29
+ const styles = formTheme.styles({ variant: 'settings' });
30
+ expect(styles.fieldLabelText({ class: 'text-2xl' })).toContain('text-2xl');
31
+ expect(styles.fieldLabelText({ class: 'text-2xl' })).not.toContain('text-lg');
32
+ });
33
+ });