@dxos/react-ui-form 0.8.4-main.406dc2a → 0.8.4-main.4668b7de9b

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