@dxos/react-ui-form 0.8.4-main.f5c0578 → 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 +2774 -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 +2774 -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 +8 -8
  13. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +84 -5
  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 +86 -8
  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 -55
  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 +56 -46
  120. package/src/components/FieldEditor/FieldEditor.stories.tsx +49 -31
  121. package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
  122. package/src/components/FieldEditor/FieldEditor.tsx +86 -91
  123. package/src/components/Form/AUDIT.md +17 -0
  124. package/src/components/Form/Form.stories.tsx +147 -224
  125. package/src/components/Form/Form.tsx +376 -96
  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} +37 -31
  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 +140 -63
  163. package/src/components/ViewEditor/ViewEditor.test.tsx +12 -15
  164. package/src/components/ViewEditor/ViewEditor.tsx +348 -212
  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 -59
  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 -42
  227. package/src/components/ControlSection/ControlSection.tsx +0 -139
  228. package/src/components/Form/ArrayField.tsx +0 -125
  229. package/src/components/Form/Defaults.tsx +0 -204
  230. package/src/components/Form/FormActions.tsx +0 -54
  231. package/src/components/Form/FormContent.tsx +0 -241
  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 -214
  235. package/src/components/Form/custom/GeoPointInput.tsx +0 -72
  236. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +0 -62
  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 -63
  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 -293
  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,102 +2,262 @@
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 { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
15
- import { type DataType, FieldSchema, type FieldType, ProjectionModel, 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 listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
22
- const listItemGrid = 'grid grid-cols-subgrid col-span-5';
23
-
24
- const ViewMetaSchema = Schema.Struct({
25
- typename: Format.URL.annotations({
26
- title: 'Record type',
27
- }),
28
- }).pipe(Schema.mutable);
29
+ import { translationKey } from '#translations';
29
30
 
30
- 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';
31
40
 
32
41
  export type ViewEditorProps = ThemedClassName<
33
42
  {
34
43
  schema: Schema.Schema.AnyNoContext;
35
- view: DataType.View;
36
- registry?: SchemaRegistry;
37
- readonly?: boolean;
44
+ view: View.View;
45
+ mode?: 'schema' | 'tag';
46
+ registry?: SchemaRegistry.SchemaRegistry;
38
47
  showHeading?: boolean;
39
- onTypenameChanged?: (typename: string) => void;
48
+ onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
40
49
  onDelete?: (fieldId: string) => void;
41
- } & Pick<FormProps<any>, 'outerSpacing'>
50
+ } & Pick<QueryFormProps, 'types' | 'tags'> &
51
+ Pick<FormRootProps<any>, 'readonly' | 'db'>
42
52
  >;
43
53
 
44
54
  /**
45
55
  * Schema-based object form.
46
56
  */
47
- export const ViewEditor = ({
48
- classNames,
49
- schema,
50
- view,
51
- registry,
52
- readonly: _readonly,
53
- showHeading = false,
54
- onTypenameChanged,
55
- onDelete,
56
- outerSpacing = true,
57
- }: 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);
58
225
  const { t } = useTranslation(translationKey);
59
- const projection = useMemo(() => {
226
+
227
+ // Subscribe to view changes for reactivity.
228
+ const [viewSnapshot] = useObject(view);
229
+
230
+ const projectionModel = useMemo(() => {
60
231
  // Use reactive and mutable version of json schema when schema is mutable.
61
232
  const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
62
- return new ProjectionModel(jsonSchema, view.projection);
63
- }, [schema, view.projection]);
64
- const [expandedField, setExpandedField] = useState<FieldType['id']>();
65
- const readonly = _readonly || !isMutable(schema);
66
-
67
- // TODO(burdon): Should be reactive.
68
- const viewValues = useMemo(() => {
69
- return {
70
- // TODO(burdon): Need to warn user of possible consequences of editing.
71
- // TODO(burdon): Settings should have domain name owned by user.
72
- typename: view.query.typename,
73
- };
74
- }, [view.query.typename]);
75
233
 
76
- const handleToggleField = useCallback(
77
- (field: FieldType) => {
78
- setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
79
- },
80
- [readonly],
81
- );
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]);
82
247
 
83
- // TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
248
+ const [expandedField, setExpandedField] = useState<View.FieldType['id']>();
84
249
 
85
250
  const handleAdd = useCallback(() => {
86
251
  invariant(!readonly);
87
- const field = projection.createFieldProjection();
252
+ const field = projectionModel.createFieldProjection();
88
253
  setExpandedField(field.id);
89
- }, [schema, projection, readonly]);
254
+ }, [projectionModel, readonly]);
90
255
 
91
- const handleUpdate = useCallback(
92
- ({ typename }: ViewMetaType) => {
93
- invariant(!readonly);
94
- requestAnimationFrame(() => {
95
- if (view.query.typename !== typename && !readonly) {
96
- onTypenameChanged?.(typename);
97
- }
98
- });
256
+ const handleToggleField = useCallback(
257
+ (field: View.FieldType) => {
258
+ setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
99
259
  },
100
- [view.query.typename, onTypenameChanged, readonly],
260
+ [readonly],
101
261
  );
102
262
 
103
263
  const handleDelete = useCallback(
@@ -115,171 +275,147 @@ export const ViewEditor = ({
115
275
  const handleMove = useCallback(
116
276
  (fromIndex: number, toIndex: number) => {
117
277
  invariant(!readonly);
118
- // NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
119
- const fields = [...view.projection.fields];
120
- const [moved] = fields.splice(fromIndex, 1);
121
- fields.splice(toIndex, 0, moved);
122
- view.projection.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
+ });
123
285
  },
124
- [view.projection.fields, readonly],
286
+ [view, readonly],
125
287
  );
126
288
 
127
289
  const handleClose = useCallback(() => setExpandedField(undefined), []);
128
290
 
129
- const hiddenProperties = projection.getHiddenProperties();
130
-
131
291
  const handleHide = useCallback(
132
292
  (fieldId: string) => {
133
293
  setExpandedField(undefined);
134
- projection.hideFieldProjection(fieldId);
294
+ projectionModel.hideFieldProjection(fieldId);
135
295
  },
136
- [projection],
296
+ [projectionModel],
137
297
  );
138
298
 
139
299
  const handleShow = useCallback(
140
300
  (property: string) => {
141
301
  setExpandedField(undefined);
142
- projection.showFieldProjection(property as JsonProp);
302
+ projectionModel.showFieldProjection(property as JsonProp);
143
303
  },
144
- [projection],
304
+ [projectionModel],
145
305
  );
146
306
 
147
- return (
148
- <div role='none' className={mx(classNames)}>
149
- {readonly && (
150
- <Callout.Root
151
- valence='info'
152
- classNames={['is-full mlb-cardSpacingBlock', outerSpacing && 'mli-cardSpacingInline']}
153
- >
154
- <Callout.Title>{t('system schema description')}</Callout.Title>
155
- </Callout.Root>
156
- )}
307
+ if (!viewSnapshot) {
308
+ return null;
309
+ }
157
310
 
158
- {/* TODO(burdon): Is the form read-only or just the schema? */}
159
- {/* TODO(burdon): Readonly fields should take up the same space as editable fields (just be ghosted). */}
160
- <Form<ViewMetaType>
161
- autoSave
162
- schema={ViewMetaSchema}
163
- values={viewValues}
164
- readonly={readonly}
165
- onSave={handleUpdate}
166
- outerSpacing={outerSpacing}
167
- />
168
-
169
- <div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
170
- <h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
171
-
172
- <List.Root<FieldType>
173
- items={view.projection.fields}
174
- isItem={Schema.is(FieldSchema)}
175
- getId={(field) => field.id}
176
- onMove={readonly ? undefined : handleMove}
177
- readonly={readonly}
178
- >
179
- {({ items: fields }) => (
180
- <>
181
- {showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
182
-
183
- <div role='list' className={listGrid}>
184
- {fields?.map((field) => (
185
- <List.Item<FieldType>
186
- key={field.id}
187
- item={field}
188
- classNames={listItemGrid}
189
- aria-expanded={expandedField === field.id}
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
+ )}
190
339
  >
191
- <div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
192
- <List.ItemDragHandle disabled={readonly} />
193
- <List.ItemTitle onClick={() => handleToggleField(field)}>{field.path}</List.ItemTitle>
194
- <List.ItemButton
195
- label={t('hide field label')}
196
- data-testid='hide-field-button'
197
- icon='ph--eye-slash--regular'
198
- autoHide={false}
199
- // TDOO(burdon): Is this the correct test?
200
- disabled={readonly || view.projection.fields.length <= 1}
201
- onClick={() => handleHide(field.id)}
202
- />
203
- {/* TODO(burdon): Remove unless implement undo. */}
204
- <List.ItemDeleteButton
205
- label={t('delete field label')}
206
- autoHide={false}
207
- disabled={readonly || view.projection.fields.length <= 1}
208
- onClick={() => handleDelete(field.id)}
209
- data-testid='field.delete'
210
- />
211
- <IconButton
212
- iconOnly
213
- variant='ghost'
214
- label={t('toggle expand label', { ns: 'os' })}
215
- icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
216
- onClick={() => handleToggleField(field)}
217
- />
218
- </div>
219
- {expandedField === field.id && (
220
- <div role='none' className='col-span-5'>
221
- <FieldEditor
222
- readonly={readonly}
223
- projection={projection}
224
- field={field}
225
- registry={registry}
226
- onSave={handleClose}
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))}
351
+ />
352
+ {!readonly && (
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'
227
360
  />
228
- </div>
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
+ </>
229
370
  )}
230
- </List.Item>
231
- ))}
232
- </div>
233
- </>
234
- )}
235
- </List.Root>
236
-
237
- {hiddenProperties.length > 0 && (
238
- <>
239
- <h2 className={mx(inputTextLabel)}>{t('hidden fields label')}</h2>
240
-
241
- <List.Root<string>
242
- items={hiddenProperties}
243
- isItem={(item): item is string => typeof item === 'string'}
244
- getId={(property) => property}
245
- >
246
- {({ items: properties }) => (
247
- <div role='list' className='grid grid-cols-[1fr_min-content]'>
248
- {properties?.map((property) => (
249
- <List.Item<string>
250
- key={property}
251
- item={property}
252
- classNames={mx('grid grid-cols-subgrid col-span-2 rounded-sm', subtleHover)}
253
- >
254
- <List.ItemTitle>{property}</List.ItemTitle>
255
- <List.ItemButton
256
- label={t('show field label')}
257
- disabled={readonly}
258
- icon='ph--eye--regular'
259
- onClick={() => handleShow(property)}
260
- 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}
261
380
  />
262
- </List.Item>
263
- ))}
264
- </div>
265
- )}
266
- </List.Root>
267
- </>
268
- )}
269
- </div>
270
-
271
- {!readonly && !expandedField && (
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.projection.fields.length >= VIEW_FIELD_LIMIT}
279
- classNames='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';