@dxos/react-ui-form 0.8.4-main.ead640a → 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 +3252 -2315
  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 +3252 -2315
  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 +123 -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 +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 -59
  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 +16 -20
  110. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  111. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +72 -66
  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 -60
  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 -51
  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 -107
  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} +31 -24
  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 +30 -25
  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 +65 -73
  196. package/src/components/ViewEditor/ViewEditor.test.tsx +11 -11
  197. package/src/components/ViewEditor/ViewEditor.tsx +308 -214
  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 -65
  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 +16 -18
  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 -84
  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 -79
  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 -322
  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
- type DataType,
22
- FieldSchema,
23
- type FieldType,
33
+ ParentLabelAnnotation,
24
34
  ProjectionModel,
25
35
  VIEW_FIELD_LIMIT,
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;
39
- view: DataType.View;
40
- mode?: 'schema' | 'query';
41
- registry?: SchemaRegistry;
42
- readonly?: boolean;
55
+ type: Type.AnyEntity;
56
+ view: View.View;
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,11 +139,15 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
96
139
  : QueryAST.Query.annotations({ title: 'Query' }),
97
140
  });
98
141
 
99
- if (mode === 'query') {
142
+ if (mode === 'tag') {
100
143
  return Schema.Struct({
101
- name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
102
144
  ...base.fields,
103
- target: Schema.optional(Schema.String.annotations({ title: 'Target Queue' })),
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
+ ),
104
151
  }).pipe(Schema.mutable);
105
152
  }
106
153
 
@@ -111,219 +158,266 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
111
158
  // TODO(burdon): Settings should have domain name owned by user.
112
159
  const viewValues = useMemo(
113
160
  () => ({
114
- name: view.name,
115
161
  query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
116
- target: queueTarget,
162
+ target: targetRef,
117
163
  }),
118
- [view.name, mode, view.query.ast, queueTarget],
164
+ [mode, view.query.ast, targetRef],
119
165
  );
120
166
 
121
- const handleToggleField = useCallback(
122
- (field: FieldType) => {
123
- setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
124
- },
125
- [readonly],
167
+ const fieldMap = useMemo<FormFieldMap | undefined>(
168
+ () => (mode === 'tag' ? customFields({ types, tags }) : undefined),
169
+ [mode, types, tags],
126
170
  );
127
171
 
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
172
  const handleUpdate = useCallback(
137
173
  (values: any) => {
138
- invariant(!readonly);
139
- requestAnimationFrame(() => {
140
- if ('name' in values && view.name !== values.name) {
141
- view.name = values.name;
142
- 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);
143
182
  }
183
+ }
144
184
 
145
- const query = mode === 'schema' ? Query.select(Filter.typename(values.query)).ast : values.query;
146
- onQueryChanged?.(query, values.target);
147
- });
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);
148
191
  },
149
- [onQueryChanged, readonly, view, queueTarget, mode],
192
+ [onQueryChanged, mode, feeds],
150
193
  );
151
194
 
152
195
  const handleDelete = useCallback(
153
196
  (fieldId: string) => {
154
197
  invariant(!readonly);
155
- if (fieldId === expandedField) {
156
- setExpandedField(undefined);
157
- }
158
-
159
198
  onDelete?.(fieldId);
160
199
  },
161
- [expandedField, onDelete, readonly],
200
+ [onDelete, readonly],
162
201
  );
163
202
 
164
- const handleMove = useCallback(
165
- (fromIndex: number, toIndex: number) => {
166
- 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) => {
167
289
  // NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
168
290
  const fields = [...view.projection.fields];
169
291
  const [moved] = fields.splice(fromIndex, 1);
170
292
  fields.splice(toIndex, 0, moved);
171
293
  view.projection.fields = fields;
172
- },
173
- [view.projection.fields, readonly],
174
- );
175
-
176
- const handleClose = useCallback(() => setExpandedField(undefined), []);
294
+ });
295
+ },
296
+ [view, readonly],
297
+ );
177
298
 
178
- const handleHide = useCallback(
179
- (fieldId: string) => {
180
- setExpandedField(undefined);
181
- projectionModel.hideFieldProjection(fieldId);
182
- },
183
- [projectionModel],
184
- );
299
+ const handleClose = useCallback(() => setExpandedField(undefined), []);
185
300
 
186
- const handleShow = useCallback(
187
- (property: string) => {
188
- setExpandedField(undefined);
189
- projectionModel.showFieldProjection(property as JsonProp);
190
- },
191
- [projectionModel],
192
- );
301
+ const handleHide = useCallback(
302
+ (fieldId: string) => {
303
+ setExpandedField(undefined);
304
+ projectionModel.hideFieldProjection(fieldId);
305
+ },
306
+ [projectionModel],
307
+ );
193
308
 
194
- 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
+ );
195
316
 
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
- )}
317
+ if (!viewSnapshot) {
318
+ return null;
319
+ }
203
320
 
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
- />
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
+ })}
309
389
  </div>
310
- )}
311
- </div>
312
- );
313
- },
314
- );
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
+ };
315
406
 
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],
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],
321
415
  );
322
416
 
323
417
  return (
324
418
  <Input.Root>
325
- <InputHeader label={props.label} />
326
- <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} />
327
421
  </Input.Root>
328
422
  );
329
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';