@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,377 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type PropsWithChildren, useRef } from 'react';
6
+
7
+ import { type AnyProperties } from '@dxos/echo/internal';
8
+ import {
9
+ Column,
10
+ type ColumnRootProps,
11
+ IconButton,
12
+ type IconButtonProps,
13
+ Input,
14
+ ScrollArea,
15
+ type ThemedClassName,
16
+ composable,
17
+ composableProps,
18
+ useMergeRefs,
19
+ useTranslation,
20
+ withColumn,
21
+ } from '@dxos/react-ui';
22
+ import { MarkdownView } from '@dxos/react-ui-markdown';
23
+ import { mx } from '@dxos/ui-theme';
24
+ import { type Merge } from '@dxos/util';
25
+
26
+ import { translationKey } from '#translations';
27
+
28
+ import {
29
+ FormContextProvider,
30
+ type FormContextValue,
31
+ type FormHandlerProps,
32
+ type FormUpdateMeta,
33
+ useFormContext,
34
+ useFormHandler,
35
+ useKeyHandler,
36
+ } from '../../hooks';
37
+ import { formTheme } from './Form.theme';
38
+ import { FormFieldSet, type FormFieldSetProps as NaturalFormFieldSetProps } from './FormFieldSet';
39
+ import { FormLayout, type FormLayoutProps as NaturalFormLayoutProps } from './FormLayout';
40
+
41
+ //
42
+ // Root
43
+ //
44
+
45
+ export type FormRootProps<T extends AnyProperties = AnyProperties> = Merge<
46
+ PropsWithChildren<{
47
+ /**
48
+ * Called when the form is submitted and passes validation.
49
+ */
50
+ onSave?: (values: T, meta: FormUpdateMeta<T>) => void;
51
+
52
+ /**
53
+ * Called when the form is canceled to abandon/undo any pending changes.
54
+ */
55
+ onCancel?: () => void;
56
+ }>,
57
+ Omit<FormContextValue<T>, 'form'>,
58
+ Pick<FormHandlerProps<T>, 'schema' | 'autoSave' | 'values' | 'defaultValues' | 'onValidate' | 'onValuesChanged'>,
59
+ Omit<NaturalFormFieldSetProps<T>, 'schema' | 'path'>
60
+ >;
61
+
62
+ // `variant` (from FormContextValue) flows through `...props` into the context for all parts to read.
63
+ export const FormRoot = <T extends AnyProperties = AnyProperties>({
64
+ children,
65
+ schema,
66
+ values,
67
+ onSave,
68
+ onCancel,
69
+ ...props
70
+ }: FormRootProps<T>) => {
71
+ const form = useFormHandler({ schema, values, onSave, onCancel, ...props });
72
+
73
+ return (
74
+ <FormContextProvider form={form} {...props}>
75
+ {children}
76
+ </FormContextProvider>
77
+ );
78
+ };
79
+
80
+ FormRoot.displayName = 'Form.Root';
81
+
82
+ //
83
+ // Viewport
84
+ //
85
+
86
+ const FORM_VIEWPORT_NAME = 'Form.Viewport';
87
+
88
+ export type FormViewportProps = {
89
+ scroll?: boolean;
90
+ gutter?: ColumnRootProps['gutter'];
91
+ };
92
+
93
+ // The viewing window: owns the gutter Column (chrome/side-padding).
94
+ // Content-height by default; `scroll` makes it fill its parent and scroll (the gutter then hosts the scrollbar).
95
+ export const FormViewport = composable<HTMLDivElement, FormViewportProps>(
96
+ ({ children, scroll, gutter = 'sm', ...props }, forwardedRef) => {
97
+ const { variant = 'default' } = useFormContext(FORM_VIEWPORT_NAME);
98
+ const styles = formTheme.styles({ variant });
99
+ // Span the full width when nested inside another Column grid (e.g. Card.Root)
100
+ // instead of landing in a single narrow track.
101
+ const span = '[.dx-column-root_&]:col-span-full';
102
+ if (scroll) {
103
+ return (
104
+ <Column.Root gutter={gutter} classNames={['dx-expander', span]}>
105
+ <ScrollArea.Root
106
+ {...composableProps(props, { classNames: styles.viewport() })}
107
+ orientation='vertical'
108
+ centered
109
+ padding
110
+ thin
111
+ ref={forwardedRef}
112
+ >
113
+ <ScrollArea.Viewport>{children}</ScrollArea.Viewport>
114
+ </ScrollArea.Root>
115
+ </Column.Root>
116
+ );
117
+ }
118
+
119
+ return (
120
+ <Column.Root
121
+ {...composableProps(props, { classNames: ['w-full min-w-0', span, styles.viewport()] })}
122
+ gutter={gutter}
123
+ ref={forwardedRef}
124
+ >
125
+ {children}
126
+ </Column.Root>
127
+ );
128
+ },
129
+ );
130
+
131
+ FormViewport.displayName = FORM_VIEWPORT_NAME;
132
+
133
+ //
134
+ // Content
135
+ //
136
+
137
+ const FORM_CONTENT_NAME = 'Form.Content';
138
+
139
+ export type FormContentProps = ThemedClassName<PropsWithChildren<{}>>;
140
+
141
+ // The viewed body: centered in the viewport's gutter. Pure body — the gutter Column is owned by `Form.Viewport`.
142
+ export const FormContent = composable<HTMLDivElement, FormContentProps>(({ children, ...props }, forwardedRef) => {
143
+ const { form, testId, variant = 'default' } = useFormContext(FORM_CONTENT_NAME);
144
+ const styles = formTheme.styles({ variant });
145
+ const localRef = useRef<HTMLDivElement>(null);
146
+ const mergedRef = useMergeRefs([forwardedRef, localRef]);
147
+ useKeyHandler(localRef, form);
148
+
149
+ return (
150
+ <div
151
+ {...composableProps(props, {
152
+ role: 'form',
153
+ classNames: mx(withColumn.center(), 'flex flex-col w-full dx-document', styles.content()),
154
+ })}
155
+ data-testid={testId}
156
+ ref={mergedRef}
157
+ >
158
+ {children}
159
+ </div>
160
+ );
161
+ });
162
+
163
+ FormContent.displayName = FORM_CONTENT_NAME;
164
+
165
+ //
166
+ // FieldSet
167
+ //
168
+
169
+ const FORM_FIELDSET_NAME = 'Form.FieldSet';
170
+
171
+ export type FormFieldSetControllerProps = ThemedClassName<NaturalFormFieldSetProps<any>>;
172
+
173
+ /** Context-reading binding for `Form.FieldSet`: pulls the schema + field context off the form and delegates to {@link FormFieldSet}. */
174
+ export const FormFieldSetController = ({ classNames, ...props }: FormFieldSetControllerProps) => {
175
+ const { form, variant = 'default', ...contextProps } = useFormContext(FORM_FIELDSET_NAME);
176
+ const styles = formTheme.styles({ variant });
177
+ return (
178
+ <FormFieldSet
179
+ schema={form.schema}
180
+ classNames={styles.fieldSet({ class: classNames })}
181
+ {...contextProps}
182
+ {...props}
183
+ />
184
+ );
185
+ };
186
+
187
+ FormFieldSetController.displayName = FORM_FIELDSET_NAME;
188
+
189
+ //
190
+ // Layout
191
+ //
192
+
193
+ const FORM_LAYOUT_NAME = 'Form.Layout';
194
+
195
+ export type FormLayoutProps = Omit<NaturalFormLayoutProps, 'schema'> & { schema?: NaturalFormLayoutProps['schema'] };
196
+
197
+ /** Context-reading binding for `Form.Layout`: resolves the schema (prop or form) and delegates to {@link FormLayout}. */
198
+ export const FormLayoutController = ({ schema, ...props }: FormLayoutProps) => {
199
+ const { form, ...contextProps } = useFormContext(FORM_LAYOUT_NAME);
200
+ const resolvedSchema = schema ?? form.schema;
201
+ if (!resolvedSchema) {
202
+ return null;
203
+ }
204
+
205
+ return <FormLayout schema={resolvedSchema} {...contextProps} {...props} />;
206
+ };
207
+
208
+ FormLayoutController.displayName = FORM_LAYOUT_NAME;
209
+
210
+ //
211
+ // Actions
212
+ //
213
+
214
+ const FORM_ACTIONS_NAME = 'Form.Actions';
215
+
216
+ export type FormActionsProps = ThemedClassName<{}>;
217
+
218
+ export const FormActions = ({ classNames }: FormActionsProps) => {
219
+ const { t } = useTranslation(translationKey);
220
+ const {
221
+ form: { canSave, onSave, onCancel },
222
+ readonly,
223
+ layout,
224
+ } = useFormContext(FORM_ACTIONS_NAME);
225
+
226
+ if (readonly || layout === 'static') {
227
+ return null;
228
+ }
229
+
230
+ // TODO(burdon): Currently onCancel is a no-op; implement "revert values".
231
+ // Deprecate FormSubmit ans use FormActions without Cancel button if no callback is supplied.
232
+
233
+ return (
234
+ <div className={mx(withColumn.center(), formTheme.styles().actions(), classNames)}>
235
+ {onCancel && (
236
+ <IconButton
237
+ icon='ph--x--regular'
238
+ iconEnd
239
+ label={t('cancel-button.label')}
240
+ onClick={onCancel}
241
+ data-testid='cancel-button'
242
+ />
243
+ )}
244
+ {onSave && (
245
+ <IconButton
246
+ type='submit'
247
+ variant='primary'
248
+ disabled={!canSave}
249
+ icon='ph--check--regular'
250
+ iconEnd
251
+ label={t('save-button.label')}
252
+ onClick={onSave}
253
+ data-testid='save-button'
254
+ />
255
+ )}
256
+ </div>
257
+ );
258
+ };
259
+
260
+ FormActions.displayName = FORM_ACTIONS_NAME;
261
+
262
+ //
263
+ // Section
264
+ //
265
+
266
+ const FORM_SECTION_NAME = 'Form.Section';
267
+
268
+ export type FormSectionProps = ThemedClassName<{
269
+ title?: string;
270
+ description?: string;
271
+ }>;
272
+
273
+ export const FormSection = composable<HTMLDivElement, FormSectionProps>(
274
+ ({ children, title, description, ...props }, forwardedRef) => {
275
+ const { variant = 'default' } = useFormContext(FORM_SECTION_NAME);
276
+ const styles = formTheme.styles({ variant });
277
+ return (
278
+ <div {...composableProps(props, { classNames: styles.section() })} ref={forwardedRef}>
279
+ {(title || description) && (
280
+ <div className={styles.sectionHeader()}>
281
+ {title && <h2 className={styles.sectionTitle()}>{title}</h2>}
282
+ {description && <MarkdownView classNames={styles.sectionDescription()} content={description} />}
283
+ </div>
284
+ )}
285
+ {children}
286
+ </div>
287
+ );
288
+ },
289
+ );
290
+
291
+ FormSection.displayName = FORM_SECTION_NAME;
292
+
293
+ //
294
+ // Group
295
+ //
296
+
297
+ const FORM_GROUP_NAME = 'Form.Group';
298
+
299
+ export type FormGroupProps = ThemedClassName<PropsWithChildren>;
300
+
301
+ /**
302
+ * A bordered card grouping related rows/controls (e.g. an entity card in a list).
303
+ * Layout-only and context-free — unlike `Form.FieldSet` it is not schema-driven.
304
+ */
305
+ export const FormGroup = composable<HTMLDivElement, FormGroupProps>(({ children, ...props }, forwardedRef) => {
306
+ const { variant = 'default' } = useFormContext(FORM_SECTION_NAME);
307
+ const styles = formTheme.styles({ variant });
308
+
309
+ return (
310
+ <div {...composableProps(props, { classNames: styles.group() })} ref={forwardedRef}>
311
+ {children}
312
+ </div>
313
+ );
314
+ });
315
+
316
+ FormGroup.displayName = FORM_GROUP_NAME;
317
+
318
+ //
319
+ // Submit
320
+ //
321
+
322
+ const FORM_SUBMIT_NAME = 'Form.Submit';
323
+
324
+ export type FormSubmitProps = ThemedClassName<Partial<Pick<IconButtonProps, 'icon' | 'label' | 'disabled'>>>;
325
+
326
+ export const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
327
+ const { t } = useTranslation(translationKey);
328
+ const {
329
+ form: { canSave, onSave },
330
+ readonly,
331
+ layout,
332
+ } = useFormContext(FORM_SUBMIT_NAME);
333
+
334
+ if (readonly || layout === 'static') {
335
+ return null;
336
+ }
337
+
338
+ return (
339
+ <div className={mx(formTheme.styles().submit(), classNames)}>
340
+ <IconButton
341
+ classNames='w-full'
342
+ type='submit'
343
+ variant='primary'
344
+ disabled={disabled ?? !canSave}
345
+ icon={icon ?? 'ph--check--regular'}
346
+ label={label ?? t('save-button.label')}
347
+ onClick={onSave}
348
+ data-testid='save-button'
349
+ />
350
+ </div>
351
+ );
352
+ };
353
+
354
+ FormSubmit.displayName = FORM_SUBMIT_NAME;
355
+
356
+ //
357
+ // Error
358
+ //
359
+
360
+ const FORM_ERROR_NAME = 'Form.Error';
361
+
362
+ export type FormErrorProps = ThemedClassName<PropsWithChildren>;
363
+
364
+ /** Form-level error/validation message (e.g. a failed submit), styled via the error valence. */
365
+ export const FormError = ({ children, classNames }: FormErrorProps) => {
366
+ if (!children) {
367
+ return null;
368
+ }
369
+
370
+ return (
371
+ <Input.Root validationValence='error'>
372
+ <Input.Validation classNames={classNames}>{children}</Input.Validation>
373
+ </Input.Root>
374
+ );
375
+ };
376
+
377
+ FormError.displayName = FORM_ERROR_NAME;
@@ -12,34 +12,31 @@ import React, { useMemo } from 'react';
12
12
  import { Annotation, Format } from '@dxos/echo';
13
13
  import { SchemaEx } from '@dxos/effect';
14
14
  import { IconButton, IconButtonProps, useTranslation } from '@dxos/react-ui';
15
- import { type ProjectionModel } from '@dxos/schema';
16
- import { mx } from '@dxos/ui-theme';
17
15
 
18
16
  import { translationKey } from '#translations';
17
+ import { type FieldContext, type FormFieldRenderer, type FormFieldRendererProps } from '#types';
19
18
 
20
- import { getRefProps } from '../../util';
19
+ import { AutofillAnnotation, OptionsLookupAnnotation } from '../../../annotations';
20
+ import { useFormFieldState } from '../../../hooks';
21
+ import { getRefProps } from '../../../util';
22
+ import { FormFieldSet } from '../FormFieldSet';
21
23
  import {
22
24
  ArrayField,
25
+ AsyncSelectField,
26
+ AutofillField,
23
27
  BooleanField,
28
+ ComboboxField,
24
29
  DateField,
25
30
  GeoPointField,
26
31
  InlineRefField,
27
32
  MarkdownField,
28
33
  NumberField,
34
+ PasswordField,
29
35
  RefField,
30
- type RefFieldProps,
31
36
  SelectField,
32
37
  TextAreaField,
33
38
  TextField,
34
39
  } from './fields';
35
- import { useFormFieldState } from './Form';
36
- import {
37
- type FormFieldComponent,
38
- type FormFieldComponentProps,
39
- type FormFieldMap,
40
- type FormFieldProvider,
41
- } from './FormFieldComponent';
42
- import { FormFieldSet } from './FormFieldSet';
43
40
 
44
41
  export type FormFieldProps = {
45
42
  /**
@@ -58,50 +55,34 @@ export type FormFieldProps = {
58
55
  name: string | null;
59
56
 
60
57
  /**
61
- * Path to the current object from the root. Used with nested forms.
62
- */
63
- path?: (string | number)[];
64
-
65
- /**
66
- * Optional projection for projection-based field management.
67
- */
68
- projection?: ProjectionModel;
69
-
70
- /**
71
- * Map of custom renderers for specific properties.
72
- * Prefer fieldProvider for plugin specific input surfaces.
58
+ * Explicit label, overriding the `title ?? capitalize(name)` derivation. Used by `ArrayField` to
59
+ * give scalar/ref items the array's resolved title (e.g. `Tags`) rather than re-capitalizing the
60
+ * raw array property name (e.g. `_tags`), since the element type carries no title of its own.
73
61
  */
74
- fieldMap?: FormFieldMap;
62
+ label?: string;
75
63
 
76
64
  /**
77
- * Function to lookup custom renderers for specific properties.
65
+ * Path to the current object from the root. Used with nested forms.
78
66
  */
79
- fieldProvider?: FormFieldProvider;
80
-
67
+ path?: (string | number)[];
68
+ autoFocus?: boolean;
69
+ /** Whether the field is required (non-optional in the schema). Drives the label asterisk. */
70
+ required?: boolean;
81
71
  /**
82
- * Typename of the ref type that the create props apply to.
83
- * When set, createOptionLabel/createOptionIcon/createInitialValuePath/createFieldMap/onCreate
84
- * are only passed to ref fields whose typename matches.
72
+ * Force a `Ref` field to render its target inline (a nested form) instead of the picker.
73
+ * Set by `ArrayField` for owned-ref arrays (`FormCreateAnnotation`); equivalent to
74
+ * `FormInlineAnnotation` but driven by the parent array rather than the element's own AST.
85
75
  */
86
- createTypename?: string;
87
- } & Pick<FormFieldComponentProps, 'autoFocus' | 'readonly' | 'layout'> &
88
- Pick<
89
- RefFieldProps,
90
- | 'createOptionLabel'
91
- | 'createOptionIcon'
92
- | 'createInitialValuePath'
93
- | 'createFieldMap'
94
- | 'db'
95
- | 'useType'
96
- | 'getOptions'
97
- | 'onCreate'
98
- >;
76
+ refInline?: boolean;
77
+ } & FieldContext;
99
78
 
100
79
  export const FormField = (props: FormFieldProps) => {
101
80
  const {
102
81
  type,
103
82
  name,
83
+ label: labelProp,
104
84
  path,
85
+ required,
105
86
  projection,
106
87
  fieldMap,
107
88
  fieldProvider,
@@ -115,42 +96,49 @@ export const FormField = (props: FormFieldProps) => {
115
96
  createInitialValuePath,
116
97
  createFieldMap,
117
98
  db,
118
- useType: schemaHook,
99
+ useType,
119
100
  getOptions,
120
101
  onCreate,
102
+ resolveCreateEntry,
103
+ refInline,
121
104
  } = props;
122
105
  const { t } = useTranslation(translationKey);
123
106
  const title = SchemaEx.getAnnotation<string>(SchemaAST.TitleAnnotationId)(type);
124
107
  const description = SchemaEx.getAnnotation<string>(SchemaAST.DescriptionAnnotationId)(type);
125
108
  const examples = SchemaEx.getAnnotation<string[]>(SchemaAST.ExamplesAnnotationId)(type);
126
109
 
127
- // `name === null` means "no header" -- collapse to an empty string so
128
- // downstream consumers keep their `label: string` types, and the falsy
129
- // value lets `FormFieldSet`'s `label && <FormFieldLabel ...>` guard skip
130
- // the header.
131
- const label = useMemo(() => title ?? (name == null ? '' : String.capitalize(name)), [title, name]);
110
+ const label = useMemo(
111
+ () => labelProp ?? title ?? (name == null ? '' : String.capitalize(name)),
112
+ [labelProp, title, name],
113
+ );
132
114
  const placeholder = useMemo(
133
115
  () => (examples?.length ? `${t('example.placeholder')}: ${examples[0]}` : (description ?? label)),
134
- [examples, description, label],
116
+ [examples, description, label, t],
135
117
  );
136
118
 
119
+ // Build the schema for `fieldProvider` only when one is registered, memoized by `type` (the AST) so
120
+ // we don't reconstruct it on every render.
121
+ const providerSchema = useMemo(() => (fieldProvider ? Schema.make(type) : undefined), [fieldProvider, type]);
122
+
137
123
  const fieldState = useFormFieldState(FormField.displayName, path);
138
124
  const jsonPath = SchemaEx.createJsonPath(path ?? []);
139
- const fieldProps: FormFieldComponentProps = {
125
+ const fieldProps: FormFieldRendererProps = {
140
126
  type,
141
127
  format: Format.FormatAnnotation.getFromAst(type).pipe((annotation) => Option.getOrUndefined(annotation)),
142
128
  readonly,
143
129
  label,
130
+ description,
144
131
  jsonPath,
145
132
  placeholder,
146
- layout,
133
+ presentation: layout,
134
+ required,
147
135
  db,
148
136
  ...fieldState,
149
137
  };
150
138
 
151
139
  // Omit empty fields entirely in read-only mode -- an empty value has nothing
152
140
  // to display, so a labelled row with a blank input is just noise. This
153
- // mirrors what `FormFieldWrapper` already does for `layout === 'static'`, but
141
+ // mirrors what `FormRow` already does for `presentation === 'static'`, but
154
142
  // covers every field type (including those that bypass the wrapper:
155
143
  // RefField, SelectField, MarkdownField, ...). Container fields
156
144
  // (`ArrayField`, nested-struct -> `FormFieldSet`) keep their own
@@ -169,10 +157,29 @@ export const FormField = (props: FormFieldProps) => {
169
157
  return <CustomField {...fieldProps} />;
170
158
  }
171
159
 
172
- // TODO(burdon): Expensive to create schema each time; pass AST?
173
- const component = fieldProvider?.({ schema: Schema.make(type), prop: name ?? '', fieldProps });
174
- if (component) {
175
- return component;
160
+ if (fieldProvider && providerSchema) {
161
+ const component = fieldProvider({ schema: providerSchema, prop: name ?? '', fieldProps });
162
+ if (component) {
163
+ return component;
164
+ }
165
+ }
166
+
167
+ //
168
+ // Dynamic, value-driven fields (options/value/validation loaded via a self-contained Effect annotation).
169
+ //
170
+
171
+ const optionsLookup = Option.getOrUndefined(OptionsLookupAnnotation.getFromAst(type));
172
+ if (optionsLookup) {
173
+ return optionsLookup.combobox ? (
174
+ <ComboboxField {...fieldProps} lookup={optionsLookup} />
175
+ ) : (
176
+ <AsyncSelectField {...fieldProps} lookup={optionsLookup} />
177
+ );
178
+ }
179
+
180
+ const autofill = Option.getOrUndefined(AutofillAnnotation.getFromAst(type));
181
+ if (autofill) {
182
+ return <AutofillField {...fieldProps} autofill={autofill} />;
176
183
  }
177
184
 
178
185
  //
@@ -223,10 +230,21 @@ export const FormField = (props: FormFieldProps) => {
223
230
 
224
231
  const refProps = getRefProps(type);
225
232
  if (refProps) {
226
- // Inline a single referenced object's own fields (nested form) instead of a picker.
227
- const inline = Annotation.FormInlineAnnotation.getFromAst(refProps.ast).pipe(Option.getOrElse(() => false));
233
+ // Inline a single referenced object's own fields (nested form) instead of a picker. `refInline` lets a
234
+ // parent (e.g. an owned-ref `ArrayField`) force this for elements whose own AST carries no annotation.
235
+ const inline =
236
+ refInline || Annotation.FormInlineAnnotation.getFromAst(refProps.ast).pipe(Option.getOrElse(() => false));
228
237
  if (inline && !refProps.isArray) {
229
- return <InlineRefField {...fieldProps} {...refProps} db={db} useType={schemaHook} onCreate={onCreate} />;
238
+ return (
239
+ <InlineRefField
240
+ {...fieldProps}
241
+ {...refProps}
242
+ db={db}
243
+ useType={useType}
244
+ onCreate={onCreate}
245
+ resolveCreateEntry={resolveCreateEntry}
246
+ />
247
+ );
230
248
  }
231
249
 
232
250
  const isCreateTarget = !createTypename || refProps.typename === createTypename;
@@ -239,9 +257,10 @@ export const FormField = (props: FormFieldProps) => {
239
257
  createInitialValuePath={isCreateTarget ? createInitialValuePath : undefined}
240
258
  createFieldMap={isCreateTarget ? createFieldMap : undefined}
241
259
  db={db}
242
- useType={schemaHook}
260
+ useType={useType}
243
261
  getOptions={getOptions}
244
262
  onCreate={onCreate}
263
+ resolveCreateEntry={resolveCreateEntry}
245
264
  />
246
265
  );
247
266
  }
@@ -273,9 +292,10 @@ export const FormField = (props: FormFieldProps) => {
273
292
  createOptionIcon={createOptionIcon}
274
293
  createInitialValuePath={createInitialValuePath}
275
294
  db={db}
276
- useType={schemaHook}
295
+ useType={useType}
277
296
  getOptions={getOptions}
278
297
  onCreate={onCreate}
298
+ resolveCreateEntry={resolveCreateEntry}
279
299
  />
280
300
  );
281
301
  }
@@ -290,28 +310,33 @@ FormField.displayName = 'Form.FormField';
290
310
  // Layout components
291
311
  //
292
312
 
293
- export const IconBlock = ({ inline, ...props }: IconButtonProps & { inline?: boolean }) => {
294
- return (
295
- <div className={mx('h-full flex px-1', inline ? 'items-center' : 'flex-col pt-2.5')}>
296
- <IconButton variant='ghost' density='xs' square iconOnly {...props} />
297
- </div>
298
- );
313
+ export const CompactIconButton = (props: IconButtonProps) => {
314
+ return <IconButton variant='ghost' iconOnly {...props} />;
299
315
  };
300
316
 
301
317
  /**
302
318
  * Get property input component.
303
319
  */
304
- const getFormField = ({ type, format }: FormFieldComponentProps): FormFieldComponent | undefined => {
320
+ const getFormField = ({
321
+ type,
322
+ format,
323
+ }: Pick<FormFieldRendererProps, 'type' | 'format'>): FormFieldRenderer | undefined => {
324
+ // Unwrap refinements (e.g. Schema.Number.pipe(Schema.between(...))) to their base type.
325
+ if (SchemaAST.isRefinement(type)) {
326
+ return getFormField({ type: type.from, format });
327
+ }
328
+
305
329
  //
306
330
  // Standard formats.
307
331
  //
308
332
 
309
333
  const formatField = Match.value(format).pipe(
310
- Match.withReturnType<FormFieldComponent | undefined>(),
334
+ Match.withReturnType<FormFieldRenderer | undefined>(),
311
335
  Match.when(Format.TypeFormat.Date, () => DateField),
312
336
  Match.when(Format.TypeFormat.DateTime, () => DateField),
313
337
  Match.when(Format.TypeFormat.GeoPoint, () => GeoPointField),
314
338
  Match.when(Format.TypeFormat.Markdown, () => MarkdownField),
339
+ Match.when(Format.TypeFormat.Password, () => PasswordField),
315
340
  Match.when(Format.TypeFormat.Text, () => TextAreaField),
316
341
  Match.when(Format.TypeFormat.Time, () => DateField),
317
342
  Match.orElse(() => undefined),
@@ -334,6 +359,8 @@ const getFormField = ({ type, format }: FormFieldComponentProps): FormFieldCompo
334
359
  case 'BooleanKeyword':
335
360
  return BooleanField;
336
361
  }
362
+
363
+ return undefined;
337
364
  };
338
365
 
339
366
  const getSelectOptions = (ast: SchemaAST.AST): Format.Options[] | undefined => {