@dxos/react-ui-form 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8

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 (318) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +0 -1
  3. package/dist/lib/browser/index.mjs +4054 -2217
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/translations.mjs +87 -0
  7. package/dist/lib/browser/translations.mjs.map +7 -0
  8. package/dist/lib/node-esm/index.mjs +4054 -2217
  9. package/dist/lib/node-esm/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/meta.json +1 -1
  11. package/dist/lib/node-esm/translations.mjs +89 -0
  12. package/dist/lib/node-esm/translations.mjs.map +7 -0
  13. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -8
  14. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  15. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +89 -5
  16. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/Form.d.ts +135 -25
  18. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts +207 -56
  20. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/FormField.d.ts +52 -0
  22. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  23. package/dist/types/src/components/Form/FormFieldComponent.d.ts +98 -0
  24. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
  25. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
  26. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
  27. package/dist/types/src/components/Form/FormFieldSet.d.ts +26 -0
  28. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
  29. package/dist/types/src/components/Form/FormTooltipsContext.d.ts +19 -0
  30. package/dist/types/src/components/Form/FormTooltipsContext.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/Layout/FormLayout.d.ts +50 -0
  32. package/dist/types/src/components/Form/Layout/FormLayout.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts +123 -0
  34. package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts +2 -0
  36. package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts +2 -0
  38. package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/Layout/annotation.d.ts +30 -0
  40. package/dist/types/src/components/Form/Layout/annotation.d.ts.map +1 -0
  41. package/dist/types/src/components/Form/Layout/index.d.ts +4 -0
  42. package/dist/types/src/components/Form/Layout/index.d.ts.map +1 -0
  43. package/dist/types/src/components/Form/Layout/parser.d.ts +31 -0
  44. package/dist/types/src/components/Form/Layout/parser.d.ts.map +1 -0
  45. package/dist/types/src/components/Form/Layout/parser.test.d.ts +2 -0
  46. package/dist/types/src/components/Form/Layout/parser.test.d.ts.map +1 -0
  47. package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
  48. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
  49. package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
  50. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
  51. package/dist/types/src/components/Form/fields/DateField.d.ts +4 -0
  52. package/dist/types/src/components/Form/fields/DateField.d.ts.map +1 -0
  53. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
  54. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
  55. package/dist/types/src/components/Form/fields/InlineRefField.d.ts +13 -0
  56. package/dist/types/src/components/Form/fields/InlineRefField.d.ts.map +1 -0
  57. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +11 -0
  58. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
  59. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +89 -0
  60. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
  62. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
  63. package/dist/types/src/components/Form/fields/RefField.d.ts +34 -0
  64. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
  65. package/dist/types/src/components/Form/fields/RefField.test.d.ts +2 -0
  66. package/dist/types/src/components/Form/fields/RefField.test.d.ts.map +1 -0
  67. package/dist/types/src/components/Form/fields/SelectField.d.ts +13 -0
  68. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
  69. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
  70. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
  71. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +89 -0
  72. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
  73. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
  74. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
  75. package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
  76. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
  77. package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
  78. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
  79. package/dist/types/src/components/Form/fields/index.d.ts +14 -0
  80. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
  81. package/dist/types/src/components/Form/index.d.ts +5 -4
  82. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  83. package/dist/types/src/components/Form/meta-tags.d.ts +30 -0
  84. package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -0
  85. package/dist/types/src/components/Form/meta-tags.test.d.ts +2 -0
  86. package/dist/types/src/components/Form/meta-tags.test.d.ts.map +1 -0
  87. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +8 -0
  88. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
  89. package/dist/types/src/components/ObjectForm/ObjectForm.stories.d.ts +87 -0
  90. package/dist/types/src/components/ObjectForm/ObjectForm.stories.d.ts.map +1 -0
  91. package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
  92. package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
  93. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +68 -0
  94. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  95. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +92 -0
  96. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  97. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  98. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  99. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
  100. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
  101. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +132 -0
  102. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
  103. package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
  104. package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
  105. package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
  106. package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
  107. package/dist/types/src/components/ObjectTree/ObjectTree.d.ts +9 -0
  108. package/dist/types/src/components/ObjectTree/ObjectTree.d.ts.map +1 -0
  109. package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts +91 -0
  110. package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts.map +1 -0
  111. package/dist/types/src/components/ObjectTree/index.d.ts +2 -0
  112. package/dist/types/src/components/ObjectTree/index.d.ts.map +1 -0
  113. package/dist/types/src/components/RefEditor/RefEditor.d.ts +73 -0
  114. package/dist/types/src/components/RefEditor/RefEditor.d.ts.map +1 -0
  115. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts +92 -0
  116. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts.map +1 -0
  117. package/dist/types/src/components/RefEditor/index.d.ts +3 -0
  118. package/dist/types/src/components/RefEditor/index.d.ts.map +1 -0
  119. package/dist/types/src/components/RefEditor/ref-editor-extension.d.ts +47 -0
  120. package/dist/types/src/components/RefEditor/ref-editor-extension.d.ts.map +1 -0
  121. package/dist/types/src/components/Settings/Settings.d.ts +51 -0
  122. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  123. package/dist/types/src/components/Settings/Settings.stories.d.ts +89 -0
  124. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  125. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
  126. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
  127. package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
  128. package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
  129. package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
  130. package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +1 -0
  131. package/dist/types/src/components/Settings/index.d.ts +4 -0
  132. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  133. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +21 -11
  134. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  135. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +91 -9
  136. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  137. package/dist/types/src/components/index.d.ts +6 -2
  138. package/dist/types/src/components/index.d.ts.map +1 -1
  139. package/dist/types/src/components/testing.d.ts +14 -4
  140. package/dist/types/src/components/testing.d.ts.map +1 -1
  141. package/dist/types/src/hooks/index.d.ts +2 -3
  142. package/dist/types/src/hooks/index.d.ts.map +1 -1
  143. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  144. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  145. package/dist/types/src/hooks/useKeyHandler.d.ts +7 -0
  146. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  147. package/dist/types/src/index.d.ts +0 -2
  148. package/dist/types/src/index.d.ts.map +1 -1
  149. package/dist/types/src/translations.d.ts +70 -55
  150. package/dist/types/src/translations.d.ts.map +1 -1
  151. package/dist/types/src/util/field-type.d.ts +19 -0
  152. package/dist/types/src/util/field-type.d.ts.map +1 -0
  153. package/dist/types/src/util/field.d.ts +3 -3
  154. package/dist/types/src/util/field.d.ts.map +1 -1
  155. package/dist/types/src/util/formatting.d.ts +2 -2
  156. package/dist/types/src/util/formatting.d.ts.map +1 -1
  157. package/dist/types/src/util/index.d.ts +2 -1
  158. package/dist/types/src/util/index.d.ts.map +1 -1
  159. package/dist/types/src/util/properties.d.ts +35 -0
  160. package/dist/types/src/util/properties.d.ts.map +1 -0
  161. package/dist/types/src/util/properties.test.d.ts +2 -0
  162. package/dist/types/src/util/properties.test.d.ts.map +1 -0
  163. package/dist/types/src/util/refs.d.ts +3 -3
  164. package/dist/types/src/util/refs.d.ts.map +1 -1
  165. package/dist/types/tsconfig.tsbuildinfo +1 -1
  166. package/package.json +64 -47
  167. package/src/components/FieldEditor/FieldEditor.stories.tsx +49 -31
  168. package/src/components/FieldEditor/FieldEditor.test.tsx +4 -2
  169. package/src/components/FieldEditor/FieldEditor.tsx +100 -97
  170. package/src/components/Form/AUDIT.md +17 -0
  171. package/src/components/Form/Form.stories.tsx +151 -221
  172. package/src/components/Form/Form.tsx +461 -95
  173. package/src/components/Form/FormField.tsx +345 -0
  174. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  175. package/src/components/Form/FormFieldComponent.tsx +253 -0
  176. package/src/components/Form/FormFieldSet.tsx +213 -0
  177. package/src/components/Form/FormTooltipsContext.ts +25 -0
  178. package/src/components/Form/Layout/FormLayout.stories.test.tsx +51 -0
  179. package/src/components/Form/Layout/FormLayout.stories.tsx +422 -0
  180. package/src/components/Form/Layout/FormLayout.test.ts +59 -0
  181. package/src/components/Form/Layout/FormLayout.tsx +251 -0
  182. package/src/components/Form/Layout/annotation.ts +38 -0
  183. package/src/components/Form/Layout/index.ts +7 -0
  184. package/src/components/Form/Layout/parser.test.ts +120 -0
  185. package/src/components/Form/Layout/parser.ts +167 -0
  186. package/src/components/Form/README.md +3 -0
  187. package/src/components/Form/fields/ArrayField.tsx +173 -0
  188. package/src/components/Form/fields/BooleanField.tsx +22 -0
  189. package/src/components/Form/fields/DateField.tsx +101 -0
  190. package/src/components/Form/fields/GeoPointField.tsx +124 -0
  191. package/src/components/Form/fields/InlineRefField.tsx +138 -0
  192. package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
  193. package/src/components/Form/fields/MarkdownField.tsx +167 -0
  194. package/src/components/Form/fields/NumberField.tsx +76 -0
  195. package/src/components/Form/fields/RefField.test.ts +45 -0
  196. package/src/components/Form/fields/RefField.tsx +266 -0
  197. package/src/components/Form/fields/SelectField.tsx +77 -0
  198. package/src/components/Form/fields/SelectOptionField.stories.tsx +66 -0
  199. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +39 -32
  200. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  201. package/src/components/Form/fields/TextField.tsx +38 -0
  202. package/src/components/Form/fields/TupleField.tsx +55 -0
  203. package/src/components/Form/fields/index.ts +17 -0
  204. package/src/components/Form/index.ts +6 -4
  205. package/src/components/Form/meta-tags.test.ts +47 -0
  206. package/src/components/Form/meta-tags.ts +33 -0
  207. package/src/components/ObjectForm/ObjectForm.stories.tsx +62 -0
  208. package/src/components/ObjectForm/ObjectForm.tsx +111 -0
  209. package/src/components/{ControlSection → ObjectForm}/index.ts +1 -1
  210. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +212 -0
  211. package/src/components/ObjectPicker/ObjectPicker.tsx +202 -0
  212. package/src/components/ObjectPicker/index.ts +5 -0
  213. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +350 -0
  214. package/src/components/ObjectProperties/ObjectProperties.test.tsx +111 -0
  215. package/src/components/ObjectProperties/ObjectProperties.tsx +146 -0
  216. package/src/components/ObjectProperties/index.ts +5 -0
  217. package/src/components/ObjectTree/ObjectTree.stories.tsx +134 -0
  218. package/src/components/ObjectTree/ObjectTree.tsx +190 -0
  219. package/src/components/ObjectTree/index.ts +5 -0
  220. package/src/components/RefEditor/RefEditor.stories.tsx +148 -0
  221. package/src/components/RefEditor/RefEditor.tsx +275 -0
  222. package/src/components/RefEditor/index.ts +6 -0
  223. package/src/components/RefEditor/ref-editor-extension.ts +254 -0
  224. package/src/components/Settings/AUDIT.md +51 -0
  225. package/src/components/Settings/Settings.stories.tsx +104 -0
  226. package/src/components/Settings/Settings.tsx +81 -0
  227. package/src/components/Settings/SettingsFieldSet.tsx +257 -0
  228. package/src/components/Settings/SettingsItem.tsx +58 -0
  229. package/src/components/Settings/SettingsSelectField.tsx +59 -0
  230. package/src/components/Settings/index.ts +7 -0
  231. package/src/components/ViewEditor/ViewEditor.stories.tsx +132 -64
  232. package/src/components/ViewEditor/ViewEditor.test.tsx +23 -22
  233. package/src/components/ViewEditor/ViewEditor.tsx +378 -216
  234. package/src/components/index.ts +6 -3
  235. package/src/components/testing.tsx +31 -12
  236. package/src/hooks/index.ts +2 -3
  237. package/src/hooks/useFormHandler.ts +380 -0
  238. package/src/hooks/useKeyHandler.tsx +37 -0
  239. package/src/index.ts +0 -4
  240. package/src/translations.ts +76 -59
  241. package/src/util/field-type.ts +60 -0
  242. package/src/util/field.ts +33 -43
  243. package/src/util/formatting.ts +22 -22
  244. package/src/util/index.ts +2 -1
  245. package/src/util/properties.test.ts +153 -0
  246. package/src/util/properties.ts +100 -0
  247. package/src/util/refs.ts +20 -19
  248. package/src/vite-env.d.ts +10 -0
  249. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  250. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  251. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -8
  252. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  253. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  254. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  255. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  256. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  257. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  258. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  259. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  260. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  261. package/dist/types/src/components/Form/FormContent.d.ts +0 -57
  262. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  263. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  264. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  265. package/dist/types/src/components/Form/Input.d.ts +0 -26
  266. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  267. package/dist/types/src/components/Form/RefField.d.ts +0 -12
  268. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  269. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  270. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  271. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  272. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  273. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
  274. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  275. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  276. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  277. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  278. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  279. package/dist/types/src/components/Form/factory.d.ts +0 -8
  280. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  281. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  282. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  283. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  284. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  285. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  286. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  287. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  288. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  289. package/dist/types/src/hooks/useForm.d.ts +0 -67
  290. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  291. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -23
  292. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  293. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  294. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  295. package/dist/types/src/util/schema.d.ts +0 -3
  296. package/dist/types/src/util/schema.d.ts.map +0 -1
  297. package/src/components/ControlSection/ControlSection.stories.tsx +0 -42
  298. package/src/components/ControlSection/ControlSection.tsx +0 -139
  299. package/src/components/Form/ArrayField.tsx +0 -125
  300. package/src/components/Form/Defaults.tsx +0 -204
  301. package/src/components/Form/FormActions.tsx +0 -54
  302. package/src/components/Form/FormContent.tsx +0 -241
  303. package/src/components/Form/FormContext.tsx +0 -110
  304. package/src/components/Form/Input.tsx +0 -51
  305. package/src/components/Form/RefField.tsx +0 -214
  306. package/src/components/Form/custom/GeoPointInput.tsx +0 -72
  307. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +0 -62
  308. package/src/components/Form/custom/TupleInput.tsx +0 -47
  309. package/src/components/Form/custom/index.ts +0 -7
  310. package/src/components/Form/factory.tsx +0 -31
  311. package/src/components/deprecated/Form.stories.tsx +0 -63
  312. package/src/components/deprecated/Form.tsx +0 -119
  313. package/src/components/deprecated/FormInput.tsx +0 -71
  314. package/src/components/deprecated/index.ts +0 -5
  315. package/src/hooks/useForm.ts +0 -293
  316. package/src/hooks/useQueryRefOptions.ts +0 -53
  317. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  318. package/src/util/schema.ts +0 -14
@@ -0,0 +1,275 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Extension, Prec } from '@codemirror/state';
6
+ import { type EditorView } from '@codemirror/view';
7
+ import React, { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
8
+
9
+ import { type Database, Filter, Obj, type Type } from '@dxos/echo';
10
+ import { useQuery } from '@dxos/react-client/echo';
11
+ import { type ThemedClassName, useThemeContext, useTranslation } from '@dxos/react-ui';
12
+ import {
13
+ Editor,
14
+ type EditorController,
15
+ type EditorMenuProviderProps,
16
+ type EditorViewProps,
17
+ type UseEditorMenuProps,
18
+ } from '@dxos/react-ui-editor';
19
+ import { createBasicExtensions, createThemeExtensions, insertAtCursor, keymap } from '@dxos/ui-editor';
20
+ import { getHashHue } from '@dxos/ui-theme';
21
+
22
+ import { translationKey } from '#translations';
23
+
24
+ import {
25
+ EMAIL_REGEX,
26
+ NAME_ADDR_REGEX,
27
+ type RefEditorMode,
28
+ type RefInfo,
29
+ formatMailbox,
30
+ refEditor,
31
+ refEditorRedecorate,
32
+ } from './ref-editor-extension';
33
+
34
+ export type RefEditorProps = ThemedClassName<
35
+ {
36
+ /** Database queried for objects of {@link RefEditorProps.type}. */
37
+ db?: Database.Database;
38
+ /** ECHO object type whose instances are offered in the typeahead and referenced as `@<id>`. */
39
+ type: Type.AnyObj;
40
+ value?: string;
41
+ readonly?: boolean;
42
+ /**
43
+ * Content model:
44
+ * - `'ref'` (default): the picker only matches existing objects; selection inserts an
45
+ * `@<id>` reference (raw tokens accepted by `match` are also allowed).
46
+ * - `'email'`: the picker shows one item per (object, value) pair and selection captures the
47
+ * value; the content is an RFC 5322 mailbox list (`Name <email>, ...`) with the comma
48
+ * separators hidden inside the tags.
49
+ */
50
+ mode?: RefEditorMode;
51
+ /** Pluggable raw-token matcher (e.g. {@link EMAIL_REGEX}); matching tokens are accepted as values. */
52
+ match?: RegExp;
53
+ /** Display label for an object; defaults to `Obj.getLabel` falling back to the id. */
54
+ getLabel?: (object: Obj.Unknown) => string;
55
+ /** Values an object can be captured as in `'email'` mode (e.g. a person's email addresses). */
56
+ getValues?: (object: Obj.Unknown) => readonly string[];
57
+ /** Menu item icon. */
58
+ icon?: string;
59
+ /** Already-translated placeholder; defaults to a generic hint. */
60
+ placeholder?: string;
61
+ /** Open the typeahead while typing (the `@` trigger remains available but is not required). */
62
+ activateOnTyping?: boolean;
63
+ } & Omit<EditorViewProps, 'initialValue'> &
64
+ Pick<EditorMenuProviderProps, 'numItems'>
65
+ >;
66
+
67
+ /**
68
+ * Single-line input for a list of object references. Typing `@` (or any text, with
69
+ * `activateOnTyping`) opens a typeahead menu matching objects of the given type by label (and
70
+ * by their `getValues` values). The content model depends on {@link RefEditorProps.mode}.
71
+ * Resolved entries render as atomic tags with the object's label; raw tokens accepted by the
72
+ * pluggable `match` regexp render as neutral tags.
73
+ */
74
+ export const RefEditor = forwardRef<EditorController, RefEditorProps>(
75
+ (
76
+ {
77
+ db,
78
+ type,
79
+ value,
80
+ readonly,
81
+ mode = 'ref',
82
+ match,
83
+ getLabel,
84
+ getValues,
85
+ icon = 'ph--link--regular',
86
+ placeholder: placeholderProp,
87
+ activateOnTyping,
88
+ numItems = 8,
89
+ ...props
90
+ },
91
+ forwardedRef,
92
+ ) => {
93
+ const { t } = useTranslation(translationKey);
94
+ const { themeMode } = useThemeContext();
95
+ const getObjectLabel = useCallback(
96
+ (object: Obj.Unknown) => getLabel?.(object) ?? Obj.getLabel(object) ?? object.id,
97
+ [getLabel],
98
+ );
99
+
100
+ // Reads go through a ref so the decoration extension stays stable: recreating extensions
101
+ // destroys and recreates the editor, which would blur the input on data changes.
102
+ const filter = useMemo(() => Filter.type(type), [type]);
103
+ const objects = useQuery(db, filter);
104
+ const refs = useMemo(
105
+ () =>
106
+ new Map<string, RefInfo>(
107
+ objects.map((object) => [object.id, { label: getObjectLabel(object), hue: getHashHue(object.id) }]),
108
+ ),
109
+ [objects, getObjectLabel],
110
+ );
111
+ const refsRef = useRef(refs);
112
+ refsRef.current = refs;
113
+
114
+ // Internal controller (merged with the forwarded ref) used to re-resolve reference
115
+ // decorations when object data changes after the document was rendered.
116
+ const controllerRef = useRef<EditorController | null>(null);
117
+ const composedRef = useCallback(
118
+ (controller: EditorController | null) => {
119
+ controllerRef.current = controller;
120
+ if (typeof forwardedRef === 'function') {
121
+ forwardedRef(controller);
122
+ } else if (forwardedRef) {
123
+ forwardedRef.current = controller;
124
+ }
125
+ },
126
+ [forwardedRef],
127
+ );
128
+ useEffect(() => {
129
+ controllerRef.current?.view?.dispatch({ effects: refEditorRedecorate.of(null) });
130
+ }, [refs]);
131
+
132
+ const getMenu = useCallback<NonNullable<UseEditorMenuProps['getMenu']>>(
133
+ async ({ text }) => {
134
+ const query = (text ?? '').replace(/^@/, '').toLowerCase();
135
+ const matchesQuery = (object: Obj.Unknown) =>
136
+ !query ||
137
+ getObjectLabel(object).toLowerCase().includes(query) ||
138
+ (getValues?.(object) ?? []).some((entry) => entry.toLowerCase().includes(query));
139
+
140
+ if (mode === 'email') {
141
+ // One item per (object, value) pair; selection captures the value as an RFC 5322 mailbox.
142
+ const mailboxes = objects
143
+ .flatMap((object) => (getValues?.(object) ?? []).map((entry) => ({ object, entry })))
144
+ .filter(
145
+ ({ object, entry }) =>
146
+ !query || getObjectLabel(object).toLowerCase().includes(query) || entry.toLowerCase().includes(query),
147
+ );
148
+
149
+ return [
150
+ {
151
+ id: 'mailboxes',
152
+ items: mailboxes.slice(0, numItems * 2).map(({ object, entry }) => ({
153
+ id: `${object.id}-${entry}`,
154
+ label: `${getObjectLabel(object)} (${entry})`,
155
+ icon,
156
+ onSelect: ({ view, head }: { view: EditorView; head: number }) =>
157
+ insertMailbox(view, head, formatMailbox(getObjectLabel(object), entry)),
158
+ })),
159
+ },
160
+ ];
161
+ }
162
+
163
+ // 'ref' mode: only match existing objects.
164
+ return [
165
+ {
166
+ id: 'refs',
167
+ items: objects
168
+ .filter(matchesQuery)
169
+ .slice(0, numItems * 2)
170
+ .map((object) => {
171
+ const label = getObjectLabel(object);
172
+ const detail = getValues?.(object)?.[0];
173
+ return {
174
+ id: object.id,
175
+ label: detail && detail !== label ? `${label} (${detail})` : label,
176
+ icon,
177
+ onSelect: ({ view, head }: { view: EditorView; head: number }) =>
178
+ insertAtCursor(view, head, `@${object.id} `),
179
+ };
180
+ }),
181
+ },
182
+ ];
183
+ },
184
+ [objects, numItems, mode, icon, getObjectLabel, getValues],
185
+ );
186
+
187
+ // NOTE: The placeholder is supplied via the popover (Editor.Root) so the trigger hint and the
188
+ // empty-state hint are a single message; a second basic-extension placeholder would stack with it.
189
+ // Must be referentially stable: the menu extension is memoized on it, and a new extension
190
+ // identity destroys and recreates the editor on every keystroke.
191
+ const placeholder = useMemo(
192
+ () => ({
193
+ content: placeholderProp ?? t(activateOnTyping ? 'ref-editor-auto.placeholder' : 'ref-editor.placeholder'),
194
+ focusOnly: false,
195
+ }),
196
+ [t, placeholderProp, activateOnTyping],
197
+ );
198
+
199
+ const extensions = useMemo<Extension[]>(
200
+ () => [
201
+ createBasicExtensions({ readOnly: readonly, lineWrapping: false }),
202
+ createThemeExtensions({ themeMode, slots: { scroller: { className: 'scrollbar-none' } } }),
203
+ refEditor({ mode, match, getRef: (id) => refsRef.current.get(id) }),
204
+ Prec.highest(
205
+ keymap.of([
206
+ {
207
+ key: 'Enter',
208
+ // Commit the in-progress token when it matches (insert the mode's separator, the
209
+ // same gesture as typing it); always consume the key (single-line input).
210
+ run: (view) => {
211
+ const { head } = view.state.selection.main;
212
+ const line = view.state.doc.lineAt(head);
213
+ const before = line.text.slice(0, head - line.from);
214
+ if (mode === 'email') {
215
+ const segment = before.slice(before.lastIndexOf(',') + 1).trim();
216
+ if (NAME_ADDR_REGEX.test(segment) || (match ?? EMAIL_REGEX).test(segment)) {
217
+ insertAtCursor(view, head, ', ');
218
+ }
219
+ } else {
220
+ const token = before.slice(before.search(/\S+$/));
221
+ if (token && match?.test(token)) {
222
+ insertAtCursor(view, head, ' ');
223
+ }
224
+ }
225
+ return true;
226
+ },
227
+ },
228
+ ]),
229
+ ),
230
+ ],
231
+ [readonly, themeMode, mode, match],
232
+ );
233
+
234
+ return (
235
+ <Editor.Root
236
+ extensions={extensions}
237
+ numItems={numItems}
238
+ trigger='@'
239
+ activateOnTyping={activateOnTyping}
240
+ placeholder={placeholder}
241
+ // The menu owns its own matching (labels AND values); the built-in label filter would
242
+ // drop value-matched objects whose label doesn't start with the query.
243
+ filter={false}
244
+ getMenu={getMenu}
245
+ ref={composedRef}
246
+ >
247
+ <Editor.View
248
+ // TOOD(burdon): Use same style as react-ui Input.
249
+ classNames='border border-separator rounded-xs px-2'
250
+ {...props}
251
+ initialValue={value}
252
+ selectionEnd
253
+ />
254
+ </Editor.Root>
255
+ );
256
+ },
257
+ );
258
+
259
+ RefEditor.displayName = 'RefEditor';
260
+
261
+ /**
262
+ * Insert an RFC 5322 mailbox, replacing the in-progress segment (anything typed after the last
263
+ * comma — multi-word queries can leave words outside the popover's completion range).
264
+ */
265
+ const insertMailbox = (view: EditorView, head: number, mailbox: string) => {
266
+ const line = view.state.doc.lineAt(head);
267
+ const before = line.text.slice(0, head - line.from);
268
+ const lastComma = before.lastIndexOf(',');
269
+ const from = line.from + (lastComma === -1 ? 0 : lastComma + 1);
270
+ const insert = `${lastComma === -1 ? '' : ' '}${mailbox}, `;
271
+ view.dispatch({
272
+ changes: { from, to: head, insert },
273
+ selection: { anchor: from + insert.length, head: from + insert.length },
274
+ });
275
+ };
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './RefEditor';
6
+ export * from './ref-editor-extension';
@@ -0,0 +1,254 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type EditorState, type Extension, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';
6
+ import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
7
+
8
+ import { Domino } from '@dxos/ui';
9
+ import { getHashHue, getStyles, mx } from '@dxos/ui-theme';
10
+
11
+ /**
12
+ * Display info for a resolved object reference.
13
+ */
14
+ export type RefInfo = {
15
+ label: string;
16
+ hue?: string;
17
+ };
18
+
19
+ /**
20
+ * Content model:
21
+ * - `'ref'`: whitespace-separated tokens; `@<id>` references to existing objects (and raw tokens
22
+ * accepted by the pluggable {@link RefEditorExtensionOptions.match} regexp).
23
+ * - `'email'`: comma-separated RFC 5322 mailboxes (`Name <email>` or bare `email`); the comma
24
+ * separators are consumed into the atomic tags so they are not visible.
25
+ */
26
+ export type RefEditorMode = 'ref' | 'email';
27
+
28
+ export type RefEditorExtensionOptions = {
29
+ mode?: RefEditorMode;
30
+ /** Resolve an object id referenced as `@<id>` to display info. */
31
+ getRef?: (id: string) => RefInfo | undefined;
32
+ /** Pluggable raw-token matcher (e.g. {@link EMAIL_REGEX}); matching tokens render as neutral tags. */
33
+ match?: RegExp;
34
+ };
35
+
36
+ /** Matches an object reference token (`@<object id>`). */
37
+ export const REF_REGEX = /^@([A-Za-z0-9]+)$/;
38
+
39
+ /** Matches a well-formed email address token. */
40
+ export const EMAIL_REGEX = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
41
+
42
+ /** Matches an RFC 5322 name-addr (`Name <email>`); group 1 = display name, group 2 = addr-spec. */
43
+ export const NAME_ADDR_REGEX = /^(.*?)\s*<([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})>$/;
44
+
45
+ /** Formats an RFC 5322 mailbox (`Name <email>`). */
46
+ export const formatMailbox = (name: string | undefined, email: string): string => (name ? `${name} <${email}>` : email);
47
+
48
+ /**
49
+ * Effect dispatched when reference data changes out-of-band (e.g. objects finish loading) so that
50
+ * already-rendered reference decorations re-resolve without a document change.
51
+ */
52
+ export const refEditorRedecorate = StateEffect.define<null>();
53
+
54
+ /**
55
+ * CodeMirror extension rendering reference-editor tokens as tags. Tags are atomic: the cursor
56
+ * cannot enter them and deletion removes the whole token.
57
+ * - `'ref'` mode: `@<id>` references resolve to the object's label (via
58
+ * {@link RefEditorExtensionOptions.getRef}); tokens accepted by the pluggable `match` regexp
59
+ * render as neutral tags.
60
+ * - `'email'` mode: RFC 5322 mailboxes render as tags labelled with the display name (falling
61
+ * back to the address); the trailing comma separator is hidden inside the tag's atomic range.
62
+ */
63
+ export const refEditor = ({ mode = 'ref', getRef, match }: RefEditorExtensionOptions = {}): Extension => {
64
+ const buildRefDecorations = (state: EditorState) => {
65
+ const deco = new RangeSetBuilder<Decoration>();
66
+ const text = state.doc.toString();
67
+ const tokenRegex = /\S+/g;
68
+ let tokenMatch: RegExpExecArray | null;
69
+ while ((tokenMatch = tokenRegex.exec(text))) {
70
+ const token = tokenMatch[0];
71
+ const from = tokenMatch.index;
72
+ const to = from + token.length;
73
+
74
+ const refMatch = token.match(REF_REGEX);
75
+ if (refMatch) {
76
+ const ref = getRef?.(refMatch[1]);
77
+ if (ref) {
78
+ deco.add(
79
+ from,
80
+ to,
81
+ Decoration.widget({
82
+ widget: new RefWidget(ref.label, ref.hue ?? getHashHue(refMatch[1])),
83
+ atomic: true,
84
+ }),
85
+ );
86
+ }
87
+ continue;
88
+ }
89
+
90
+ if (match?.test(token)) {
91
+ deco.add(
92
+ from,
93
+ to,
94
+ Decoration.widget({
95
+ widget: new TokenWidget(token),
96
+ atomic: true,
97
+ }),
98
+ );
99
+ }
100
+ }
101
+
102
+ return deco.finish();
103
+ };
104
+
105
+ const buildEmailDecorations = (state: EditorState) => {
106
+ const addrRegex = match ?? EMAIL_REGEX;
107
+ const deco = new RangeSetBuilder<Decoration>();
108
+ const text = state.doc.toString();
109
+ // Segments are comma-separated; a segment's atomic range includes its trailing comma (and
110
+ // following whitespace) so the separator never renders.
111
+ const segmentRegex = /[^,]+(?:,\s*)?|,\s*/g;
112
+ let segmentMatch: RegExpExecArray | null;
113
+ while ((segmentMatch = segmentRegex.exec(text))) {
114
+ const segment = segmentMatch[0];
115
+ const commaIndex = segment.indexOf(',');
116
+ const content = (commaIndex === -1 ? segment : segment.slice(0, commaIndex)).trim();
117
+ if (!content) {
118
+ continue;
119
+ }
120
+ const leading = segment.length - segment.trimStart().length;
121
+ const from = segmentMatch.index + leading;
122
+ const to = segmentMatch.index + segment.length;
123
+
124
+ const nameAddr = content.match(NAME_ADDR_REGEX);
125
+ if (nameAddr) {
126
+ deco.add(
127
+ from,
128
+ to,
129
+ Decoration.widget({
130
+ widget: new RefWidget(nameAddr[1] || nameAddr[2], getHashHue(nameAddr[2])),
131
+ atomic: true,
132
+ }),
133
+ );
134
+ } else if (addrRegex.test(content)) {
135
+ deco.add(
136
+ from,
137
+ to,
138
+ Decoration.widget({
139
+ widget: new TokenWidget(content),
140
+ atomic: true,
141
+ }),
142
+ );
143
+ }
144
+ }
145
+
146
+ return deco.finish();
147
+ };
148
+
149
+ const buildDecorations = mode === 'email' ? buildEmailDecorations : buildRefDecorations;
150
+
151
+ return [
152
+ styles,
153
+ StateField.define<DecorationSet>({
154
+ create: (state) => buildDecorations(state),
155
+ update: (deco, tr) => {
156
+ if (tr.docChanged || tr.effects.some((effect) => effect.is(refEditorRedecorate))) {
157
+ return buildDecorations(tr.state);
158
+ }
159
+
160
+ return deco;
161
+ },
162
+ provide: (field) => [
163
+ EditorView.decorations.from(field),
164
+ EditorView.atomicRanges.of((view) => {
165
+ const builder = new RangeSetBuilder<Decoration>();
166
+ const cursor = view.state.field(field).iter();
167
+ while (cursor.value) {
168
+ if (cursor.value.spec.atomic) {
169
+ builder.add(cursor.from, cursor.to, cursor.value);
170
+ }
171
+ cursor.next();
172
+ }
173
+
174
+ return builder.finish();
175
+ }),
176
+ ],
177
+ }),
178
+ ];
179
+ };
180
+
181
+ const lineHeight = '30px';
182
+
183
+ /**
184
+ * NOTE: The outer container vertically aligns the inner text with content in the outer div.
185
+ */
186
+ const container = (classNames: string, ...children: Domino<HTMLElement>[]) => {
187
+ const inner = Domino.of('span')
188
+ .classNames(mx('inline-flex h-[26px] border rounded-xs', classNames))
189
+ .append(...children);
190
+ return Domino.of('span').classNames('inline-flex h-[28px] align-middle').append(inner).root;
191
+ };
192
+
193
+ /**
194
+ * Resolved reference (object reference or RFC 5322 mailbox), rendered as the object's label.
195
+ */
196
+ class RefWidget extends WidgetType {
197
+ constructor(
198
+ private readonly _label: string,
199
+ private readonly _hue: string,
200
+ ) {
201
+ super();
202
+ }
203
+
204
+ override eq(other: this) {
205
+ return this._label === other._label && this._hue === other._hue;
206
+ }
207
+
208
+ override toDOM() {
209
+ const { bg: fill, border, surface } = getStyles(this._hue);
210
+ return container(
211
+ border,
212
+ Domino.of('span').classNames(mx('flex items-center px-1 text-black text-xs', fill)).text('@'),
213
+ Domino.of('span')
214
+ .classNames(mx('flex items-center px-1 text-subdued text-sm rounded-r-[3px]', surface))
215
+ .text(this._label),
216
+ );
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Raw token accepted by the pluggable matcher (e.g. an email address).
222
+ */
223
+ class TokenWidget extends WidgetType {
224
+ constructor(private readonly _token: string) {
225
+ super();
226
+ }
227
+
228
+ override eq(other: this) {
229
+ return this._token === other._token;
230
+ }
231
+
232
+ override toDOM() {
233
+ return container(
234
+ 'border-separator',
235
+ Domino.of('span').classNames('flex items-center px-1 text-subdued text-sm').text(this._token),
236
+ );
237
+ }
238
+ }
239
+
240
+ const styles = EditorView.theme({
241
+ '.cm-line': {
242
+ lineHeight,
243
+ },
244
+ // Match the standard Input block size (md density): 2.5rem, 2rem on pointer-fine devices.
245
+ '.cm-scroller': {
246
+ alignItems: 'center',
247
+ minHeight: '2.5rem',
248
+ },
249
+ '@media (pointer: fine)': {
250
+ '.cm-scroller': {
251
+ minHeight: '2rem',
252
+ },
253
+ },
254
+ });
@@ -0,0 +1,51 @@
1
+ # Plugin Settings Audit
2
+
3
+ Tracks which plugin settings panels use schema-driven `Settings.FieldSet` and which still hand-roll their controls.
4
+
5
+ The target shape for a plugin settings panel is:
6
+
7
+ ```tsx
8
+ <SettingsForm.Viewport>
9
+ <SettingsForm.Section title={t('settings.title', { ns: meta.id })}>
10
+ <SettingsForm.FieldSet
11
+ readonly={!onSettingsChange}
12
+ schema={Settings.Settings}
13
+ values={settings}
14
+ onValuesChanged={(values) => onSettingsChange?.(() => values)}
15
+ />
16
+ </SettingsForm.Section>
17
+ </SettingsForm.Viewport>
18
+ ```
19
+
20
+ Field labels and descriptions come from `.annotations({ title, description })` on each schema field. Use `fieldMap` for runtime-driven option lists (model pickers, etc.). Use the `visible` callback to gate fields on environment or sibling values.
21
+
22
+ ## FieldSet adopters
23
+
24
+ | Plugin | Notes |
25
+ | ---------------------- | -------------------------------------------------------------------------------------------------------- |
26
+ | `plugin-assistant` | Nested `modelDefaults` struct; `fieldMap` provides per-provider model selectors via `createSelectField`. |
27
+ | `plugin-crx` | Two booleans. |
28
+ | `plugin-deck` | Five booleans; `enableNativeRedirect` hidden via `visible` when `isSocket`. |
29
+ | `plugin-inbox` | Single boolean. |
30
+ | `plugin-markdown` | Original reference adopter. `snippets` text field gated via `visible`/`fieldMap`. |
31
+ | `plugin-meeting` | Single boolean. |
32
+ | `plugin-observability` | Boolean; info `Message.Root` banner kept above `FieldSet`. |
33
+ | `plugin-presenter` | Single boolean. |
34
+ | `plugin-sample` | Single boolean. |
35
+ | `plugin-script` | Mixed: auth `Button` retained as a manual `SettingsForm.Item`; `editorInputMode` rendered by `FieldSet`. |
36
+ | `plugin-sketch` | Boolean + `gridType` literal union (rendered as a Select — was previously a Switch). |
37
+
38
+ ## Skipped
39
+
40
+ | Plugin | Reason |
41
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
+ | `plugin-debug` | Action buttons (download diagnostics, download logs, repair), `Toast`, and a storage-adapter selector wired to `client.config` rather than the plugin settings atom. |
43
+ | `plugin-space` | Renders a list of spaces with per-space "open settings" buttons — not bound to any schema field. |
44
+ | `plugin-spacetime` | Section is currently empty; nothing to render. |
45
+ | `plugin-thread` | Settings schema is `Schema.Struct({})` — nothing to render. |
46
+
47
+ ## When to extend `SettingsFieldSet` vs. add a custom field
48
+
49
+ - **Extend `SettingsFieldSet`** when the rendering rule applies generally to a schema construct (e.g. nested structs recursing into sibling fields, literal unions becoming Selects). Keep auto-detection in `detectFieldType`.
50
+ - **Use `fieldMap`** when the schema can't fully describe the rendering — runtime-driven option lists, custom inputs, or override of the auto-rendered control. Use the typed form `SettingsFieldMap<T>` (or `satisfies SettingsFieldMap<Settings>`) so the entries are checked against the values shape.
51
+ - **Drop back to manual `SettingsForm.Item`** when the panel needs UI that isn't bound to a settings field at all (action buttons, info banners, lists of external entities). These coexist as siblings of `FieldSet` inside the same `Section`.
@@ -0,0 +1,104 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Schema from 'effect/Schema';
7
+ import React from 'react';
8
+
9
+ import { random } from '@dxos/random';
10
+ import { Button, Input } from '@dxos/react-ui';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
+
13
+ import { translations } from '#translations';
14
+
15
+ import { Settings } from './Settings';
16
+
17
+ random.seed(132);
18
+
19
+ const DefaultStory = () => {
20
+ return (
21
+ <Settings.Viewport>
22
+ <Settings.Section title='Settings' description={random.lorem.paragraphs(1)}>
23
+ <Settings.Item title={random.lorem.sentence(2)} description={random.lorem.paragraphs(1)}>
24
+ <Input.TextInput placeholder='Input' />
25
+ </Settings.Item>
26
+ <Settings.Item title={random.lorem.sentence(2)} description={random.lorem.paragraphs(2)}>
27
+ <Input.Switch />
28
+ </Settings.Item>
29
+ <Settings.Item title={random.lorem.sentence(3)} description={random.lorem.paragraphs(2)}>
30
+ <Button>Test</Button>
31
+ </Settings.Item>
32
+ </Settings.Section>
33
+ <Settings.Section title='Panel Example'>
34
+ <Settings.Panel>
35
+ <h3 className='text-lg mb-2'>Members</h3>
36
+ <p className='text-description'>Content inside a panel.</p>
37
+ </Settings.Panel>
38
+ </Settings.Section>
39
+ </Settings.Viewport>
40
+ );
41
+ };
42
+
43
+ const meta = {
44
+ title: 'ui/react-ui-form/Settings',
45
+ render: DefaultStory,
46
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
47
+ parameters: {
48
+ layout: 'fullscreen',
49
+ translations,
50
+ },
51
+ } satisfies Meta;
52
+
53
+ export default meta;
54
+
55
+ type Story = StoryObj<typeof meta>;
56
+
57
+ export const Default: Story = {};
58
+
59
+ const TestViewModes = ['preview', 'readonly', 'source'] as const;
60
+ const TestViewMode = Schema.Union(...TestViewModes.map((mode) => Schema.Literal(mode)));
61
+
62
+ const TestSettings = Schema.mutable(
63
+ Schema.Struct({
64
+ viewMode: TestViewMode.annotations({ title: 'View mode', description: 'Default document view mode.' }),
65
+ toolbar: Schema.optional(
66
+ Schema.Boolean.annotations({ title: 'Show toolbar', description: 'Display formatting toolbar.' }),
67
+ ),
68
+ fontSize: Schema.optional(
69
+ Schema.Number.annotations({ title: 'Font size', description: 'Editor font size in pixels.' }),
70
+ ),
71
+ placeholder: Schema.optional(
72
+ Schema.String.annotations({ title: 'Placeholder', description: 'Default placeholder text.' }),
73
+ ),
74
+ debug: Schema.optional(Schema.Boolean.annotations({ title: 'Debug mode', description: 'Enable debug features.' })),
75
+ }),
76
+ );
77
+
78
+ type TestSettings = Schema.Schema.Type<typeof TestSettings>;
79
+
80
+ const FieldSetStory = () => {
81
+ const [values, setValues] = React.useState<TestSettings>({
82
+ viewMode: 'preview',
83
+ toolbar: true,
84
+ fontSize: 14,
85
+ debug: false,
86
+ });
87
+
88
+ return (
89
+ <Settings.Viewport>
90
+ <Settings.Section title='Plugin Settings (Auto-generated)'>
91
+ <Settings.FieldSet
92
+ schema={TestSettings}
93
+ values={values}
94
+ onValuesChanged={setValues}
95
+ visible={(path, values) => path !== 'placeholder' || !!values.debug}
96
+ />
97
+ </Settings.Section>
98
+ </Settings.Viewport>
99
+ );
100
+ };
101
+
102
+ export const FieldSet: Story = {
103
+ render: FieldSetStory,
104
+ };