@dxos/react-ui-form 0.8.4-main.e8ec1fe → 0.8.4-main.effb148878

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