@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,81 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Cause from 'effect/Cause';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Exit from 'effect/Exit';
8
+ import { useEffect, useRef, useState } from 'react';
9
+
10
+ import { type AnyProperties } from '@dxos/echo/internal';
11
+
12
+ /** Picks the declared dependency fields from the form values — the subset a dynamic field subscribes to. */
13
+ export const pickValues = (values: AnyProperties | undefined, deps: readonly string[]): Record<string, unknown> => {
14
+ const subset: Record<string, unknown> = {};
15
+ for (const dep of deps) {
16
+ subset[dep] = values?.[dep];
17
+ }
18
+ return subset;
19
+ };
20
+
21
+ export type AsyncFieldEffectState<A> = {
22
+ loading: boolean;
23
+ error: unknown;
24
+ data: A | undefined;
25
+ };
26
+
27
+ export type UseAsyncFieldEffectOptions = {
28
+ /** Debounce applied before running, so per-keystroke value changes coalesce into one run. */
29
+ debounceMs?: number;
30
+ /** When false, the effect is not run and prior data/error is cleared. */
31
+ enabled?: boolean;
32
+ };
33
+
34
+ /**
35
+ * Runs a self-contained Effect (`R = never`) whenever `key` changes, debounced and abortable. Used by
36
+ * dynamic form fields to load select options, derive values, or validate against an external source from
37
+ * the current form values. The latest `factory` is always used, but a run is only (re)triggered when
38
+ * `key` changes — derive `key` from the inputs the factory actually reads. Re-running interrupts the
39
+ * prior run (cancelling any in-flight request).
40
+ */
41
+ export const useAsyncFieldEffect = <A>(
42
+ factory: () => Effect.Effect<A, unknown>,
43
+ key: string,
44
+ { debounceMs = 400, enabled = true }: UseAsyncFieldEffectOptions = {},
45
+ ): AsyncFieldEffectState<A> => {
46
+ const factoryRef = useRef(factory);
47
+ factoryRef.current = factory;
48
+ const [state, setState] = useState<AsyncFieldEffectState<A>>({ loading: false, error: undefined, data: undefined });
49
+
50
+ useEffect(() => {
51
+ if (!enabled) {
52
+ setState({ loading: false, error: undefined, data: undefined });
53
+ return;
54
+ }
55
+
56
+ let cancel: (() => void) | undefined;
57
+ const timer = setTimeout(() => {
58
+ setState((prev) => ({ ...prev, loading: true, error: undefined }));
59
+ cancel = Effect.runCallback(factoryRef.current(), {
60
+ onExit: (exit: Exit.Exit<A, unknown>) => {
61
+ Exit.match(exit, {
62
+ onSuccess: (data) => setState({ loading: false, error: undefined, data }),
63
+ onFailure: (cause) => {
64
+ // A superseding run interrupts this one; ignore the resulting interruption.
65
+ if (!Cause.isInterruptedOnly(cause)) {
66
+ setState((prev) => ({ loading: false, error: Cause.squash(cause), data: prev.data }));
67
+ }
68
+ },
69
+ });
70
+ },
71
+ });
72
+ }, debounceMs);
73
+
74
+ return () => {
75
+ clearTimeout(timer);
76
+ cancel?.();
77
+ };
78
+ }, [key, enabled, debounceMs]);
79
+
80
+ return state;
81
+ };
@@ -0,0 +1,89 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { createContext } from '@radix-ui/react-context';
6
+ import * as SchemaAST from 'effect/SchemaAST';
7
+ import { useEffect, useMemo, useRef } from 'react';
8
+
9
+ import { type AnyProperties } from '@dxos/echo/internal';
10
+ import { SchemaEx } from '@dxos/effect';
11
+
12
+ import { type FieldContext, type FormFieldStateProps } from '#types';
13
+
14
+ import { type FormVariant } from '../components/Form/Form.theme';
15
+ import { type FormHandler } from './useFormHandler';
16
+
17
+ //
18
+ // Context
19
+ //
20
+
21
+ export type FormContextValue<T extends AnyProperties = any> = {
22
+ /**
23
+ * Form handler.
24
+ */
25
+ form: FormHandler<T>;
26
+
27
+ /**
28
+ * Testing.
29
+ */
30
+ testId?: string;
31
+
32
+ /** Visual variant applied across the form's parts (see Form.theme). */
33
+ variant?: FormVariant;
34
+ } & FieldContext;
35
+
36
+ export const [FormContextProvider, useFormContext] = createContext<FormContextValue>('Form');
37
+
38
+ /**
39
+ * Get the current form values.
40
+ */
41
+ export const useFormValues: {
42
+ <T extends AnyProperties>(componentName: string, path?: (string | number)[]): T;
43
+ <T extends AnyProperties>(
44
+ componentName: string,
45
+ path: (string | number)[] | undefined,
46
+ defaultValue: () => T,
47
+ ): T | undefined;
48
+ } = (componentName: string, path?: (string | number)[], defaultValue?: () => any) => {
49
+ const stablePath = useMemo(() => path ?? [], [path ? path.join('.') : undefined]);
50
+ const jsonPath = SchemaEx.createJsonPath(stablePath);
51
+ const {
52
+ form: { values, onValueChange },
53
+ } = useFormContext(componentName);
54
+
55
+ const value = SchemaEx.getValue(values, jsonPath);
56
+
57
+ // Apply default value once when the field has no value. lastAppliedPathRef prevents
58
+ // re-applying on every render (e.g. when defaultValue() returns null) and ensures
59
+ // we apply per path when the hook is used for different fields.
60
+ const lastAppliedPathRef = useRef<string | null>(null);
61
+ useEffect(() => {
62
+ if (value == null && defaultValue && lastAppliedPathRef.current !== jsonPath) {
63
+ lastAppliedPathRef.current = jsonPath;
64
+ onValueChange(stablePath, SchemaAST.stringKeyword, defaultValue());
65
+ }
66
+ }, [value, defaultValue, onValueChange, stablePath, jsonPath]);
67
+
68
+ return value;
69
+ };
70
+
71
+ /**
72
+ * Get the state props for the given field.
73
+ */
74
+ export const useFormFieldState = (componentName: string, path: (string | number)[] = []): FormFieldStateProps => {
75
+ const stablePath = useMemo(() => path, [Array.isArray(path) ? path.join('.') : path]);
76
+ const {
77
+ form: { getStatus, getValue, onBlur, onValueChange },
78
+ } = useFormContext(componentName);
79
+
80
+ return useMemo(
81
+ () => ({
82
+ getStatus: () => getStatus(stablePath),
83
+ getValue: () => getValue(stablePath),
84
+ onBlur: () => onBlur(stablePath),
85
+ onValueChange: (ast: SchemaAST.AST, value: any) => onValueChange(stablePath, ast, value),
86
+ }),
87
+ [getStatus, getValue, onBlur, onValueChange, stablePath],
88
+ );
89
+ };
@@ -14,10 +14,7 @@ import { useDefaultValue } from '@dxos/react-ui';
14
14
  import { type ValidationError, validateSchema } from '@dxos/schema';
15
15
  import { type MaybePromise } from '@dxos/util';
16
16
 
17
- export type FormFieldStatus = {
18
- status?: 'error';
19
- error?: string;
20
- };
17
+ import { type FormFieldStatus } from '#types';
21
18
 
22
19
  /**
23
20
  * Form properties.
@@ -80,17 +77,21 @@ export type FormHandler<T extends AnyProperties> = Pick<FormHandlerProps<T>, 'sc
80
77
  /** Initial values (which may not pass validation). */
81
78
  values: Partial<T>;
82
79
 
83
- /** Map of changed fields. */
80
+ /** Fields whose value has been edited (set by `onValueChange`). Gates autosave-on-blur. */
84
81
  changed: Record<SchemaEx.JsonPath, boolean>;
85
- // TODO(burdon): How is this different from above?
82
+ /** Fields the user has interacted with (set by `onBlur`). Gates error *visibility* — errors only surface once touched. */
86
83
  touched: Record<SchemaEx.JsonPath, boolean>;
87
84
 
88
85
  /** Map of error strings. */
89
86
  errors: Record<SchemaEx.JsonPath, string>;
90
87
 
91
- /** Whether the form can be saved (i.e., data is valid). */
88
+ /** Whether the values pass validation (no errors at all). */
92
89
  isValid: boolean;
93
- // TODO(burdon): Why is this needed separately from isValid?
90
+ /**
91
+ * Whether the submit affordance should be enabled. Stricter than `isValid`: it also requires no save
92
+ * in flight (`saving`) and only blocks on errors for *touched* fields, so a pristine form with
93
+ * not-yet-touched required fields can still show an enabled control.
94
+ */
94
95
  canSave: boolean;
95
96
 
96
97
  onSave: () => void;
@@ -328,6 +329,7 @@ export const useFormHandler = <T extends AnyProperties>({
328
329
  }),
329
330
  [
330
331
  schema,
332
+ autoSave,
331
333
  values,
332
334
  errors,
333
335
  touched,
@@ -338,8 +340,8 @@ export const useFormHandler = <T extends AnyProperties>({
338
340
  getValue,
339
341
  onBlur,
340
342
  onValueChange,
341
- onSave,
342
- onCancel,
343
+ handleSave,
344
+ handleCancel,
343
345
  ],
344
346
  );
345
347
  };
package/src/index.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ export * from './annotations';
5
6
  export * from './components';
6
7
  export * from './hooks';
8
+ export * from './types';
7
9
  export * from './util';
@@ -7,23 +7,26 @@ import React, { type PropsWithChildren } from 'react';
7
7
  import { type ThemedClassName } from '@dxos/react-ui';
8
8
  import { composableProps, slottable } from '@dxos/react-ui';
9
9
  import { Syntax } from '@dxos/react-ui-syntax-highlighter';
10
+ import { mx } from '@dxos/ui-theme';
10
11
 
11
- type TestLayoutProps = PropsWithChildren<{ json?: any }>;
12
+ type TestLayoutProps = PropsWithChildren<{ json?: unknown }>;
12
13
 
13
14
  export const TestLayout = ({ children, json }: TestLayoutProps) => {
14
15
  return (
15
- <div className='dx-container grid grid-cols-[1fr_1fr] p-4 gap-4'>
16
+ <div className={mx('dx-container grid grid-cols-1 p-4 gap-4', !!json && 'grid-cols-[1fr_1fr]')}>
16
17
  <TestPanel>{children}</TestPanel>
17
- <TestPanel>
18
- <Syntax.Root data={json}>
19
- <Syntax.Content>
20
- <Syntax.Filter />
21
- <Syntax.Viewport>
22
- <Syntax.Code testId='debug' classNames='text-sm' />
23
- </Syntax.Viewport>
24
- </Syntax.Content>
25
- </Syntax.Root>
26
- </TestPanel>
18
+ {!!json && (
19
+ <TestPanel>
20
+ <Syntax.Root data={json}>
21
+ <Syntax.Content>
22
+ <Syntax.Filter />
23
+ <Syntax.Viewport>
24
+ <Syntax.Code testId='debug' classNames='text-sm' />
25
+ </Syntax.Viewport>
26
+ </Syntax.Content>
27
+ </Syntax.Root>
28
+ </TestPanel>
29
+ )}
27
30
  </div>
28
31
  );
29
32
  };
@@ -32,7 +35,7 @@ type TestPanelProps = ThemedClassName<PropsWithChildren>;
32
35
 
33
36
  export const TestPanel = slottable<HTMLDivElement, TestPanelProps>(({ children }, forwardedRef) => {
34
37
  return (
35
- <div {...composableProps({ classNames: 'dx-container bg-card-surface rounded-md' })} ref={forwardedRef}>
38
+ <div {...composableProps({ classNames: 'dx-container dx-card-surface rounded-sm' })} ref={forwardedRef}>
36
39
  {children}
37
40
  </div>
38
41
  );
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './schema';
6
+
7
+ export * from './TestLayout';
@@ -0,0 +1,59 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as Schema from 'effect/Schema';
6
+
7
+ import { Annotation, DXN, Format, Ref, Tag, Type } from '@dxos/echo';
8
+
9
+ /**
10
+ * Shared test schemas for form stories. Intentionally small, hand-written types
11
+ * (not `@dxos/types`) exercising the range of field renderers: scalars, nested
12
+ * structs, refs, ref arrays, enums, and the various formats.
13
+ */
14
+
15
+ export class Organization extends Type.makeObject<Organization>(DXN.make('com.example.type.organization', '0.1.0'))(
16
+ Schema.Struct({
17
+ name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
18
+ }),
19
+ ) {}
20
+
21
+ export class Person extends Type.makeObject<Person>(DXN.make('org.dxos.type.person', '0.1.0'))(
22
+ Schema.Struct({
23
+ name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
24
+ ignore: Schema.String.pipe(Annotation.FormInputAnnotation.set(false), Schema.optional),
25
+ active: Schema.optional(Schema.Boolean.annotations({ title: 'Active' })),
26
+ address: Schema.optional(
27
+ Schema.Struct({
28
+ street: Schema.String,
29
+ city: Schema.String,
30
+ // TODO(burdon): Constrain input control.
31
+ state: Schema.String.pipe(Schema.minLength(2), Schema.maxLength(2)).annotations({
32
+ title: 'State',
33
+ description: 'State code',
34
+ }),
35
+ zip: Schema.Number.annotations({ title: 'ZIP Code' }),
36
+ }).annotations({ title: 'Address' }),
37
+ ),
38
+ employer: Schema.optional(Ref.Ref(Organization).annotations({ title: 'Employer' })),
39
+ tags: Schema.optional(Schema.Array(Ref.Ref(Tag.Tag)).annotations({ title: 'Tags' })),
40
+ status: Schema.optional(Schema.Literal('active', 'inactive').annotations({ title: 'Status' })),
41
+ notes: Schema.optional(Format.Text.annotations({ title: 'Notes' })),
42
+ location: Schema.optional(Format.GeoPoint.annotations({ title: 'Location' })),
43
+ birthday: Schema.optional(Format.DateOnly.annotations({ title: 'Birthday' })),
44
+ meetingAt: Schema.optional(Format.DateTime.annotations({ title: 'Next meeting' })),
45
+ reminderAt: Schema.optional(Format.TimeOnly.annotations({ title: 'Reminder time' })),
46
+ tasks: Schema.optional(Schema.Array(Schema.String).annotations({ title: 'Tasks' })),
47
+ locations: Schema.optional(Schema.Array(Format.GeoPoint).annotations({ title: 'Locations' })),
48
+ identities: Schema.optional(
49
+ Schema.Array(
50
+ Schema.Struct({
51
+ type: Schema.String.annotations({ title: 'Type' }),
52
+ value: Schema.String.annotations({ title: 'Value' }),
53
+ }).annotations({ title: 'Identities' }),
54
+ ).annotations({
55
+ title: 'Identities',
56
+ }),
57
+ ),
58
+ }),
59
+ ) {}
package/src/types.ts ADDED
@@ -0,0 +1,186 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import type * as Schema from 'effect/Schema';
6
+ import type * as SchemaAST from 'effect/SchemaAST';
7
+ import { type FC, type FocusEvent, type ReactElement } from 'react';
8
+
9
+ import { type Database, type Entity, type Format, type Obj, type Type } from '@dxos/echo';
10
+ import { type URI } from '@dxos/keys';
11
+ import { type Palette } from '@dxos/react-ui';
12
+ import { type ProjectionModel } from '@dxos/schema';
13
+
14
+ //
15
+ // Field component contracts.
16
+ //
17
+
18
+ /**
19
+ * Validation status of a field.
20
+ */
21
+ export type FormFieldStatus = {
22
+ status?: 'error';
23
+ error?: string;
24
+ };
25
+
26
+ /**
27
+ * FormPresentation mode.
28
+ * - full: Show label, control, and status.
29
+ * - compact: Show label and control.
30
+ * - inline: Control only.
31
+ * - static: Plain DOM; omit all undefined values.
32
+ */
33
+ export type FormPresentation = 'full' | 'compact' | 'inline' | 'static';
34
+
35
+ /**
36
+ * Dynamic props passed to input components.
37
+ */
38
+ export type FormFieldStateProps<T = any> = {
39
+ getStatus: () => FormFieldStatus;
40
+ getValue: () => T | undefined;
41
+ onBlur: (event: FocusEvent<HTMLElement>) => void;
42
+ onValueChange: (type: SchemaAST.AST, value: T) => void;
43
+ };
44
+
45
+ /**
46
+ * Props passed to input components.
47
+ */
48
+ export type FormFieldRendererProps<T = any> = {
49
+ /** Database the form is editing against; populated for fields whose value is an ECHO object/Ref. */
50
+ db?: Database.Database;
51
+ type: SchemaAST.AST;
52
+ format?: Format.TypeFormat;
53
+ readonly?: boolean;
54
+ label: string;
55
+ /**
56
+ * Human-readable description (from the schema's description annotation). Rendered as visible text
57
+ * only when the resolved presentation opts in; otherwise it falls back to the input placeholder.
58
+ */
59
+ description?: string;
60
+ /**
61
+ * Dotted JSON path of this field within the form values (e.g. `runtime.client.storage.persistent`).
62
+ */
63
+ jsonPath?: string;
64
+ placeholder?: string;
65
+ autoFocus?: boolean;
66
+ /** Presentation mode for the field (full/compact/inline/static); see {@link FormPresentation}. */
67
+ presentation?: FormPresentation;
68
+ /** Whether the field is required (non-optional in the schema); surfaces a trailing asterisk on the label. */
69
+ required?: boolean;
70
+ } & FormFieldStateProps<T>;
71
+
72
+ export type FormFieldRenderer = FC<FormFieldRendererProps>;
73
+
74
+ export type FormFieldMap = Record<string, FormFieldRenderer>;
75
+
76
+ export type FormFieldProvider = (props: {
77
+ prop: string;
78
+ schema: Schema.Schema<any>;
79
+ fieldProps: FormFieldRendererProps;
80
+ }) => ReactElement | null | undefined;
81
+
82
+ //
83
+ // Ref / create contracts.
84
+ //
85
+
86
+ /**
87
+ * Per-typename override for the ref-field inline create form and its submission handler.
88
+ * The type parameter S links the form schema's decoded type to the createObject values argument,
89
+ * ensuring the schema and the handler agree on the shape of the form data.
90
+ */
91
+ export type CreateEntryOverride<S extends Schema.Schema.AnyNoContext = Schema.Schema.AnyNoContext> = {
92
+ /** Replaces the raw ECHO type schema for the inline create form. */
93
+ inputSchema?: S;
94
+ /** Runs instead of the default onCreate(schema, values) path; values are typed from inputSchema. */
95
+ createObject?: (values: Schema.Schema.Type<S>, db: Database.Database) => Promise<Obj.Unknown>;
96
+ };
97
+
98
+ /**
99
+ * Configuration for the inline "create a new referenced object" affordance, shared by every
100
+ * surface that can spawn a ref target (the picker, `RefField`, and the `Form*` props that forward
101
+ * to them). `onCreate` is intentionally NOT part of this group: its signature differs per consumer
102
+ * (the picker takes `(values)`, `RefField` takes `(schema, values)`).
103
+ */
104
+ export type CreateOptions = {
105
+ createOptionLabel?: [string, { ns: string | readonly string[] }];
106
+ createOptionIcon?: string;
107
+ createInitialValuePath?: string;
108
+ createFieldMap?: FormFieldMap;
109
+ };
110
+
111
+ export type RefOption = {
112
+ id: string;
113
+ label: string;
114
+ /** Optional secondary line shown beneath the label in the picker list. */
115
+ description?: string;
116
+ hue?: Palette;
117
+ };
118
+
119
+ /**
120
+ * Data plumbing for resolving and creating ref targets: how to look up the ref type, query
121
+ * candidate objects, map them to options, and persist a newly-created one.
122
+ */
123
+ export type RefFieldDataProps = {
124
+ // TODO(burdon): Replace hooks with callbacks?
125
+ useType?: (db?: Database.Database, typeUri?: URI.URI) => Type.AnyEntity;
126
+ useResults?: (db?: Database.Database, typename?: string) => Entity.Any[];
127
+ getOptions?: (
128
+ objects: Entity.Any[],
129
+ options?: { parentLabel?: boolean; getTypePlaceholder?: (typename: string) => string },
130
+ ) => RefOption[];
131
+ /**
132
+ * Persist a newly-created object. Called after the user fills out the inline create form and
133
+ * clicks Save. Should add the object to the database and return it (sync or async). The returned
134
+ * object is then wired into this slot's form value as a Ref.
135
+ */
136
+ onCreate?: (schema: Type.AnyEntity, values: any) => Obj.Unknown | Promise<Obj.Unknown> | undefined | void;
137
+ /**
138
+ * Supply default values for a newly-created owned object when the user clicks "add" on an owned-ref
139
+ * array field (see `FormCreateAnnotation`). Keyed by the field's json path so a container can pre-populate
140
+ * the new object (e.g. a back-reference to the parent). The returned values are passed to
141
+ * `onCreate(schema, values)` before the object is persisted.
142
+ */
143
+ getCreateDefaults?: (props: { jsonPath: string; schema: Type.AnyEntity }) => Record<string, unknown> | undefined;
144
+ /**
145
+ * Optional resolver that maps a ref's typename to a {@link CreateEntryOverride}, replacing the
146
+ * raw ECHO type schema and/or the default `onCreate` path with plugin-registered create logic
147
+ * (e.g. `SpaceCapabilities.CreateObjectEntry`). When absent or returning `undefined` for a given
148
+ * typename the existing fallback behaviour is preserved.
149
+ */
150
+ resolveCreateEntry?: (typename: string) => CreateEntryOverride | undefined;
151
+ };
152
+
153
+ /**
154
+ * Form-wide field options carried by the form context and threaded down to every field. The
155
+ * canonical source for the config that `FormContextValue`, `FormFieldProps`, and `FormFieldSetProps`
156
+ * all share — so none of them has to reach "up" into another's props to express it.
157
+ */
158
+ export type FormFieldOptions = {
159
+ db?: Database.Database;
160
+ readonly?: boolean;
161
+ layout?: FormPresentation;
162
+ projection?: ProjectionModel;
163
+ fieldMap?: FormFieldMap;
164
+ fieldProvider?: FormFieldProvider;
165
+ /**
166
+ * Typename of the ref type that the create props apply to.
167
+ * When set, the create affordance is only offered to ref fields whose typename matches.
168
+ */
169
+ createTypename?: string;
170
+ };
171
+
172
+ /**
173
+ * The subset of {@link RefFieldDataProps} that is forwarded down the field recursion. `useResults`
174
+ * is consumed only by `RefField` itself (it defaults internally), so it is not threaded.
175
+ */
176
+ export type RefThreadedProps = Pick<
177
+ RefFieldDataProps,
178
+ 'useType' | 'getOptions' | 'onCreate' | 'getCreateDefaults' | 'resolveCreateEntry'
179
+ >;
180
+
181
+ /**
182
+ * Form-wide configuration threaded unchanged down the entire field recursion. This is the single
183
+ * canonical source for the bag that `FormContextValue`, `FormFieldProps`, and `FormFieldSetProps`
184
+ * all carry — so none of them re-Picks fragments of the others.
185
+ */
186
+ export type FieldContext = FormFieldOptions & CreateOptions & RefThreadedProps;
package/src/util/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './field';
6
- export * from './field-type';
7
6
  export * from './formatting';
7
+ export * from './omit';
8
8
  export * from './properties';
9
9
  export * from './refs';
@@ -0,0 +1,61 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as Option from 'effect/Option';
6
+ import * as Schema from 'effect/Schema';
7
+ import * as SchemaAST from 'effect/SchemaAST';
8
+
9
+ import { Annotation, Type } from '@dxos/echo';
10
+
11
+ // TODO(burdon): Reconcile with @dxos/echo.
12
+ // Distributive: applied per union member so `ExcludeId<A | B>` → `ExcludeId<A> | ExcludeId<B>` rather
13
+ // than `Omit<InstanceType<A> | InstanceType<B>, 'id'>` (which would intersect away per-member narrowing).
14
+ export type ExcludeId<S extends Schema.Schema.AnyNoContext | Type.AnyEntity> = S extends Type.AnyEntity
15
+ ? Omit<Type.InstanceType<S>, 'id'>
16
+ : S extends Schema.Schema.AnyNoContext
17
+ ? Omit<Schema.Schema.Type<S>, 'id'>
18
+ : never;
19
+
20
+ // TODO(burdon): Move to @dxos/schema (re-export here).
21
+ export const omitId = <S extends Schema.Schema.AnyNoContext | Type.AnyEntity>(
22
+ schemaOrType: S,
23
+ ): Schema.Schema<ExcludeId<S>, ExcludeId<S>> => {
24
+ const schema = Type.isType(schemaOrType)
25
+ ? Type.getSchema(schemaOrType)
26
+ : (schemaOrType as Schema.Schema.AnyNoContext);
27
+ // Cast: `Schema.omit` cannot statically express the `ExcludeId<S>` result type, so the result is a
28
+ // widened schema; the runtime shape matches the declared return type.
29
+ return omitIdFromSchema(schema) as any;
30
+ };
31
+
32
+ /**
33
+ * Removes the `id` field where present, preserving the schema's shape. Recurses into unions so a
34
+ * discriminated (create) schema keeps its discriminant — `Schema.omit('id')` is a struct operation and
35
+ * would otherwise flatten the union, breaking variant rendering. A no-op when there is no `id` (plain
36
+ * create structs that never carried one).
37
+ */
38
+ const omitIdFromSchema = (schema: Schema.Schema.AnyNoContext): Schema.Schema.AnyNoContext => {
39
+ const ast = schema.ast;
40
+ if (SchemaAST.isUnion(ast)) {
41
+ return Schema.Union(...ast.types.map((type) => omitIdFromSchema(Schema.make(type))));
42
+ }
43
+ const hasId = SchemaAST.getPropertySignatures(ast).some((prop) => prop.name === 'id');
44
+ return hasId ? schema.pipe(Schema.omit('id')) : schema;
45
+ };
46
+
47
+ /**
48
+ * Drop fields annotated with `FormInputAnnotation.set(false)` from a schema so
49
+ * the form's validator doesn't trip on required-but-hidden fields. Used by
50
+ * the picker's inline create form, where a `FactoryAnnotation` typically
51
+ * supplies the hidden values (e.g. a backing-object Ref) outside the form.
52
+ */
53
+ export const omitHiddenFormFields = <S extends Schema.Schema.AnyNoContext>(schema: S): S => {
54
+ const properties = SchemaAST.getPropertySignatures(schema.ast);
55
+ const hidden = properties
56
+ .filter((prop) => Option.getOrElse(Annotation.FormInputAnnotation.getFromAst(prop.type), () => true) === false)
57
+ .map((prop) => prop.name as string);
58
+ // Cast: omitting a dynamically-computed set of keys can't be expressed as the original `S`, but
59
+ // the result is structurally a subset of `S` and callers treat it as `S`.
60
+ return hidden.length === 0 ? schema : (schema.pipe(Schema.omit(...(hidden as [string, ...string[]]))) as any);
61
+ };