@dxos/react-ui-form 0.8.4-main.fffef41 → 0.9.0

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 (301) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +0 -1
  3. package/dist/lib/browser/index.mjs +4005 -2402
  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 +4005 -2402
  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 -7
  14. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  15. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +73 -59
  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 -27
  18. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.stories.d.ts +192 -137
  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 +39 -12
  94. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  95. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +69 -58
  96. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  97. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
  98. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
  99. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +132 -0
  100. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
  101. package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
  102. package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
  103. package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
  104. package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
  105. package/dist/types/src/components/ObjectTree/ObjectTree.d.ts +9 -0
  106. package/dist/types/src/components/ObjectTree/ObjectTree.d.ts.map +1 -0
  107. package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts +91 -0
  108. package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts.map +1 -0
  109. package/dist/types/src/components/ObjectTree/index.d.ts +2 -0
  110. package/dist/types/src/components/ObjectTree/index.d.ts.map +1 -0
  111. package/dist/types/src/components/RefEditor/RefEditor.d.ts +73 -0
  112. package/dist/types/src/components/RefEditor/RefEditor.d.ts.map +1 -0
  113. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts +92 -0
  114. package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts.map +1 -0
  115. package/dist/types/src/components/RefEditor/index.d.ts +3 -0
  116. package/dist/types/src/components/RefEditor/index.d.ts.map +1 -0
  117. package/dist/types/src/components/RefEditor/ref-editor-extension.d.ts +47 -0
  118. package/dist/types/src/components/RefEditor/ref-editor-extension.d.ts.map +1 -0
  119. package/dist/types/src/components/Settings/Settings.d.ts +51 -0
  120. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  121. package/dist/types/src/components/Settings/Settings.stories.d.ts +89 -0
  122. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  123. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
  124. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
  125. package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
  126. package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
  127. package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
  128. package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +1 -0
  129. package/dist/types/src/components/Settings/index.d.ts +4 -0
  130. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  131. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +15 -19
  132. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  133. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +75 -64
  134. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  135. package/dist/types/src/components/index.d.ts +5 -1
  136. package/dist/types/src/components/index.d.ts.map +1 -1
  137. package/dist/types/src/components/testing.d.ts +13 -6
  138. package/dist/types/src/components/testing.d.ts.map +1 -1
  139. package/dist/types/src/hooks/index.d.ts +2 -3
  140. package/dist/types/src/hooks/index.d.ts.map +1 -1
  141. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  142. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  143. package/dist/types/src/hooks/useKeyHandler.d.ts +7 -0
  144. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  145. package/dist/types/src/index.d.ts +0 -2
  146. package/dist/types/src/index.d.ts.map +1 -1
  147. package/dist/types/src/translations.d.ts +70 -59
  148. package/dist/types/src/translations.d.ts.map +1 -1
  149. package/dist/types/src/util/field-type.d.ts +19 -0
  150. package/dist/types/src/util/field-type.d.ts.map +1 -0
  151. package/dist/types/src/util/field.d.ts +3 -3
  152. package/dist/types/src/util/field.d.ts.map +1 -1
  153. package/dist/types/src/util/formatting.d.ts +2 -2
  154. package/dist/types/src/util/formatting.d.ts.map +1 -1
  155. package/dist/types/src/util/index.d.ts +2 -1
  156. package/dist/types/src/util/index.d.ts.map +1 -1
  157. package/dist/types/src/util/properties.d.ts +35 -0
  158. package/dist/types/src/util/properties.d.ts.map +1 -0
  159. package/dist/types/src/util/properties.test.d.ts +2 -0
  160. package/dist/types/src/util/properties.test.d.ts.map +1 -0
  161. package/dist/types/src/util/refs.d.ts +2 -2
  162. package/dist/types/src/util/refs.d.ts.map +1 -1
  163. package/dist/types/tsconfig.tsbuildinfo +1 -1
  164. package/package.json +64 -52
  165. package/src/components/FieldEditor/FieldEditor.stories.tsx +44 -25
  166. package/src/components/FieldEditor/FieldEditor.test.tsx +4 -2
  167. package/src/components/FieldEditor/FieldEditor.tsx +91 -86
  168. package/src/components/Form/AUDIT.md +17 -0
  169. package/src/components/Form/Form.stories.tsx +147 -252
  170. package/src/components/Form/Form.tsx +461 -108
  171. package/src/components/Form/FormField.tsx +345 -0
  172. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  173. package/src/components/Form/FormFieldComponent.tsx +253 -0
  174. package/src/components/Form/FormFieldSet.tsx +213 -0
  175. package/src/components/Form/FormTooltipsContext.ts +25 -0
  176. package/src/components/Form/Layout/FormLayout.stories.test.tsx +51 -0
  177. package/src/components/Form/Layout/FormLayout.stories.tsx +422 -0
  178. package/src/components/Form/Layout/FormLayout.test.ts +59 -0
  179. package/src/components/Form/Layout/FormLayout.tsx +251 -0
  180. package/src/components/Form/Layout/annotation.ts +38 -0
  181. package/src/components/Form/Layout/index.ts +7 -0
  182. package/src/components/Form/Layout/parser.test.ts +120 -0
  183. package/src/components/Form/Layout/parser.ts +167 -0
  184. package/src/components/Form/README.md +3 -0
  185. package/src/components/Form/fields/ArrayField.tsx +173 -0
  186. package/src/components/Form/fields/BooleanField.tsx +22 -0
  187. package/src/components/Form/fields/DateField.tsx +101 -0
  188. package/src/components/Form/fields/GeoPointField.tsx +124 -0
  189. package/src/components/Form/fields/InlineRefField.tsx +138 -0
  190. package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
  191. package/src/components/Form/fields/MarkdownField.tsx +167 -0
  192. package/src/components/Form/fields/NumberField.tsx +76 -0
  193. package/src/components/Form/fields/RefField.test.ts +45 -0
  194. package/src/components/Form/fields/RefField.tsx +266 -0
  195. package/src/components/Form/fields/SelectField.tsx +77 -0
  196. package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +23 -18
  197. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +33 -26
  198. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  199. package/src/components/Form/fields/TextField.tsx +38 -0
  200. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +17 -9
  201. package/src/components/Form/fields/index.ts +17 -0
  202. package/src/components/Form/index.ts +6 -4
  203. package/src/components/Form/meta-tags.test.ts +47 -0
  204. package/src/components/Form/meta-tags.ts +33 -0
  205. package/src/components/ObjectForm/ObjectForm.stories.tsx +62 -0
  206. package/src/components/ObjectForm/ObjectForm.tsx +111 -0
  207. package/src/components/{ControlSection → ObjectForm}/index.ts +1 -1
  208. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +26 -21
  209. package/src/components/ObjectPicker/ObjectPicker.tsx +123 -82
  210. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +350 -0
  211. package/src/components/ObjectProperties/ObjectProperties.test.tsx +111 -0
  212. package/src/components/ObjectProperties/ObjectProperties.tsx +146 -0
  213. package/src/components/ObjectProperties/index.ts +5 -0
  214. package/src/components/ObjectTree/ObjectTree.stories.tsx +134 -0
  215. package/src/components/ObjectTree/ObjectTree.tsx +190 -0
  216. package/src/components/ObjectTree/index.ts +5 -0
  217. package/src/components/RefEditor/RefEditor.stories.tsx +148 -0
  218. package/src/components/RefEditor/RefEditor.tsx +275 -0
  219. package/src/components/RefEditor/index.ts +6 -0
  220. package/src/components/RefEditor/ref-editor-extension.ts +254 -0
  221. package/src/components/Settings/AUDIT.md +51 -0
  222. package/src/components/Settings/Settings.stories.tsx +104 -0
  223. package/src/components/Settings/Settings.tsx +81 -0
  224. package/src/components/Settings/SettingsFieldSet.tsx +257 -0
  225. package/src/components/Settings/SettingsItem.tsx +58 -0
  226. package/src/components/Settings/SettingsSelectField.tsx +59 -0
  227. package/src/components/Settings/index.ts +7 -0
  228. package/src/components/ViewEditor/ViewEditor.stories.tsx +70 -76
  229. package/src/components/ViewEditor/ViewEditor.test.tsx +17 -13
  230. package/src/components/ViewEditor/ViewEditor.tsx +340 -226
  231. package/src/components/index.ts +5 -1
  232. package/src/components/testing.tsx +24 -11
  233. package/src/hooks/index.ts +2 -3
  234. package/src/hooks/useFormHandler.ts +380 -0
  235. package/src/hooks/useKeyHandler.tsx +37 -0
  236. package/src/index.ts +0 -4
  237. package/src/translations.ts +76 -64
  238. package/src/util/field-type.ts +60 -0
  239. package/src/util/field.ts +25 -25
  240. package/src/util/formatting.ts +22 -22
  241. package/src/util/index.ts +2 -1
  242. package/src/util/properties.test.ts +153 -0
  243. package/src/util/properties.ts +100 -0
  244. package/src/util/refs.ts +16 -15
  245. package/src/vite-env.d.ts +10 -0
  246. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  247. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  248. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -83
  249. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  250. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  251. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  252. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  253. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  254. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  255. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  256. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  257. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  258. package/dist/types/src/components/Form/FormContent.d.ts +0 -63
  259. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  260. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  261. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  262. package/dist/types/src/components/Form/Input.d.ts +0 -29
  263. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  264. package/dist/types/src/components/Form/RefField.d.ts +0 -22
  265. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  266. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  267. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  268. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  269. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  270. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -78
  271. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  272. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  273. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  274. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  275. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  276. package/dist/types/src/components/Form/factory.d.ts +0 -8
  277. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  278. package/dist/types/src/hooks/useForm.d.ts +0 -67
  279. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  280. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -29
  281. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  282. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  283. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  284. package/dist/types/src/util/schema.d.ts +0 -3
  285. package/dist/types/src/util/schema.d.ts.map +0 -1
  286. package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
  287. package/src/components/ControlSection/ControlSection.tsx +0 -153
  288. package/src/components/Form/ArrayField.tsx +0 -125
  289. package/src/components/Form/Defaults.tsx +0 -205
  290. package/src/components/Form/FormActions.tsx +0 -54
  291. package/src/components/Form/FormContent.tsx +0 -362
  292. package/src/components/Form/FormContext.tsx +0 -110
  293. package/src/components/Form/Input.tsx +0 -54
  294. package/src/components/Form/RefField.tsx +0 -283
  295. package/src/components/Form/custom/GeoPointInput.tsx +0 -80
  296. package/src/components/Form/custom/index.ts +0 -7
  297. package/src/components/Form/factory.tsx +0 -31
  298. package/src/hooks/useForm.ts +0 -296
  299. package/src/hooks/useQueryRefOptions.ts +0 -62
  300. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  301. package/src/util/schema.ts +0 -12
@@ -2,92 +2,144 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import * as Array from 'effect/Array';
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
6
  import * as Match from 'effect/Match';
7
7
  import * as Option from 'effect/Option';
8
8
  import * as Schema from 'effect/Schema';
9
- import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react';
9
+ import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
10
10
 
11
- import { QueryAST } from '@dxos/echo';
12
- import { EchoSchema, Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
13
- import { Filter, Query, type SchemaRegistry } from '@dxos/echo-db';
11
+ import {
12
+ DXN,
13
+ EID,
14
+ Entity,
15
+ Feed,
16
+ Filter,
17
+ Format,
18
+ Obj,
19
+ Query,
20
+ QueryAST,
21
+ Ref,
22
+ type Registry,
23
+ Type,
24
+ View,
25
+ } from '@dxos/echo';
26
+ import { SchemaEx } from '@dxos/effect';
14
27
  import { invariant } from '@dxos/invariant';
15
- import { Callout, IconButton, Input, type ThemedClassName, useTranslation } from '@dxos/react-ui';
28
+ import { useObject, useQuery } from '@dxos/react-client/echo';
29
+ import { IconButton, Input, Message, type ThemedClassName, useTranslation } from '@dxos/react-ui';
16
30
  import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
17
31
  import { List } from '@dxos/react-ui-list';
18
- import { cardSpacing } from '@dxos/react-ui-stack';
19
- import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
20
32
  import {
21
- FieldSchema,
22
- type FieldType,
33
+ ParentLabelAnnotation,
23
34
  ProjectionModel,
24
35
  VIEW_FIELD_LIMIT,
25
- type View,
26
- getTypenameFromQuery,
36
+ createEchoChangeCallback,
37
+ getTypeURIFromQuery,
27
38
  } from '@dxos/schema';
39
+ import { mx, osTranslations } from '@dxos/ui-theme';
28
40
 
29
- import { translationKey } from '../../translations';
30
- import { FieldEditor } from '../FieldEditor';
31
- import { Form, type FormProps, type InputComponent, InputHeader, type InputProps } from '../Form';
41
+ import { translationKey } from '#translations';
32
42
 
33
- const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
34
- const listItemGrid = 'grid grid-cols-subgrid col-span-5';
43
+ import { FieldEditor } from '../FieldEditor';
44
+ import {
45
+ Form,
46
+ type FormFieldComponent,
47
+ type FormFieldComponentProps,
48
+ FormFieldLabel,
49
+ type FormFieldMap,
50
+ type FormRootProps,
51
+ } from '../Form';
35
52
 
36
53
  export type ViewEditorProps = ThemedClassName<
37
54
  {
38
- schema: Schema.Schema.AnyNoContext;
55
+ type?: Type.AnyEntity;
39
56
  view: View.View;
40
- mode?: 'schema' | 'query' | 'named-query';
41
- registry?: SchemaRegistry;
42
- readonly?: boolean;
57
+ mode?: 'schema' | 'tag';
58
+ registry?: Registry.Registry;
43
59
  showHeading?: boolean;
44
- onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
60
+ onQueryChanged?: (query: QueryAST.Query, target?: EID.EID) => void;
45
61
  onDelete?: (fieldId: string) => void;
46
- } & Pick<FormProps<any>, 'outerSpacing'> &
47
- Pick<QueryFormProps, 'types' | 'tags'>
62
+ } & Pick<QueryFormProps, 'types' | 'tags'> &
63
+ Pick<FormRootProps<any>, 'readonly' | 'db'>
48
64
  >;
49
65
 
50
66
  /**
51
67
  * Schema-based object form.
52
68
  */
53
- export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
69
+ export const ViewEditor = forwardRef<ProjectionModel | null, ViewEditorProps>(
54
70
  (
55
71
  {
56
72
  classNames,
57
- schema,
73
+ type,
58
74
  view,
59
75
  mode = 'schema',
60
76
  registry,
77
+ db,
61
78
  readonly,
62
79
  showHeading = false,
63
80
  types,
64
81
  tags,
65
82
  onQueryChanged,
66
83
  onDelete,
67
- outerSpacing = true,
68
84
  },
69
85
  forwardedRef,
70
86
  ) => {
71
- const schemaReadonly = !isMutable(schema);
87
+ const atomRegistry = useContext(RegistryContext);
88
+ const schemaReadonly = type == null || Type.getDatabase(type) == null;
72
89
  const { t } = useTranslation(translationKey);
90
+
73
91
  const projectionModel = useMemo(() => {
74
- // Use reactive and mutable version of json schema when schema is mutable.
75
- const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
76
- return new ProjectionModel(jsonSchema, view.projection);
77
- }, [schema, JSON.stringify(view.projection)]);
78
- useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
79
- const [expandedField, setExpandedField] = useState<FieldType['id']>();
92
+ if (!type) {
93
+ return null;
94
+ }
95
+ const jsonSchema = type.jsonSchema;
96
+
97
+ // Always use createEchoChangeCallback since the view is ECHO-backed.
98
+ // Pass type only when mutable to allow schema mutations.
99
+ const change = createEchoChangeCallback(view, Type.getDatabase(type) != null ? type : undefined);
100
+
101
+ const model = new ProjectionModel({
102
+ registry: atomRegistry,
103
+ view,
104
+ baseSchema: jsonSchema,
105
+ change,
106
+ });
107
+
108
+ return model;
109
+ }, [atomRegistry, type, view]);
110
+
111
+ useImperativeHandle<ProjectionModel | null, ProjectionModel | null>(forwardedRef, () => projectionModel, [
112
+ projectionModel,
113
+ ]);
80
114
 
81
115
  const queueTarget = Match.value(view.query.ast).pipe(
82
- Match.when({ type: 'options' }, ({ options }) => {
83
- return Option.fromNullable(options.queues).pipe(
84
- Option.flatMap((queues) => Array.head(queues)),
116
+ Match.when({ type: 'from' }, ({ from }) => {
117
+ if (from._tag !== 'scope') {
118
+ return undefined;
119
+ }
120
+ const feedScope = from.scopes.find((s) => s._tag === 'feed');
121
+ return Option.fromNullable(feedScope).pipe(
122
+ Option.map((s) => s.feedUri),
85
123
  Option.getOrUndefined,
86
124
  );
87
125
  }),
88
126
  Match.orElse(() => undefined),
89
127
  );
90
128
 
129
+ const feeds = useQuery(db, Filter.type(Feed.Feed));
130
+
131
+ const targetRef = useMemo(() => {
132
+ if (!queueTarget) {
133
+ return undefined;
134
+ }
135
+ const targetEid = EID.tryParse(queueTarget);
136
+ const feed = feeds.find((feed) => {
137
+ const feedEid = Feed.getQueueUri(feed);
138
+ return feedEid != null && targetEid != null && EID.equals(feedEid, targetEid);
139
+ });
140
+ return feed ? Ref.fromURI(Entity.getURI(feed)) : undefined;
141
+ }, [queueTarget, feeds]);
142
+
91
143
  const viewSchema = useMemo(() => {
92
144
  const base = Schema.Struct({
93
145
  query:
@@ -96,14 +148,15 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
96
148
  : QueryAST.Query.annotations({ title: 'Query' }),
97
149
  });
98
150
 
99
- if (mode === 'query' || mode === 'named-query') {
100
- const name = Schema.Struct({
101
- name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
102
- });
103
-
151
+ if (mode === 'tag') {
104
152
  return Schema.Struct({
105
- ...(mode === 'named-query' ? { ...name.fields, ...base.fields } : base.fields),
106
- target: Schema.optional(Schema.String.annotations({ title: 'Target Queue' })),
153
+ ...base.fields,
154
+ // TODO(wittjosiah): Replace Type.Feed with Dataset.Dataset when Ref.Ref supports unions.
155
+ target: Ref.Ref(Feed.Feed).pipe(
156
+ Schema.annotations({ title: 'Target Feed' }),
157
+ ParentLabelAnnotation.set(true),
158
+ Schema.optional,
159
+ ),
107
160
  }).pipe(Schema.mutable);
108
161
  }
109
162
 
@@ -112,221 +165,282 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
112
165
 
113
166
  // TODO(burdon): Need to warn user of possible consequences of editing.
114
167
  // TODO(burdon): Settings should have domain name owned by user.
115
- const viewValues = useMemo(
116
- () => ({
117
- name: view.name,
118
- query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
119
- target: queueTarget,
120
- }),
121
- [view.name, mode, view.query.ast, queueTarget],
122
- );
168
+ const viewValues = useMemo(() => {
169
+ // Schema mode edits a typename; show the version-less name of the type DXN.
170
+ const typeUri = getTypeURIFromQuery(view.query.ast);
171
+ const typeDxn = typeUri ? DXN.tryMake(typeUri) : undefined;
172
+ return {
173
+ query: mode === 'schema' ? (typeDxn ? DXN.getName(typeDxn) : (typeUri ?? '')) : view.query.ast,
174
+ target: targetRef,
175
+ };
176
+ }, [mode, view.query.ast, targetRef]);
123
177
 
124
- const handleToggleField = useCallback(
125
- (field: FieldType) => {
126
- setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
127
- },
128
- [readonly],
178
+ const fieldMap = useMemo<FormFieldMap | undefined>(
179
+ () => (mode === 'tag' ? customFields({ types, tags }) : undefined),
180
+ [mode, types, tags],
129
181
  );
130
182
 
131
- // TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
132
-
133
- const handleAdd = useCallback(() => {
134
- invariant(!readonly);
135
- const field = projectionModel.createFieldProjection();
136
- setExpandedField(field.id);
137
- }, [schema, projectionModel, readonly]);
138
-
139
183
  const handleUpdate = useCallback(
140
184
  (values: any) => {
141
- invariant(!readonly);
142
- requestAnimationFrame(() => {
143
- if ('name' in values && view.name !== values.name) {
144
- view.name = values.name;
145
- return;
185
+ const targetValue = values.target;
186
+ let queueDxn: EID.EID | undefined;
187
+
188
+ if (Ref.isRef(targetValue)) {
189
+ const feedUri = targetValue.uri;
190
+ const feed = feeds.find((feed) => Obj.getURI(feed) === feedUri);
191
+ if (feed) {
192
+ queueDxn = Feed.getQueueUri(feed);
146
193
  }
194
+ }
147
195
 
148
- const query = mode === 'schema' ? Query.select(Filter.typename(values.query)).ast : values.query;
149
- onQueryChanged?.(query, values.target);
150
- });
196
+ // TODO(wittjosiah): Deep-clone the AST to plain JS or ECHO proxy arrays become objects with numeric keys.
197
+ // In schema mode, values.query is either a DXN name (static types) or a raw `echo:` EID
198
+ // (db-backed types, which have no DXN). Preserve EIDs verbatim; wrap bare names as DXN.
199
+ const query =
200
+ mode === 'schema'
201
+ ? Query.select(Filter.type(EID.isEID(values.query) ? (values.query as EID.EID) : DXN.make(values.query)))
202
+ .ast
203
+ : JSON.parse(JSON.stringify(values.query));
204
+ onQueryChanged?.(query, queueDxn);
151
205
  },
152
- [onQueryChanged, readonly, view, queueTarget, mode],
206
+ [onQueryChanged, mode, feeds],
153
207
  );
154
208
 
155
209
  const handleDelete = useCallback(
156
210
  (fieldId: string) => {
157
211
  invariant(!readonly);
158
- if (fieldId === expandedField) {
159
- setExpandedField(undefined);
160
- }
161
-
162
212
  onDelete?.(fieldId);
163
213
  },
164
- [expandedField, onDelete, readonly],
214
+ [onDelete, readonly],
165
215
  );
166
216
 
167
- const handleMove = useCallback(
168
- (fromIndex: number, toIndex: number) => {
169
- invariant(!readonly);
217
+ return (
218
+ <div className={mx(classNames)}>
219
+ {/* If readonly is set, then the callout is not needed. */}
220
+ {schemaReadonly && !readonly && (
221
+ <Message.Root valence='info' classNames='my-form-padding'>
222
+ <Message.Title>{t('system-schema.description')}</Message.Title>
223
+ </Message.Root>
224
+ )}
225
+
226
+ {/* TODO(burdon): Is the form read-only or just the schema? */}
227
+ <Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} db={db} onValuesChanged={handleUpdate}>
228
+ <Form.FieldSet />
229
+
230
+ {type && (
231
+ <>
232
+ <FormFieldLabel label={t('fields.label')} asChild />
233
+ <FieldList
234
+ type={type}
235
+ view={view}
236
+ registry={registry}
237
+ readonly={readonly}
238
+ showHeading={showHeading}
239
+ onDelete={handleDelete}
240
+ />
241
+ </>
242
+ )}
243
+ </Form.Root>
244
+ </div>
245
+ );
246
+ },
247
+ );
248
+
249
+ type FieldListProps = Omit<
250
+ Pick<ViewEditorProps, 'type' | 'view' | 'registry' | 'readonly' | 'showHeading' | 'onDelete'>,
251
+ 'type'
252
+ > & {
253
+ type: Type.AnyEntity;
254
+ };
255
+
256
+ const FieldList = ({ type, view, registry, readonly, showHeading = false, onDelete }: FieldListProps) => {
257
+ const atomRegistry = useContext(RegistryContext);
258
+ const schemaReadonly = Type.getDatabase(type) == null;
259
+ const { t } = useTranslation(translationKey);
260
+
261
+ // Subscribe to view changes for reactivity.
262
+ const [viewSnapshot] = useObject(view);
263
+
264
+ const projectionModel = useMemo(() => {
265
+ const jsonSchema = type.jsonSchema;
266
+
267
+ // Always use createEchoChangeCallback since the view is ECHO-backed.
268
+ // Pass type only when mutable to allow schema mutations.
269
+ const change = createEchoChangeCallback(view, Type.getDatabase(type) != null ? type : undefined);
270
+
271
+ const model = new ProjectionModel({
272
+ registry: atomRegistry,
273
+ view,
274
+ baseSchema: jsonSchema,
275
+ change,
276
+ });
277
+
278
+ return model;
279
+ }, [atomRegistry, type, view]);
280
+
281
+ const [expandedField, setExpandedField] = useState<View.FieldType['id']>();
282
+
283
+ const handleAdd = useCallback(() => {
284
+ invariant(!readonly);
285
+ const field = projectionModel.createFieldProjection();
286
+ setExpandedField(field.id);
287
+ }, [projectionModel, readonly]);
288
+
289
+ const handleToggleField = useCallback(
290
+ (field: View.FieldType) => {
291
+ setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
292
+ },
293
+ [readonly],
294
+ );
295
+
296
+ const handleDelete = useCallback(
297
+ (fieldId: string) => {
298
+ invariant(!readonly);
299
+ if (fieldId === expandedField) {
300
+ setExpandedField(undefined);
301
+ }
302
+
303
+ onDelete?.(fieldId);
304
+ },
305
+ [expandedField, onDelete, readonly],
306
+ );
307
+
308
+ const handleMove = useCallback(
309
+ (fromIndex: number, toIndex: number) => {
310
+ invariant(!readonly);
311
+ Obj.update(view, (view) => {
170
312
  // NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
171
313
  const fields = [...view.projection.fields];
172
314
  const [moved] = fields.splice(fromIndex, 1);
173
315
  fields.splice(toIndex, 0, moved);
174
316
  view.projection.fields = fields;
175
- },
176
- [view.projection.fields, readonly],
177
- );
178
-
179
- const handleClose = useCallback(() => setExpandedField(undefined), []);
317
+ });
318
+ },
319
+ [view, readonly],
320
+ );
180
321
 
181
- const handleHide = useCallback(
182
- (fieldId: string) => {
183
- setExpandedField(undefined);
184
- projectionModel.hideFieldProjection(fieldId);
185
- },
186
- [projectionModel],
187
- );
322
+ const handleClose = useCallback(() => setExpandedField(undefined), []);
188
323
 
189
- const handleShow = useCallback(
190
- (property: string) => {
191
- setExpandedField(undefined);
192
- projectionModel.showFieldProjection(property as JsonProp);
193
- },
194
- [projectionModel],
195
- );
324
+ const handleHide = useCallback(
325
+ (fieldId: string) => {
326
+ setExpandedField(undefined);
327
+ projectionModel.hideFieldProjection(fieldId);
328
+ },
329
+ [projectionModel],
330
+ );
196
331
 
197
- const custom = useMemo(() => (mode === 'query' ? customFields({ types, tags }) : undefined), [types, tags, mode]);
332
+ const handleShow = useCallback(
333
+ (property: string) => {
334
+ setExpandedField(undefined);
335
+ projectionModel.showFieldProjection(property as SchemaEx.JsonProp);
336
+ },
337
+ [projectionModel],
338
+ );
198
339
 
199
- return (
200
- <div role='none' className={mx(classNames)}>
201
- {schemaReadonly && mode === 'schema' && (
202
- <Callout.Root valence='info' classNames={['mlb-cardSpacingBlock', outerSpacing && 'mli-cardSpacingInline']}>
203
- <Callout.Title>{t('system schema description')}</Callout.Title>
204
- </Callout.Root>
205
- )}
340
+ if (!viewSnapshot) {
341
+ return null;
342
+ }
206
343
 
207
- {/* TODO(burdon): Is the form read-only or just the schema? */}
208
- {/* TODO(burdon): Readonly fields should take up the same space as editable fields (just be ghosted). */}
209
- <Form<Schema.Schema.Type<typeof viewSchema>>
210
- autoSave
211
- schema={viewSchema}
212
- values={viewValues}
213
- readonly={readonly ? 'disabled-input' : false}
214
- onSave={handleUpdate}
215
- outerSpacing={outerSpacing}
216
- Custom={custom}
217
- />
218
-
219
- <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
220
- <h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
221
-
222
- <List.Root<FieldType>
223
- items={view.projection.fields}
224
- isItem={Schema.is(FieldSchema)}
225
- getId={(field) => field.id}
226
- onMove={readonly ? undefined : handleMove}
227
- readonly={readonly}
228
- >
229
- {({ items: fields }) => (
230
- <>
231
- {showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
232
- <div role='list' className={listGrid}>
233
- {fields?.map((field) => {
234
- const hidden = field.visible === false;
235
- return (
236
- <List.Item<FieldType>
237
- key={field.id}
238
- item={field}
239
- classNames={listItemGrid}
240
- aria-expanded={expandedField === field.id}
241
- >
242
- <div
243
- role='none'
244
- className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
245
- >
246
- <List.ItemDragHandle disabled={readonly} />
247
- <List.ItemTitle
248
- classNames={hidden && 'text-subdued'}
249
- onClick={() => handleToggleField(field)}
250
- >
251
- {field.path}
252
- </List.ItemTitle>
253
- <List.ItemButton
254
- label={t(hidden ? 'show field label' : 'hide field label')}
255
- data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
256
- icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
257
- autoHide={false}
258
- disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
259
- onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
260
- />
261
- {mode === 'schema' && (
262
- <>
263
- <List.ItemDeleteButton
264
- label={t('delete field label')}
265
- autoHide={false}
266
- disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
267
- onClick={() => handleDelete(field.id)}
268
- data-testid='field.delete'
269
- />
270
- <IconButton
271
- iconOnly
272
- variant='ghost'
273
- label={t('toggle expand label', { ns: 'os' })}
274
- icon={
275
- expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'
276
- }
277
- onClick={() => handleToggleField(field)}
278
- />
279
- </>
280
- )}
281
- </div>
282
- {expandedField === field.id && mode === 'schema' && (
283
- <div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
284
- <FieldEditor
285
- readonly={readonly || schemaReadonly ? 'disabled-input' : false}
286
- projection={projectionModel}
287
- field={field}
288
- registry={registry}
289
- onSave={handleClose}
290
- />
291
- </div>
292
- )}
293
- </List.Item>
294
- );
295
- })}
296
- </div>
297
- </>
298
- )}
299
- </List.Root>
300
- </div>
301
-
302
- {!readonly && !expandedField && mode === 'schema' && (
303
- <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
304
- <IconButton
305
- icon='ph--plus--regular'
306
- label={t('add property button')}
307
- onClick={readonly ? undefined : handleAdd}
308
- // TODO(burdon): Show field limit in ux (not tooltip).
309
- disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
310
- classNames='is-full'
311
- />
344
+ return (
345
+ <List.Root<View.FieldType>
346
+ items={viewSnapshot.projection.fields as View.FieldType[]}
347
+ isItem={Schema.is(View.FieldSchema)}
348
+ getId={(field) => field.id}
349
+ onMove={readonly ? undefined : handleMove}
350
+ readonly={readonly}
351
+ >
352
+ {({ items: fields }) => (
353
+ <>
354
+ {showHeading && <h3 className='text-sm'>{t('field-path.label')}</h3>}
355
+ <div role='list' className='grid grid-cols-[min-content_1fr_min-content_min-content_min-content]'>
356
+ {fields?.map((field) => {
357
+ const hidden = field.visible === false;
358
+ return (
359
+ <List.Item<View.FieldType>
360
+ key={field.id}
361
+ item={field}
362
+ classNames={'grid grid-cols-subgrid col-span-5'}
363
+ aria-expanded={expandedField === field.id}
364
+ >
365
+ <div className='grid grid-cols-subgrid col-span-5 rounded-xs cursor-pointer min-h-10 dx-hover'>
366
+ <List.ItemDragHandle disabled={readonly || schemaReadonly} />
367
+ <List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
368
+ {field.path}
369
+ </List.ItemTitle>
370
+ <List.ItemIconButton
371
+ label={t(hidden ? 'show-field.label' : 'hide-field.label')}
372
+ data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
373
+ icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
374
+ autoHide={false}
375
+ disabled={readonly || (!hidden && projectionModel.getFields().length <= 1)}
376
+ onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
377
+ />
378
+ {!readonly && (
379
+ <>
380
+ <List.ItemDeleteButton
381
+ label={t('delete-field.label')}
382
+ autoHide={false}
383
+ disabled={readonly || schemaReadonly || viewSnapshot.projection.fields.length <= 1}
384
+ onClick={() => handleDelete(field.id)}
385
+ data-testid='field.delete'
386
+ />
387
+ <List.ItemIconButton
388
+ iconOnly
389
+ variant='ghost'
390
+ label={t('toggle-expand.label', { ns: osTranslations })}
391
+ icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
392
+ onClick={() => handleToggleField(field)}
393
+ data-testid='field.toggle'
394
+ />
395
+ </>
396
+ )}
397
+ </div>
398
+ {expandedField === field.id && !readonly && (
399
+ <div className='col-span-full mt-1 mb-1 border border-separator rounded-md'>
400
+ <FieldEditor
401
+ readonly={readonly || schemaReadonly}
402
+ registry={registry}
403
+ projection={projectionModel}
404
+ field={field}
405
+ onSave={handleClose}
406
+ />
407
+ </div>
408
+ )}
409
+ </List.Item>
410
+ );
411
+ })}
312
412
  </div>
313
- )}
314
- </div>
315
- );
316
- },
317
- );
413
+ {!readonly && !expandedField && (
414
+ <div className='my-form-padding'>
415
+ <IconButton
416
+ icon='ph--plus--regular'
417
+ label={t('add-property-button.label')}
418
+ onClick={handleAdd}
419
+ disabled={viewSnapshot.projection.fields.length >= VIEW_FIELD_LIMIT}
420
+ classNames='w-full'
421
+ />
422
+ </div>
423
+ )}
424
+ </>
425
+ )}
426
+ </List.Root>
427
+ );
428
+ };
318
429
 
319
- const customFields = ({ types, tags }: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, InputComponent> => ({
320
- query: (props: InputProps) => {
321
- const handleChange = useCallback(
322
- (query: Query.Any) => props.onValueChange('object', query.ast),
323
- [props.onValueChange],
430
+ const customFields = ({
431
+ types,
432
+ tags,
433
+ }: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, FormFieldComponent> => ({
434
+ query: ({ type, readonly, label, getValue, onValueChange }: FormFieldComponentProps) => {
435
+ const handleChange = useCallback<NonNullable<QueryFormProps['onChange']>>(
436
+ (query) => onValueChange(type, query.ast),
437
+ [onValueChange],
324
438
  );
325
439
 
326
440
  return (
327
441
  <Input.Root>
328
- <InputHeader label={props.label} />
329
- <QueryForm initialQuery={props.getValue()} types={types} tags={tags} onChange={handleChange} />
442
+ <FormFieldLabel readonly={readonly} label={label} />
443
+ <QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
330
444
  </Input.Root>
331
445
  );
332
446
  },