@dxos/react-ui-form 0.8.4-main.f9ba587 → 0.8.4-main.fcc0d83b33

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