@dxos/react-ui-form 0.9.0 → 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 (372) hide show
  1. package/dist/lib/browser/index.mjs +2337 -1919
  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 +2337 -1919
  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 +3 -3
  10. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  11. package/dist/types/src/components/Form/Form.d.ts +23 -110
  12. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  13. package/dist/types/src/components/Form/Form.stories.d.ts +48 -101
  14. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.theme.d.ts +252 -0
  16. package/dist/types/src/components/Form/Form.theme.d.ts.map +1 -0
  17. package/dist/types/src/components/Form/Form.theme.test.d.ts +2 -0
  18. package/dist/types/src/components/Form/Form.theme.test.d.ts.map +1 -0
  19. package/dist/types/src/components/Form/FormControls.d.ts +75 -0
  20. package/dist/types/src/components/Form/FormControls.d.ts.map +1 -0
  21. package/dist/types/src/components/Form/FormField/FormField.d.ts +44 -0
  22. package/dist/types/src/components/Form/FormField/FormField.d.ts.map +1 -0
  23. package/dist/types/src/components/Form/FormField/FormFieldHeader.d.ts +27 -0
  24. package/dist/types/src/components/Form/FormField/FormFieldHeader.d.ts.map +1 -0
  25. package/dist/types/src/components/Form/FormField/FormRow.d.ts +85 -0
  26. package/dist/types/src/components/Form/FormField/FormRow.d.ts.map +1 -0
  27. package/dist/types/src/components/Form/{FormFieldComponent.stories.d.ts → FormField/FormRow.stories.d.ts} +6 -3
  28. package/dist/types/src/components/Form/FormField/FormRow.stories.d.ts.map +1 -0
  29. package/dist/types/src/components/Form/{fields → FormField/fields/ArrayField}/ArrayField.d.ts +2 -2
  30. package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts +88 -0
  32. package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/FormField/fields/ArrayField/index.d.ts +2 -0
  34. package/dist/types/src/components/Form/FormField/fields/ArrayField/index.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.d.ts +12 -0
  36. package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/index.d.ts +2 -0
  38. package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/index.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/FormField/fields/AutofillField/AutofillField.d.ts +12 -0
  40. package/dist/types/src/components/Form/FormField/fields/AutofillField/AutofillField.d.ts.map +1 -0
  41. package/dist/types/src/components/Form/FormField/fields/AutofillField/index.d.ts +2 -0
  42. package/dist/types/src/components/Form/FormField/fields/AutofillField/index.d.ts.map +1 -0
  43. package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.d.ts +4 -0
  44. package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.d.ts.map +1 -0
  45. package/dist/types/src/components/{Settings/Settings.stories.d.ts → Form/FormField/fields/BooleanField/BooleanField.stories.d.ts} +1 -2
  46. package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.stories.d.ts.map +1 -0
  47. package/dist/types/src/components/Form/FormField/fields/BooleanField/index.d.ts +2 -0
  48. package/dist/types/src/components/Form/FormField/fields/BooleanField/index.d.ts.map +1 -0
  49. package/dist/types/src/components/Form/FormField/fields/ComboboxField/ComboboxField.d.ts +12 -0
  50. package/dist/types/src/components/Form/FormField/fields/ComboboxField/ComboboxField.d.ts.map +1 -0
  51. package/dist/types/src/components/Form/FormField/fields/ComboboxField/index.d.ts +2 -0
  52. package/dist/types/src/components/Form/FormField/fields/ComboboxField/index.d.ts.map +1 -0
  53. package/dist/types/src/components/Form/FormField/fields/DateField/DateField.d.ts +4 -0
  54. package/dist/types/src/components/Form/FormField/fields/DateField/DateField.d.ts.map +1 -0
  55. package/dist/types/src/components/Form/FormField/fields/DateField/DateField.stories.d.ts +88 -0
  56. package/dist/types/src/components/Form/FormField/fields/DateField/DateField.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/Form/FormField/fields/DateField/index.d.ts +2 -0
  58. package/dist/types/src/components/Form/FormField/fields/DateField/index.d.ts.map +1 -0
  59. package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.d.ts +5 -0
  60. package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.d.ts.map +1 -0
  61. package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.d.ts +88 -0
  62. package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.d.ts.map +1 -0
  63. package/dist/types/src/components/Form/FormField/fields/GeoPointField/index.d.ts +2 -0
  64. package/dist/types/src/components/Form/FormField/fields/GeoPointField/index.d.ts.map +1 -0
  65. package/dist/types/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.d.ts +2 -2
  66. package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.d.ts.map +1 -0
  67. package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.stories.d.ts.map +1 -0
  68. package/dist/types/src/components/Form/FormField/fields/MarkdownField/index.d.ts +2 -0
  69. package/dist/types/src/components/Form/FormField/fields/MarkdownField/index.d.ts.map +1 -0
  70. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.d.ts +15 -0
  71. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.d.ts.map +1 -0
  72. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.stories.d.ts +88 -0
  73. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.stories.d.ts.map +1 -0
  74. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.test.d.ts +2 -0
  75. package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.test.d.ts.map +1 -0
  76. package/dist/types/src/components/Form/FormField/fields/NumberField/index.d.ts +2 -0
  77. package/dist/types/src/components/Form/FormField/fields/NumberField/index.d.ts.map +1 -0
  78. package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.d.ts +4 -0
  79. package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.d.ts.map +1 -0
  80. package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.d.ts +88 -0
  81. package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.d.ts.map +1 -0
  82. package/dist/types/src/components/Form/FormField/fields/PasswordField/index.d.ts +2 -0
  83. package/dist/types/src/components/Form/FormField/fields/PasswordField/index.d.ts.map +1 -0
  84. package/dist/types/src/components/Form/FormField/fields/RefField/InlineRefField.d.ts.map +1 -0
  85. package/dist/types/src/components/Form/FormField/fields/RefField/RefField.d.ts +16 -0
  86. package/dist/types/src/components/Form/FormField/fields/RefField/RefField.d.ts.map +1 -0
  87. package/dist/types/src/components/Form/FormField/fields/RefField/RefField.stories.d.ts +92 -0
  88. package/dist/types/src/components/Form/FormField/fields/RefField/RefField.stories.d.ts.map +1 -0
  89. package/dist/types/src/components/Form/FormField/fields/RefField/RefField.test.d.ts.map +1 -0
  90. package/dist/types/src/components/Form/FormField/fields/RefField/index.d.ts +3 -0
  91. package/dist/types/src/components/Form/FormField/fields/RefField/index.d.ts.map +1 -0
  92. package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.d.ts +29 -0
  93. package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.d.ts.map +1 -0
  94. package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.stories.d.ts +88 -0
  95. package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.stories.d.ts.map +1 -0
  96. package/dist/types/src/components/Form/FormField/fields/SelectField/index.d.ts +2 -0
  97. package/dist/types/src/components/Form/FormField/fields/SelectField/index.d.ts.map +1 -0
  98. package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.d.ts +5 -0
  99. package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.d.ts.map +1 -0
  100. package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.stories.d.ts.map +1 -0
  101. package/dist/types/src/components/Form/FormField/fields/SelectOptionField/index.d.ts +2 -0
  102. package/dist/types/src/components/Form/FormField/fields/SelectOptionField/index.d.ts.map +1 -0
  103. package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.d.ts +4 -0
  104. package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.d.ts.map +1 -0
  105. package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.d.ts +88 -0
  106. package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.d.ts.map +1 -0
  107. package/dist/types/src/components/Form/FormField/fields/TextAreaField/index.d.ts +2 -0
  108. package/dist/types/src/components/Form/FormField/fields/TextAreaField/index.d.ts.map +1 -0
  109. package/dist/types/src/components/Form/FormField/fields/TextField/TextField.d.ts +4 -0
  110. package/dist/types/src/components/Form/FormField/fields/TextField/TextField.d.ts.map +1 -0
  111. package/dist/types/src/components/Form/FormField/fields/TextField/TextField.stories.d.ts +88 -0
  112. package/dist/types/src/components/Form/FormField/fields/TextField/TextField.stories.d.ts.map +1 -0
  113. package/dist/types/src/components/Form/FormField/fields/TextField/index.d.ts +2 -0
  114. package/dist/types/src/components/Form/FormField/fields/TextField/index.d.ts.map +1 -0
  115. package/dist/types/src/components/Form/{fields → FormField/fields/TupleField}/TupleField.d.ts +2 -2
  116. package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.d.ts.map +1 -0
  117. package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.stories.d.ts +88 -0
  118. package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.stories.d.ts.map +1 -0
  119. package/dist/types/src/components/Form/FormField/fields/TupleField/index.d.ts +2 -0
  120. package/dist/types/src/components/Form/FormField/fields/TupleField/index.d.ts.map +1 -0
  121. package/dist/types/src/components/Form/{fields → FormField/fields}/index.d.ts +4 -1
  122. package/dist/types/src/components/Form/FormField/fields/index.d.ts.map +1 -0
  123. package/dist/types/src/components/Form/FormField/index.d.ts +6 -0
  124. package/dist/types/src/components/Form/FormField/index.d.ts.map +1 -0
  125. package/dist/types/src/components/Form/FormField/presentation.d.ts +22 -0
  126. package/dist/types/src/components/Form/FormField/presentation.d.ts.map +1 -0
  127. package/dist/types/src/components/Form/FormFieldSet/FormFieldSet.d.ts +30 -0
  128. package/dist/types/src/components/Form/FormFieldSet/FormFieldSet.d.ts.map +1 -0
  129. package/dist/types/src/components/Form/FormFieldSet/FormFieldSetContainer.d.ts +22 -0
  130. package/dist/types/src/components/Form/FormFieldSet/FormFieldSetContainer.d.ts.map +1 -0
  131. package/dist/types/src/components/Form/FormFieldSet/index.d.ts +3 -0
  132. package/dist/types/src/components/Form/FormFieldSet/index.d.ts.map +1 -0
  133. package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.d.ts +2 -0
  134. package/dist/types/src/components/Form/FormLayout/FormLayout.d.ts.map +1 -0
  135. package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.stories.d.ts +5 -5
  136. package/dist/types/src/components/Form/FormLayout/FormLayout.stories.d.ts.map +1 -0
  137. package/dist/types/src/components/Form/FormLayout/FormLayout.stories.test.d.ts.map +1 -0
  138. package/dist/types/src/components/Form/FormLayout/FormLayout.test.d.ts.map +1 -0
  139. package/dist/types/src/components/Form/FormLayout/index.d.ts +3 -0
  140. package/dist/types/src/components/Form/FormLayout/index.d.ts.map +1 -0
  141. package/dist/types/src/components/Form/FormLayout/parser.d.ts.map +1 -0
  142. package/dist/types/src/components/Form/FormLayout/parser.test.d.ts.map +1 -0
  143. package/dist/types/src/components/Form/index.d.ts +3 -5
  144. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  145. package/dist/types/src/components/Form/meta-tags.d.ts +1 -12
  146. package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -1
  147. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -1
  148. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +6 -23
  149. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  150. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +2 -1
  151. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
  152. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts +2 -2
  153. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts.map +1 -1
  154. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +1 -1
  155. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  156. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +2 -2
  157. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  158. package/dist/types/src/components/index.d.ts +0 -1
  159. package/dist/types/src/components/index.d.ts.map +1 -1
  160. package/dist/types/src/hooks/index.d.ts +2 -0
  161. package/dist/types/src/hooks/index.d.ts.map +1 -1
  162. package/dist/types/src/hooks/useAsyncFieldEffect.d.ts +24 -0
  163. package/dist/types/src/hooks/useAsyncFieldEffect.d.ts.map +1 -0
  164. package/dist/types/src/hooks/useFormContext.d.ts +42 -0
  165. package/dist/types/src/hooks/useFormContext.d.ts.map +1 -0
  166. package/dist/types/src/hooks/useFormHandler.d.ts +9 -6
  167. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
  168. package/dist/types/src/index.d.ts +2 -0
  169. package/dist/types/src/index.d.ts.map +1 -1
  170. package/dist/types/src/{components/testing.d.ts → testing/TestLayout.d.ts} +2 -2
  171. package/dist/types/src/testing/TestLayout.d.ts.map +1 -0
  172. package/dist/types/src/testing/index.d.ts +3 -0
  173. package/dist/types/src/testing/index.d.ts.map +1 -0
  174. package/dist/types/src/testing/schema.d.ts +40 -0
  175. package/dist/types/src/testing/schema.d.ts.map +1 -0
  176. package/dist/types/src/types.d.ts +161 -0
  177. package/dist/types/src/types.d.ts.map +1 -0
  178. package/dist/types/src/util/index.d.ts +1 -1
  179. package/dist/types/src/util/index.d.ts.map +1 -1
  180. package/dist/types/src/util/omit.d.ts +12 -0
  181. package/dist/types/src/util/omit.d.ts.map +1 -0
  182. package/dist/types/tsconfig.tsbuildinfo +1 -1
  183. package/package.json +43 -41
  184. package/src/annotations.ts +63 -0
  185. package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
  186. package/src/components/FieldEditor/FieldEditor.test.tsx +1 -1
  187. package/src/components/FieldEditor/FieldEditor.tsx +10 -6
  188. package/src/components/Form/DESIGN.md +24 -0
  189. package/src/components/Form/Form.stories.tsx +260 -61
  190. package/src/components/Form/Form.theme.test.ts +33 -0
  191. package/src/components/Form/Form.theme.ts +86 -0
  192. package/src/components/Form/Form.tsx +30 -453
  193. package/src/components/Form/FormControls.tsx +377 -0
  194. package/src/components/Form/{FormField.tsx → FormField/FormField.tsx} +98 -71
  195. package/src/components/Form/FormField/FormFieldHeader.tsx +68 -0
  196. package/src/components/Form/FormField/FormRow.stories.tsx +97 -0
  197. package/src/components/Form/FormField/FormRow.tsx +298 -0
  198. package/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.tsx +119 -0
  199. package/src/components/Form/FormField/fields/ArrayField/ArrayField.tsx +328 -0
  200. package/src/components/Form/FormField/fields/ArrayField/index.ts +5 -0
  201. package/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.tsx +59 -0
  202. package/src/components/Form/FormField/fields/AsyncSelectField/index.ts +5 -0
  203. package/src/components/Form/FormField/fields/AutofillField/AutofillField.tsx +67 -0
  204. package/src/components/Form/FormField/fields/AutofillField/index.ts +5 -0
  205. package/src/components/Form/FormField/fields/BooleanField/BooleanField.stories.tsx +52 -0
  206. package/src/components/Form/FormField/fields/BooleanField/BooleanField.tsx +28 -0
  207. package/src/components/Form/FormField/fields/BooleanField/index.ts +5 -0
  208. package/src/components/Form/FormField/fields/ComboboxField/ComboboxField.tsx +104 -0
  209. package/src/components/Form/FormField/fields/ComboboxField/index.ts +5 -0
  210. package/src/components/Form/FormField/fields/DateField/DateField.stories.tsx +60 -0
  211. package/src/components/Form/{fields → FormField/fields/DateField}/DateField.tsx +12 -10
  212. package/src/components/Form/FormField/fields/DateField/index.ts +5 -0
  213. package/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.tsx +53 -0
  214. package/src/components/Form/{fields → FormField/fields/GeoPointField}/GeoPointField.tsx +19 -21
  215. package/src/components/Form/FormField/fields/GeoPointField/index.ts +5 -0
  216. package/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.stories.tsx +8 -8
  217. package/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.tsx +41 -41
  218. package/src/components/Form/FormField/fields/MarkdownField/index.ts +5 -0
  219. package/src/components/Form/FormField/fields/NumberField/NumberField.stories.tsx +52 -0
  220. package/src/components/Form/FormField/fields/NumberField/NumberField.test.ts +29 -0
  221. package/src/components/Form/FormField/fields/NumberField/NumberField.tsx +140 -0
  222. package/src/components/Form/FormField/fields/NumberField/index.ts +5 -0
  223. package/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.tsx +53 -0
  224. package/src/components/Form/FormField/fields/PasswordField/PasswordField.tsx +41 -0
  225. package/src/components/Form/FormField/fields/PasswordField/index.ts +5 -0
  226. package/src/components/Form/{fields → FormField/fields/RefField}/InlineRefField.tsx +21 -14
  227. package/src/components/Form/FormField/fields/RefField/RefField.stories.tsx +97 -0
  228. package/src/components/Form/{fields → FormField/fields/RefField}/RefField.test.ts +10 -1
  229. package/src/components/Form/{fields → FormField/fields/RefField}/RefField.tsx +55 -47
  230. package/src/components/Form/FormField/fields/RefField/index.ts +6 -0
  231. package/src/components/Form/FormField/fields/SelectField/SelectField.stories.tsx +53 -0
  232. package/src/components/Form/FormField/fields/SelectField/SelectField.tsx +136 -0
  233. package/src/components/Form/FormField/fields/SelectField/index.ts +5 -0
  234. package/src/components/Form/{fields → FormField/fields/SelectOptionField}/SelectOptionField.stories.tsx +5 -5
  235. package/src/components/Form/{fields → FormField/fields/SelectOptionField}/SelectOptionField.tsx +56 -54
  236. package/src/components/Form/FormField/fields/SelectOptionField/index.ts +5 -0
  237. package/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.tsx +53 -0
  238. package/src/components/Form/{fields → FormField/fields/TextAreaField}/TextAreaField.tsx +6 -4
  239. package/src/components/Form/FormField/fields/TextAreaField/index.ts +5 -0
  240. package/src/components/Form/FormField/fields/TextField/TextField.stories.tsx +52 -0
  241. package/src/components/Form/{fields → FormField/fields/TextField}/TextField.tsx +6 -4
  242. package/src/components/Form/FormField/fields/TextField/index.ts +5 -0
  243. package/src/components/Form/FormField/fields/TupleField/TupleField.stories.tsx +55 -0
  244. package/src/components/Form/{fields → FormField/fields/TupleField}/TupleField.tsx +4 -2
  245. package/src/components/Form/FormField/fields/TupleField/index.ts +5 -0
  246. package/src/components/Form/{fields → FormField/fields}/index.ts +4 -1
  247. package/src/components/Form/FormField/index.ts +10 -0
  248. package/src/components/Form/FormField/presentation.tsx +32 -0
  249. package/src/components/Form/{FormFieldSet.tsx → FormFieldSet/FormFieldSet.tsx} +53 -90
  250. package/src/components/Form/FormFieldSet/FormFieldSetContainer.tsx +93 -0
  251. package/src/components/Form/FormFieldSet/index.ts +6 -0
  252. package/src/components/Form/{Layout → FormLayout}/FormLayout.stories.tsx +11 -9
  253. package/src/components/Form/{Layout → FormLayout}/FormLayout.tsx +29 -18
  254. package/src/components/Form/FormLayout/index.ts +6 -0
  255. package/src/components/Form/index.ts +3 -5
  256. package/src/components/Form/meta-tags.test.ts +1 -1
  257. package/src/components/Form/meta-tags.ts +1 -1
  258. package/src/components/ObjectForm/ObjectForm.tsx +15 -14
  259. package/src/components/ObjectPicker/ObjectPicker.tsx +20 -27
  260. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +2 -2
  261. package/src/components/ObjectProperties/ObjectProperties.test.tsx +1 -1
  262. package/src/components/ObjectProperties/ObjectProperties.tsx +13 -8
  263. package/src/components/RefEditor/RefEditor.stories.tsx +2 -3
  264. package/src/components/RefEditor/RefEditor.tsx +1 -1
  265. package/src/components/ViewEditor/ViewEditor.stories.tsx +6 -7
  266. package/src/components/ViewEditor/ViewEditor.test.tsx +9 -3
  267. package/src/components/ViewEditor/ViewEditor.tsx +98 -128
  268. package/src/components/index.ts +0 -1
  269. package/src/hooks/index.ts +2 -0
  270. package/src/hooks/useAsyncFieldEffect.ts +81 -0
  271. package/src/hooks/useFormContext.ts +89 -0
  272. package/src/hooks/useFormHandler.ts +12 -10
  273. package/src/index.ts +2 -0
  274. package/src/{components/testing.tsx → testing/TestLayout.tsx} +16 -13
  275. package/src/testing/index.ts +7 -0
  276. package/src/testing/schema.ts +59 -0
  277. package/src/types.ts +186 -0
  278. package/src/util/index.ts +1 -1
  279. package/src/util/omit.ts +61 -0
  280. package/src/util/properties.test.ts +110 -28
  281. package/src/util/refs.ts +1 -1
  282. package/src/vitest-setup.ts +40 -0
  283. package/dist/types/src/components/Form/FormField.d.ts +0 -52
  284. package/dist/types/src/components/Form/FormField.d.ts.map +0 -1
  285. package/dist/types/src/components/Form/FormFieldComponent.d.ts +0 -98
  286. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +0 -1
  287. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +0 -1
  288. package/dist/types/src/components/Form/FormFieldSet.d.ts +0 -26
  289. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +0 -1
  290. package/dist/types/src/components/Form/FormTooltipsContext.d.ts +0 -19
  291. package/dist/types/src/components/Form/FormTooltipsContext.d.ts.map +0 -1
  292. package/dist/types/src/components/Form/Layout/FormLayout.d.ts.map +0 -1
  293. package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts.map +0 -1
  294. package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts.map +0 -1
  295. package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts.map +0 -1
  296. package/dist/types/src/components/Form/Layout/annotation.d.ts +0 -30
  297. package/dist/types/src/components/Form/Layout/annotation.d.ts.map +0 -1
  298. package/dist/types/src/components/Form/Layout/index.d.ts +0 -4
  299. package/dist/types/src/components/Form/Layout/index.d.ts.map +0 -1
  300. package/dist/types/src/components/Form/Layout/parser.d.ts.map +0 -1
  301. package/dist/types/src/components/Form/Layout/parser.test.d.ts.map +0 -1
  302. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +0 -1
  303. package/dist/types/src/components/Form/fields/BooleanField.d.ts +0 -4
  304. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +0 -1
  305. package/dist/types/src/components/Form/fields/DateField.d.ts +0 -4
  306. package/dist/types/src/components/Form/fields/DateField.d.ts.map +0 -1
  307. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +0 -5
  308. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +0 -1
  309. package/dist/types/src/components/Form/fields/InlineRefField.d.ts.map +0 -1
  310. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +0 -1
  311. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +0 -1
  312. package/dist/types/src/components/Form/fields/NumberField.d.ts +0 -4
  313. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +0 -1
  314. package/dist/types/src/components/Form/fields/RefField.d.ts +0 -34
  315. package/dist/types/src/components/Form/fields/RefField.d.ts.map +0 -1
  316. package/dist/types/src/components/Form/fields/RefField.test.d.ts.map +0 -1
  317. package/dist/types/src/components/Form/fields/SelectField.d.ts +0 -13
  318. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +0 -1
  319. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +0 -5
  320. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +0 -1
  321. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +0 -1
  322. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +0 -4
  323. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +0 -1
  324. package/dist/types/src/components/Form/fields/TextField.d.ts +0 -4
  325. package/dist/types/src/components/Form/fields/TextField.d.ts.map +0 -1
  326. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +0 -1
  327. package/dist/types/src/components/Form/fields/index.d.ts.map +0 -1
  328. package/dist/types/src/components/Settings/Settings.d.ts +0 -51
  329. package/dist/types/src/components/Settings/Settings.d.ts.map +0 -1
  330. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +0 -1
  331. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +0 -43
  332. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +0 -1
  333. package/dist/types/src/components/Settings/SettingsItem.d.ts +0 -16
  334. package/dist/types/src/components/Settings/SettingsItem.d.ts.map +0 -1
  335. package/dist/types/src/components/Settings/SettingsSelectField.d.ts +0 -18
  336. package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +0 -1
  337. package/dist/types/src/components/Settings/index.d.ts +0 -4
  338. package/dist/types/src/components/Settings/index.d.ts.map +0 -1
  339. package/dist/types/src/components/testing.d.ts.map +0 -1
  340. package/dist/types/src/util/field-type.d.ts +0 -19
  341. package/dist/types/src/util/field-type.d.ts.map +0 -1
  342. package/src/components/Form/AUDIT.md +0 -17
  343. package/src/components/Form/FormFieldComponent.stories.tsx +0 -65
  344. package/src/components/Form/FormFieldComponent.tsx +0 -253
  345. package/src/components/Form/FormTooltipsContext.ts +0 -25
  346. package/src/components/Form/Layout/annotation.ts +0 -38
  347. package/src/components/Form/Layout/index.ts +0 -7
  348. package/src/components/Form/README.md +0 -3
  349. package/src/components/Form/fields/ArrayField.tsx +0 -173
  350. package/src/components/Form/fields/BooleanField.tsx +0 -22
  351. package/src/components/Form/fields/NumberField.tsx +0 -76
  352. package/src/components/Form/fields/SelectField.tsx +0 -77
  353. package/src/components/Settings/AUDIT.md +0 -51
  354. package/src/components/Settings/Settings.stories.tsx +0 -104
  355. package/src/components/Settings/Settings.tsx +0 -81
  356. package/src/components/Settings/SettingsFieldSet.tsx +0 -257
  357. package/src/components/Settings/SettingsItem.tsx +0 -58
  358. package/src/components/Settings/SettingsSelectField.tsx +0 -59
  359. package/src/components/Settings/index.ts +0 -7
  360. package/src/util/field-type.ts +0 -60
  361. /package/dist/types/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.stories.d.ts +0 -0
  362. /package/dist/types/src/components/Form/{fields → FormField/fields/RefField}/InlineRefField.d.ts +0 -0
  363. /package/dist/types/src/components/Form/{fields → FormField/fields/RefField}/RefField.test.d.ts +0 -0
  364. /package/dist/types/src/components/Form/{fields → FormField/fields/SelectOptionField}/SelectOptionField.stories.d.ts +0 -0
  365. /package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.stories.test.d.ts +0 -0
  366. /package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.test.d.ts +0 -0
  367. /package/dist/types/src/components/Form/{Layout → FormLayout}/parser.d.ts +0 -0
  368. /package/dist/types/src/components/Form/{Layout → FormLayout}/parser.test.d.ts +0 -0
  369. /package/src/components/Form/{Layout → FormLayout}/FormLayout.stories.test.tsx +0 -0
  370. /package/src/components/Form/{Layout → FormLayout}/FormLayout.test.ts +0 -0
  371. /package/src/components/Form/{Layout → FormLayout}/parser.test.ts +0 -0
  372. /package/src/components/Form/{Layout → FormLayout}/parser.ts +0 -0
@@ -0,0 +1,68 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, { type ReactNode } from 'react';
6
+
7
+ import { CompactIconButton } from './FormField';
8
+ import { FormFieldLabel } from './FormRow';
9
+
10
+ export type FormFieldHeaderProps = {
11
+ label: string;
12
+ /** JSON path forwarded to the label as field metadata. */
13
+ path?: string;
14
+ required?: boolean;
15
+ readonly?: boolean;
16
+ classNames?: string;
17
+ /** Trailing inline add affordance; omit to hide it. */
18
+ add?: { icon?: string; label: string; disabled?: boolean; onClick: () => void };
19
+ /** Extra trailing controls placed after the add affordance (e.g. a collapse toggle). */
20
+ actions?: ReactNode;
21
+ /** Header row click (e.g. toggle collapse). The add affordance stops propagation so it doesn't trigger this. */
22
+ onClick?: () => void;
23
+ };
24
+
25
+ /**
26
+ * Header row for a labelled group or list: a label with optional trailing controls (an inline add
27
+ * affordance and/or arbitrary `actions`). Shared by array/list fields ({@link ArrayField}, the view
28
+ * editor) and {@link FormFieldSetContainer}'s collapse header, so all group/list headers render identically.
29
+ */
30
+ export const FormFieldHeader = ({
31
+ label,
32
+ path,
33
+ required,
34
+ readonly,
35
+ classNames,
36
+ add,
37
+ actions,
38
+ onClick,
39
+ }: FormFieldHeaderProps) => (
40
+ <FormFieldLabel
41
+ standalone
42
+ classNames={classNames}
43
+ label={label}
44
+ required={required}
45
+ readonly={readonly}
46
+ path={path}
47
+ button={
48
+ (!readonly && add) || actions ? (
49
+ <>
50
+ {!readonly && add && (
51
+ <CompactIconButton
52
+ disabled={add.disabled}
53
+ icon={add.icon ?? 'ph--plus--regular'}
54
+ label={add.label}
55
+ onClick={(event) => {
56
+ // Don't let the add click bubble to the header's onClick (e.g. collapse toggle).
57
+ event.stopPropagation();
58
+ add.onClick();
59
+ }}
60
+ />
61
+ )}
62
+ {actions}
63
+ </>
64
+ ) : undefined
65
+ }
66
+ onClick={onClick}
67
+ />
68
+ );
@@ -0,0 +1,97 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { type ChangeEvent, useCallback } from 'react';
7
+
8
+ import { Input } from '@dxos/react-ui';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
+
11
+ import { type FormFieldRendererProps } from '#types';
12
+
13
+ import { Form } from '../Form';
14
+ import { FormRow } from './FormRow';
15
+
16
+ const DefaultStory = ({
17
+ type,
18
+ placeholder,
19
+ readonly,
20
+ onBlur,
21
+ onValueChange,
22
+ ...props
23
+ }: FormFieldRendererProps<string>) => {
24
+ const handleChange = useCallback(
25
+ (event: ChangeEvent<HTMLInputElement>) => onValueChange(type, event.target.value),
26
+ [type, onValueChange],
27
+ );
28
+
29
+ return (
30
+ <Form.Root>
31
+ <Form.Viewport>
32
+ <Form.Content>
33
+ <FormRow<string> {...props}>
34
+ {({ value = '' }) => (
35
+ <Input.TextInput
36
+ disabled={!!readonly}
37
+ placeholder={placeholder}
38
+ noAutoFill
39
+ value={value}
40
+ onChange={handleChange}
41
+ onBlur={onBlur}
42
+ />
43
+ )}
44
+ </FormRow>
45
+ </Form.Content>
46
+ </Form.Viewport>
47
+ </Form.Root>
48
+ );
49
+ };
50
+
51
+ const meta: Meta<typeof DefaultStory> = {
52
+ title: 'ui/react-ui-form/FormRow',
53
+ component: DefaultStory,
54
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
55
+ parameters: {
56
+ layout: 'fullscreen',
57
+ },
58
+ };
59
+
60
+ export default meta;
61
+
62
+ type Story = StoryObj<typeof meta>;
63
+
64
+ const props: Story['args'] = {
65
+ label: 'Label',
66
+ required: true,
67
+ getStatus: () => ({ status: 'error', error: 'Required field' }),
68
+ getValue: () => 'DXOS',
69
+ };
70
+
71
+ export const Default: Story = {
72
+ args: {
73
+ ...props,
74
+ presentation: 'full',
75
+ },
76
+ };
77
+
78
+ export const Compact: Story = {
79
+ args: {
80
+ ...props,
81
+ presentation: 'compact',
82
+ },
83
+ };
84
+
85
+ export const Inline: Story = {
86
+ args: {
87
+ ...props,
88
+ presentation: 'inline',
89
+ },
90
+ };
91
+
92
+ export const Static: Story = {
93
+ args: {
94
+ ...props,
95
+ presentation: 'static',
96
+ },
97
+ };
@@ -0,0 +1,298 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { format as formatDate } from 'date-fns';
6
+ import React, { Component, type PropsWithChildren, type ReactNode, type Ref } from 'react';
7
+
8
+ import { Format } from '@dxos/echo';
9
+ import { Icon, Input, type ThemedClassName, Tooltip } from '@dxos/react-ui';
10
+ import { mx } from '@dxos/ui-theme';
11
+
12
+ import { type FormFieldRendererProps } from '#types';
13
+
14
+ import { useFormContext } from '../../../hooks';
15
+ import { type FormVariant, formTheme } from '../Form.theme';
16
+ import { type FormFieldPresentation, presentationFor } from './presentation';
17
+
18
+ //
19
+ // FormFieldLabel
20
+ //
21
+
22
+ export type FormFieldLabelProps = ThemedClassName<
23
+ {
24
+ /** Render a plain `<span>` instead of an input-associated `Input.Label`, for labels used outside an `Input.Root` (e.g. section/group headers). */
25
+ standalone?: boolean;
26
+ /** Class applied to the inner label text node, overriding the default size/color (e.g. `text-lg`). */
27
+ labelClassName?: string;
28
+ /** Form variant; selects the label's chrome (e.g. `settings` enlarges the text and places it in the field grid). */
29
+ variant?: FormVariant;
30
+ error?: string;
31
+ /**
32
+ * JSON path of the field this label describes (e.g. `runtime.client.storage.persistent`).
33
+ * Field metadata; accepted by callers but not currently rendered.
34
+ */
35
+ path?: string;
36
+ /**
37
+ * Trailing button rendered at the end of the label row (third grid column).
38
+ * Used by nested-object field sets to surface a collapse toggle.
39
+ */
40
+ button?: ReactNode;
41
+ onClick?: () => void;
42
+ } & Pick<FormFieldRendererProps, 'label' | 'readonly' | 'required'>
43
+ >;
44
+
45
+ export const FormFieldLabel = ({
46
+ classNames,
47
+ labelClassName,
48
+ variant = 'default',
49
+ label,
50
+ error,
51
+ readonly,
52
+ required,
53
+ standalone,
54
+ button,
55
+ onClick,
56
+ }: FormFieldLabelProps) => {
57
+ const styles = formTheme.styles({ variant });
58
+ // Render the required asterisk via a `::after` pseudo-element rather than a DOM node: it keeps the
59
+ // label's `textContent` exactly `label`, so fields stay locatable by their exact label text
60
+ // (`getByLabelText('Name')`), which the DOM-text-based query would otherwise miss as `Name *`.
61
+ // The `fieldLabelText` slot is applied last so a variant/caller size/color (e.g. `text-lg`) wins over
62
+ const labelClassNames = mx(
63
+ 'text-sm text-description',
64
+ required && "after:content-['*'] after:ms-0.5 after:text-warning-text",
65
+ styles.fieldLabelText({ class: labelClassName }),
66
+ );
67
+
68
+ // `Input.Label` is a themed primitive that reads `classNames` (and ignores `className`), whereas the
69
+ // plain `span` used for read-only/standalone labels reads `className`.
70
+ const labelNode =
71
+ readonly || standalone ? (
72
+ <span className={labelClassNames}>{label}</span>
73
+ ) : (
74
+ <Input.Label classNames={labelClassNames}>{label}</Input.Label>
75
+ );
76
+
77
+ return (
78
+ <div className={styles.fieldLabel({ class: mx(onClick && 'cursor-pointer', classNames) })} onClick={onClick}>
79
+ {labelNode}
80
+ {error ? (
81
+ <Tooltip.Trigger asChild content={error} side='bottom'>
82
+ <Icon icon='ph--warning--regular' size={4} classNames='text-error-text' />
83
+ </Tooltip.Trigger>
84
+ ) : (
85
+ <span />
86
+ )}
87
+ {button}
88
+ </div>
89
+ );
90
+ };
91
+
92
+ FormFieldLabel.displayName = 'Form.FieldLabel';
93
+
94
+ //
95
+ // FormRow
96
+ //
97
+
98
+ /**
99
+ * Formats a value for `static` (read-only, plain-DOM) presentation based on its
100
+ * type `format`. Dates/times are rendered human-readable; everything else falls
101
+ * back to `String(value)`.
102
+ */
103
+ const formatStaticValue = (value: unknown, format?: Format.TypeFormat): string => {
104
+ if (value == null) {
105
+ return '';
106
+ }
107
+
108
+ switch (format) {
109
+ case Format.TypeFormat.DateTime:
110
+ case Format.TypeFormat.Date:
111
+ case Format.TypeFormat.Time: {
112
+ const date = new Date(value as string);
113
+ if (Number.isNaN(date.getTime())) {
114
+ return String(value);
115
+ }
116
+ const pattern = format === Format.TypeFormat.DateTime ? 'PPp' : format === Format.TypeFormat.Date ? 'PP' : 'p';
117
+ return formatDate(date, pattern);
118
+ }
119
+ default:
120
+ return String(value);
121
+ }
122
+ };
123
+
124
+ const FORM_ROW_NAME = 'Form.Row';
125
+
126
+ export type FormRowProps<T = any> = ThemedClassName<
127
+ Pick<FormFieldRendererProps<T>, 'readonly' | 'label' | 'description' | 'presentation' | 'required'> &
128
+ Partial<Pick<FormFieldRendererProps<T>, 'getStatus' | 'getValue' | 'jsonPath' | 'format'>> & {
129
+ /**
130
+ * Ref to the row's outer element. Enables measurement/positioning (e.g. a future virtualized
131
+ * field set) without forwarding through the generic component.
132
+ */
133
+ rootRef?: Ref<HTMLDivElement>;
134
+ /**
135
+ * Render the label as a standalone `<span>` (group/multi-input fields with no single associated control).
136
+ */
137
+ standalone?: boolean;
138
+ /**
139
+ * Override the read-only/`static` rendering of the value. Fields whose value is not plain text
140
+ * (refs, selects, markdown) supply this; the default formats scalars/dates via `formatStaticValue`.
141
+ * Return `null` to render nothing (e.g. an empty/unresolved value).
142
+ */
143
+ renderStatic?: (value: T | undefined) => ReactNode;
144
+ /**
145
+ * Validation/error content rendered in the validation slot. Supplied directly in action mode; in
146
+ * field mode it is derived from `getStatus`.
147
+ */
148
+ validation?: ReactNode;
149
+ /**
150
+ * The control. A render-prop binds to the form value (field mode: `Input.Root` validation, static
151
+ * rendering, value via `getValue`). Plain nodes render an arbitrary control with no value wiring
152
+ * (action mode, e.g. a button) — the labeled-card escape hatch for an arbitrary control.
153
+ *
154
+ * `value` is `T | undefined` because `getValue()` returns no value when the field is unset (optional
155
+ * schema properties, freshly-added array items); renderers default it (e.g. `{ value = '' }`).
156
+ */
157
+ children?: ReactNode | ((props: { value: T | undefined; presentation: FormFieldPresentation }) => ReactNode);
158
+ }
159
+ >;
160
+
161
+ /**
162
+ * A labeled card row — the single shell behind both schema fields and free-form "action" rows. Field
163
+ * renderers pass a render-prop `children` (field mode: bound to the form value, with validation and
164
+ * static rendering); consumers pass plain `children` for an arbitrary control (action mode), the
165
+ * affordance for an arbitrary labeled control (action rows, settings cards).
166
+ */
167
+ export const FormRow = <T,>({
168
+ classNames,
169
+ children,
170
+ readonly,
171
+ presentation,
172
+ label,
173
+ description,
174
+ jsonPath,
175
+ format,
176
+ required,
177
+ standalone,
178
+ renderStatic,
179
+ validation,
180
+ getStatus,
181
+ getValue,
182
+ rootRef,
183
+ }: FormRowProps<T>) => {
184
+ const { variant = 'default' } = useFormContext(FORM_ROW_NAME);
185
+ const styles = formTheme.styles({ variant });
186
+ const { showDescription } = formTheme.behavior[variant];
187
+ const resolved = presentationFor(presentation);
188
+
189
+ //
190
+ // Field mode: a render-prop control bound to the form value.
191
+ //
192
+ if (typeof children === 'function' && getStatus && getValue) {
193
+ const { status, error } = getStatus();
194
+ const value: T | undefined = getValue();
195
+ if (resolved.isStatic && value == null) {
196
+ return null;
197
+ }
198
+
199
+ const control = resolved.isStatic
200
+ ? (renderStatic?.(value) ?? <p className='truncate min-w-0'>{formatStaticValue(value, format)}</p>)
201
+ : children({ value, presentation: resolved });
202
+
203
+ return (
204
+ <Input.Root validationValence={status}>
205
+ <div className={styles.field({ class: classNames })} ref={rootRef}>
206
+ {resolved.showLabel && (
207
+ <FormFieldLabel
208
+ variant={variant}
209
+ error={error}
210
+ readonly={readonly}
211
+ required={required}
212
+ standalone={standalone}
213
+ label={label}
214
+ path={jsonPath}
215
+ />
216
+ )}
217
+ {showDescription && description && (
218
+ <Input.Description classNames={styles.fieldDescription()}>{description}</Input.Description>
219
+ )}
220
+ <div className={styles.fieldControl()}>{control}</div>
221
+ {resolved.showError && error && (
222
+ <div className={styles.fieldValidation()}>
223
+ <Input.DescriptionAndValidation>
224
+ <Input.Validation>{error}</Input.Validation>
225
+ </Input.DescriptionAndValidation>
226
+ </div>
227
+ )}
228
+ </div>
229
+ </Input.Root>
230
+ );
231
+ }
232
+
233
+ //
234
+ // Action mode: an arbitrary control with a standalone label; no value/`Input.Root` wiring.
235
+ //
236
+ return (
237
+ <div ref={rootRef} className={styles.field({ class: classNames })}>
238
+ {resolved.showLabel && label && (
239
+ <FormFieldLabel
240
+ variant={variant}
241
+ readonly={readonly}
242
+ required={required}
243
+ standalone
244
+ label={label}
245
+ path={jsonPath}
246
+ />
247
+ )}
248
+ {showDescription && description && <p className={styles.fieldDescription()}>{description}</p>}
249
+ <div className={styles.fieldControl()}>{typeof children === 'function' ? null : children}</div>
250
+ {validation && <div className={styles.fieldValidation()}>{validation}</div>}
251
+ </div>
252
+ );
253
+ };
254
+
255
+ FormRow.displayName = FORM_ROW_NAME;
256
+
257
+ //
258
+ // FormFieldErrorBoundary
259
+ //
260
+
261
+ type FormFieldErrorState = {
262
+ error: Error | undefined;
263
+ };
264
+
265
+ type FormFieldErrorBoundaryProps = PropsWithChildren<{
266
+ path?: (string | number)[];
267
+ }>;
268
+
269
+ export class FormFieldErrorBoundary extends Component<FormFieldErrorBoundaryProps, FormFieldErrorState> {
270
+ static getDerivedStateFromError(error: Error): { error: Error } {
271
+ return { error };
272
+ }
273
+
274
+ override state = { error: undefined };
275
+
276
+ override componentDidUpdate(prevProps: FormFieldErrorBoundaryProps) {
277
+ if (prevProps.path !== this.props.path) {
278
+ this.resetError();
279
+ }
280
+ }
281
+
282
+ override render() {
283
+ if (this.state.error) {
284
+ return (
285
+ <div className='flex gap-2 border border-error-border font-mono text-sm'>
286
+ <span className='bg-error-bg text-base-fg px-1 font-thin'>ERROR</span>
287
+ {String(this.props.path?.join('.'))}
288
+ </div>
289
+ );
290
+ }
291
+
292
+ return this.props.children;
293
+ }
294
+
295
+ private resetError() {
296
+ this.setState({ error: undefined });
297
+ }
298
+ }
@@ -0,0 +1,119 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Schema from 'effect/Schema';
7
+ import React, { useState } from 'react';
8
+
9
+ import { FormLayoutAnnotation, FormOrderedAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
+ import { trim } from '@dxos/util';
12
+
13
+ import { translations } from '#translations';
14
+
15
+ import { TestLayout } from '../../../../../testing';
16
+ import { Form } from '../../../Form';
17
+
18
+ // Mirrors the structure of `Pipeline` (`@dxos/types`): a scalar header plus an
19
+ // array of `Column`-like structs. The array is the field we render as either a
20
+ // static or an ordered (drag-to-reorder) list.
21
+ const Column = Schema.Struct({
22
+ name: Schema.String.annotations({ title: 'Name' }),
23
+ value: Schema.optional(Schema.Number.annotations({ title: 'Value' })),
24
+ }).pipe(
25
+ Schema.mutable,
26
+ LabelAnnotation.set(['name']),
27
+ FormLayoutAnnotation.set({
28
+ default: trim`
29
+ <grid cols="2">
30
+ <field name="name"/>
31
+ <field name="value"/>
32
+ </grid>
33
+ `,
34
+ }),
35
+ );
36
+ type Column = Schema.Schema.Type<typeof Column>;
37
+
38
+ const columnsField = Schema.Array(Column).pipe(Schema.mutable, Schema.annotations({ title: 'Columns' }));
39
+
40
+ const headerFields = {
41
+ name: Schema.String.pipe(Schema.annotations({ title: 'Name' }), Schema.optional),
42
+ description: Schema.String.pipe(Schema.annotations({ title: 'Description' }), Schema.optional),
43
+ };
44
+
45
+ const Pipeline = Schema.Struct({
46
+ ...headerFields,
47
+ columns: columnsField,
48
+ }).pipe(Schema.mutable);
49
+
50
+ const OrderedPipeline = Schema.Struct({
51
+ ...headerFields,
52
+ columns: columnsField.pipe(FormOrderedAnnotation.set(true)),
53
+ }).pipe(Schema.mutable);
54
+
55
+ const StringPipeline = Schema.Struct({
56
+ ...headerFields,
57
+ columns: Schema.Array(Schema.String).pipe(FormOrderedAnnotation.set(true)),
58
+ }).pipe(Schema.mutable);
59
+
60
+ type PipelineValues = { name?: string; description?: string; columns: readonly unknown[] };
61
+
62
+ // Object-array columns (matches `Pipeline` / `OrderedPipeline`).
63
+ const objectColumns: PipelineValues = {
64
+ name: 'Sales',
65
+ description: 'Lead qualification pipeline.',
66
+ columns: [{ name: 'Contacts' }, { name: 'Organizations' }, { name: 'Tasks' }, { name: 'Messages', value: 100 }],
67
+ };
68
+
69
+ // String-array columns (matches `StringPipeline`); feeding object columns here renders `[object Object]`.
70
+ const stringColumns: PipelineValues = {
71
+ name: 'Sales',
72
+ description: 'Lead qualification pipeline.',
73
+ columns: ['Contacts', 'Organizations', 'Tasks', 'Messages'],
74
+ };
75
+
76
+ const DefaultStory = ({ schema, values: initial }: { schema: Schema.Schema<any>; values: PipelineValues }) => {
77
+ const [values, setValues] = useState<PipelineValues>(initial);
78
+
79
+ return (
80
+ <TestLayout json={values}>
81
+ <Form.Root
82
+ schema={schema}
83
+ values={values}
84
+ onValuesChanged={(next) => setValues((prev) => ({ ...prev, ...next }))}
85
+ >
86
+ <Form.Viewport scroll>
87
+ <Form.Content>
88
+ <Form.FieldSet />
89
+ </Form.Content>
90
+ </Form.Viewport>
91
+ </Form.Root>
92
+ </TestLayout>
93
+ );
94
+ };
95
+
96
+ const meta = {
97
+ title: 'ui/react-ui-form/FormField/ArrayField',
98
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
99
+ parameters: {
100
+ layout: 'fullscreen',
101
+ translations,
102
+ },
103
+ } satisfies Meta<typeof DefaultStory>;
104
+
105
+ export default meta;
106
+
107
+ type Story = StoryObj<typeof meta>;
108
+
109
+ export const Default: Story = {
110
+ render: () => <DefaultStory schema={Pipeline} values={objectColumns} />,
111
+ };
112
+
113
+ export const Ordered: Story = {
114
+ render: () => <DefaultStory schema={OrderedPipeline} values={objectColumns} />,
115
+ };
116
+
117
+ export const Simple: Story = {
118
+ render: () => <DefaultStory schema={StringPipeline} values={stringColumns} />,
119
+ };