@dxos/react-ui-form 0.8.4-main.74a063c4e0 → 0.8.4-main.765dc60934

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 (134) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/index.mjs +873 -446
  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 +873 -446
  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.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +63 -61
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.d.ts +34 -12
  16. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/Form.stories.d.ts +73 -71
  18. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/FormField.d.ts +5 -5
  20. package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/FormFieldComponent.d.ts +8 -6
  22. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
  24. package/dist/types/src/components/Form/FormFieldSet.d.ts +5 -8
  25. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
  26. package/dist/types/src/components/Form/fields/ArrayField.d.ts +4 -4
  27. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
  28. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -1
  29. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
  30. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +8 -1
  31. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -1
  32. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +84 -0
  33. package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -0
  34. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -1
  35. package/dist/types/src/components/Form/fields/RefField.d.ts +10 -4
  36. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
  37. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -1
  38. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
  39. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +63 -61
  40. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
  41. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -1
  42. package/dist/types/src/components/Form/fields/TextField.d.ts +1 -1
  43. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -1
  44. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
  45. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +9 -0
  46. package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
  47. package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
  48. package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
  49. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +17 -3
  50. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
  51. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +63 -61
  52. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
  53. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
  54. package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
  55. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +127 -0
  56. package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
  58. package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
  59. package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
  60. package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
  61. package/dist/types/src/components/Settings/Settings.d.ts +24 -12
  62. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
  63. package/dist/types/src/components/Settings/Settings.stories.d.ts +64 -61
  64. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
  65. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
  66. package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
  67. package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
  68. package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
  69. package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
  70. package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +1 -0
  71. package/dist/types/src/components/Settings/index.d.ts +2 -0
  72. package/dist/types/src/components/Settings/index.d.ts.map +1 -1
  73. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +3 -4
  74. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  75. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +63 -61
  76. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  77. package/dist/types/src/components/index.d.ts +2 -0
  78. package/dist/types/src/components/index.d.ts.map +1 -1
  79. package/dist/types/src/components/testing.d.ts +4 -4
  80. package/dist/types/src/components/testing.d.ts.map +1 -1
  81. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
  82. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -1
  83. package/dist/types/src/index.d.ts +0 -2
  84. package/dist/types/src/index.d.ts.map +1 -1
  85. package/dist/types/src/translations.d.ts +63 -61
  86. package/dist/types/src/translations.d.ts.map +1 -1
  87. package/dist/types/src/util/field-type.d.ts +19 -0
  88. package/dist/types/src/util/field-type.d.ts.map +1 -0
  89. package/dist/types/src/util/field.d.ts.map +1 -1
  90. package/dist/types/src/util/formatting.d.ts.map +1 -1
  91. package/dist/types/src/util/index.d.ts +1 -0
  92. package/dist/types/src/util/index.d.ts.map +1 -1
  93. package/dist/types/src/util/properties.d.ts.map +1 -1
  94. package/dist/types/src/util/refs.d.ts.map +1 -1
  95. package/dist/types/tsconfig.tsbuildinfo +1 -1
  96. package/package.json +40 -38
  97. package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -1
  98. package/src/components/FieldEditor/FieldEditor.tsx +3 -2
  99. package/src/components/Form/Form.stories.tsx +9 -19
  100. package/src/components/Form/Form.tsx +59 -7
  101. package/src/components/Form/FormField.tsx +12 -10
  102. package/src/components/Form/FormFieldComponent.tsx +6 -3
  103. package/src/components/Form/FormFieldSet.tsx +47 -31
  104. package/src/components/Form/fields/ArrayField.tsx +7 -6
  105. package/src/components/Form/fields/GeoPointField.tsx +3 -2
  106. package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
  107. package/src/components/Form/fields/MarkdownField.tsx +135 -45
  108. package/src/components/Form/fields/RefField.tsx +61 -30
  109. package/src/components/Form/fields/SelectOptionField.stories.tsx +8 -5
  110. package/src/components/Form/fields/SelectOptionField.tsx +6 -5
  111. package/src/components/Form/fields/TextField.tsx +2 -2
  112. package/src/components/ObjectForm/ObjectForm.tsx +114 -0
  113. package/src/components/ObjectForm/index.ts +5 -0
  114. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +9 -6
  115. package/src/components/ObjectPicker/ObjectPicker.tsx +28 -12
  116. package/src/components/ObjectProperties/ObjectProperties.stories.tsx +362 -0
  117. package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
  118. package/src/components/ObjectProperties/ObjectProperties.tsx +141 -0
  119. package/src/components/ObjectProperties/index.ts +5 -0
  120. package/src/components/Settings/AUDIT.md +52 -0
  121. package/src/components/Settings/Settings.stories.tsx +50 -1
  122. package/src/components/Settings/Settings.tsx +7 -56
  123. package/src/components/Settings/SettingsFieldSet.tsx +257 -0
  124. package/src/components/Settings/SettingsItem.tsx +58 -0
  125. package/src/components/Settings/SettingsSelectField.tsx +59 -0
  126. package/src/components/Settings/index.ts +2 -0
  127. package/src/components/ViewEditor/ViewEditor.stories.tsx +10 -10
  128. package/src/components/ViewEditor/ViewEditor.tsx +19 -26
  129. package/src/components/index.ts +2 -0
  130. package/src/components/testing.tsx +20 -21
  131. package/src/index.ts +0 -4
  132. package/src/translations.ts +7 -3
  133. package/src/util/field-type.ts +60 -0
  134. package/src/util/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-form",
3
- "version": "0.8.4-main.74a063c4e0",
3
+ "version": "0.8.4-main.765dc60934",
4
4
  "description": "Data components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,56 +8,58 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "DXOS.org",
13
13
  "type": "module",
14
+ "imports": {
15
+ "#translations": "./src/translations.ts"
16
+ },
14
17
  "exports": {
15
18
  ".": {
16
19
  "source": "./src/index.ts",
17
20
  "types": "./dist/types/src/index.d.ts",
18
21
  "browser": "./dist/lib/browser/index.mjs",
19
22
  "node": "./dist/lib/node-esm/index.mjs"
23
+ },
24
+ "./translations": {
25
+ "source": "./src/translations.ts",
26
+ "types": "./dist/types/src/translations.d.ts",
27
+ "browser": "./dist/lib/browser/translations.mjs",
28
+ "node": "./dist/lib/node-esm/translations.mjs"
20
29
  }
21
30
  },
22
31
  "types": "dist/types/src/index.d.ts",
23
- "typesVersions": {
24
- "*": {}
25
- },
26
32
  "files": [
27
33
  "dist",
28
34
  "src"
29
35
  ],
30
36
  "dependencies": {
31
- "@atlaskit/pragmatic-drag-and-drop": "1.7.7",
32
- "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
33
37
  "@effect-atom/atom-react": "^0.5.0",
34
38
  "@fluentui/react-tabster": "9.26.11",
35
39
  "@radix-ui/react-context": "1.1.1",
36
40
  "@radix-ui/react-use-controllable-state": "1.1.0",
37
41
  "date-fns": "^3.6.0",
38
42
  "tabster": "^8.5.5",
39
- "@dxos/debug": "0.8.4-main.74a063c4e0",
40
- "@dxos/echo": "0.8.4-main.74a063c4e0",
41
- "@dxos/async": "0.8.4-main.74a063c4e0",
42
- "@dxos/echo-query": "0.8.4-main.74a063c4e0",
43
- "@dxos/invariant": "0.8.4-main.74a063c4e0",
44
- "@dxos/echo-react": "0.8.4-main.74a063c4e0",
45
- "@dxos/keys": "0.8.4-main.74a063c4e0",
46
- "@dxos/effect": "0.8.4-main.74a063c4e0",
47
- "@dxos/lit-ui": "0.8.4-main.74a063c4e0",
48
- "@dxos/log": "0.8.4-main.74a063c4e0",
49
- "@dxos/react-client": "0.8.4-main.74a063c4e0",
50
- "@dxos/react-ui-editor": "0.8.4-main.74a063c4e0",
51
- "@dxos/react-ui-list": "0.8.4-main.74a063c4e0",
52
- "@dxos/react-ui-mosaic": "0.8.4-main.74a063c4e0",
53
- "@dxos/react-ui-components": "0.8.4-main.74a063c4e0",
54
- "@dxos/react-ui-pickers": "0.8.4-main.74a063c4e0",
55
- "@dxos/react-ui-search": "0.8.4-main.74a063c4e0",
56
- "@dxos/schema": "0.8.4-main.74a063c4e0",
57
- "@dxos/types": "0.8.4-main.74a063c4e0",
58
- "@dxos/react-ui-stack": "0.8.4-main.74a063c4e0",
59
- "@dxos/ui-types": "0.8.4-main.74a063c4e0",
60
- "@dxos/util": "0.8.4-main.74a063c4e0"
43
+ "@dxos/async": "0.8.4-main.765dc60934",
44
+ "@dxos/echo": "0.8.4-main.765dc60934",
45
+ "@dxos/echo-db": "0.8.4-main.765dc60934",
46
+ "@dxos/echo-react": "0.8.4-main.765dc60934",
47
+ "@dxos/effect": "0.8.4-main.765dc60934",
48
+ "@dxos/lit-ui": "0.8.4-main.765dc60934",
49
+ "@dxos/keys": "0.8.4-main.765dc60934",
50
+ "@dxos/invariant": "0.8.4-main.765dc60934",
51
+ "@dxos/log": "0.8.4-main.765dc60934",
52
+ "@dxos/react-client": "0.8.4-main.765dc60934",
53
+ "@dxos/react-ui-components": "0.8.4-main.765dc60934",
54
+ "@dxos/react-ui-editor": "0.8.4-main.765dc60934",
55
+ "@dxos/react-ui-list": "0.8.4-main.765dc60934",
56
+ "@dxos/react-ui-search": "0.8.4-main.765dc60934",
57
+ "@dxos/react-ui-pickers": "0.8.4-main.765dc60934",
58
+ "@dxos/schema": "0.8.4-main.765dc60934",
59
+ "@dxos/types": "0.8.4-main.765dc60934",
60
+ "@dxos/util": "0.8.4-main.765dc60934",
61
+ "@dxos/ui-editor": "0.8.4-main.765dc60934",
62
+ "@dxos/echo-atom": "0.8.4-main.765dc60934"
61
63
  },
62
64
  "devDependencies": {
63
65
  "@types/react": "~19.2.7",
@@ -66,20 +68,20 @@
66
68
  "fast-check": "^3.19.0",
67
69
  "react": "~19.2.3",
68
70
  "react-dom": "~19.2.3",
69
- "vite": "^7.1.11",
70
- "@dxos/random": "0.8.4-main.74a063c4e0",
71
- "@dxos/react-ui": "0.8.4-main.74a063c4e0",
72
- "@dxos/ui-theme": "0.8.4-main.74a063c4e0",
73
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.74a063c4e0",
74
- "@dxos/storybook-utils": "0.8.4-main.74a063c4e0",
75
- "@dxos/react-client": "0.8.4-main.74a063c4e0"
71
+ "vite": "^8.0.13",
72
+ "@dxos/random": "0.8.4-main.765dc60934",
73
+ "@dxos/react-client": "0.8.4-main.765dc60934",
74
+ "@dxos/react-ui": "0.8.4-main.765dc60934",
75
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.765dc60934",
76
+ "@dxos/storybook-utils": "0.8.4-main.765dc60934",
77
+ "@dxos/ui-theme": "0.8.4-main.765dc60934"
76
78
  },
77
79
  "peerDependencies": {
78
80
  "effect": "3.20.0",
79
81
  "react": "~19.2.3",
80
82
  "react-dom": "~19.2.3",
81
- "@dxos/react-ui": "0.8.4-main.74a063c4e0",
82
- "@dxos/ui-theme": "0.8.4-main.74a063c4e0"
83
+ "@dxos/react-ui": "0.8.4-main.765dc60934",
84
+ "@dxos/ui-theme": "0.8.4-main.765dc60934"
83
85
  },
84
86
  "publishConfig": {
85
87
  "access": "public"
@@ -12,7 +12,8 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import { ProjectionModel, ViewModel, createEchoChangeCallback } from '@dxos/schema';
13
13
  import { Example } from '@dxos/schema/testing';
14
14
 
15
- import { translations } from '../../translations';
15
+ import { translations } from '#translations';
16
+
16
17
  import { FIELD_EDITOR_DEBUG_SYMBOL, TestLayout } from '../testing';
17
18
  import { FieldEditor, type FieldEditorProps } from './FieldEditor';
18
19
 
@@ -16,7 +16,8 @@ import {
16
16
  getFormatSchema,
17
17
  } from '@dxos/schema';
18
18
 
19
- import { translationKey } from '../../translations';
19
+ import { translationKey } from '#translations';
20
+
20
21
  import { getFormProperties } from '../../util';
21
22
  import { Form, type FormFieldMap, type FormRootProps, SelectField, SelectOptionField } from '../Form';
22
23
 
@@ -151,7 +152,7 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
151
152
  const handleSave = useCallback<NonNullable<FormRootProps<PropertyType>['onSave']>>(
152
153
  (props) => {
153
154
  if (view) {
154
- Obj.change(view, () => {
155
+ Obj.update(view, () => {
155
156
  projection.setFieldProjection({ field, props });
156
157
  });
157
158
  } else {
@@ -12,9 +12,10 @@ import { log } from '@dxos/log';
12
12
  import { useSpaces } from '@dxos/react-client/echo';
13
13
  import { withClientProvider } from '@dxos/react-client/testing';
14
14
  import { Tooltip } from '@dxos/react-ui';
15
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
16
+
17
+ import { translations } from '#translations';
16
18
 
17
- import { translations } from '../../translations';
18
19
  import { TestLayout } from '../testing';
19
20
  import { type ExcludeId, Form, type FormRootProps, omitId } from './Form';
20
21
 
@@ -42,7 +43,7 @@ const Person = Schema.Struct({
42
43
  title: 'State',
43
44
  description: 'State code',
44
45
  }),
45
- zip: Schema.Number,
46
+ zip: Schema.Number.annotations({ title: 'ZIP Code' }),
46
47
  }).annotations({ title: 'Address' }),
47
48
  ),
48
49
  employer: Schema.optional(Ref.Ref(Organization).annotations({ title: 'Employer' })),
@@ -72,8 +73,8 @@ const Person = Schema.Struct({
72
73
  export interface Person extends Schema.Schema.Type<typeof Person> {}
73
74
 
74
75
  type DefaultStoryProps<T extends AnyProperties> = {
76
+ schema?: Schema.Schema<T>;
75
77
  debug?: boolean;
76
- schema: Schema.Schema<T>;
77
78
  } & FormRootProps<T>;
78
79
 
79
80
  const DefaultStory = <T extends AnyProperties = AnyProperties>({
@@ -97,12 +98,12 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
97
98
  }, []);
98
99
 
99
100
  if (!space) {
100
- return <></>;
101
+ return <Loading />;
101
102
  }
102
103
 
103
104
  return (
104
105
  <Tooltip.Provider>
105
- <TestLayout json={{ values, schema: schema.ast }}>
106
+ <TestLayout json={{ values, schema: schema?.ast }}>
106
107
  <Form.Root
107
108
  debug={debug}
108
109
  schema={schema}
@@ -114,6 +115,7 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
114
115
  >
115
116
  <Form.Viewport>
116
117
  <Form.Content>
118
+ <Form.Section label='Section' description='This is a section' />
117
119
  <Form.FieldSet />
118
120
  <Form.Actions />
119
121
  </Form.Content>
@@ -128,7 +130,6 @@ const meta = {
128
130
  title: 'ui/react-ui-form/Form',
129
131
  component: Form.Root,
130
132
  render: DefaultStory,
131
-
132
133
  decorators: [
133
134
  withTheme(),
134
135
  withLayout({ layout: 'fullscreen' }),
@@ -185,16 +186,5 @@ export const Static: Story<ExcludeId<typeof Person>> = {
185
186
  };
186
187
 
187
188
  export const Empty: Story<ExcludeId<typeof Person>> = {
188
- render: () => (
189
- <TestLayout>
190
- <Form.Root>
191
- <Form.Viewport>
192
- <Form.Content>
193
- <Form.FieldSet />
194
- <Form.Actions />
195
- </Form.Content>
196
- </Form.Viewport>
197
- </Form.Root>
198
- </TestLayout>
199
- ),
189
+ args: {},
200
190
  };
@@ -3,10 +3,12 @@
3
3
  //
4
4
 
5
5
  import { createContext } from '@radix-ui/react-context';
6
+ import * as Option from 'effect/Option';
6
7
  import * as Schema from 'effect/Schema';
7
8
  import * as SchemaAST from 'effect/SchemaAST';
8
9
  import React, { type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
9
10
 
11
+ import { Annotation as EchoAnnotation } from '@dxos/echo';
10
12
  import { type AnyProperties } from '@dxos/echo/internal';
11
13
  import { createJsonPath, getValue as getValue$ } from '@dxos/effect';
12
14
  import {
@@ -17,7 +19,9 @@ import {
17
19
  useMergeRefs,
18
20
  useTranslation,
19
21
  } from '@dxos/react-ui';
20
- import { composable, composableProps, mx } from '@dxos/ui-theme';
22
+ import { composable, composableProps, mx, withColumn } from '@dxos/ui-theme';
23
+
24
+ import { translationKey } from '#translations';
21
25
 
22
26
  import {
23
27
  type FormHandler,
@@ -26,20 +30,35 @@ import {
26
30
  useFormHandler,
27
31
  useKeyHandler,
28
32
  } from '../../hooks';
29
- import { translationKey } from '../../translations';
30
33
  import { FormFieldLabel, type FormFieldLabelProps, type FormFieldStateProps } from './FormFieldComponent';
31
34
  import {
32
35
  FormFieldSet as NaturalFormFieldSet,
33
36
  type FormFieldSetProps as NaturalFormFieldSetProps,
34
37
  } from './FormFieldSet';
35
38
 
36
- // TODO(burdon): Move to @dxos/schema (re-export here).
39
+ // TODO(burdon): Move styles to form.ts (as with ui-theme).
40
+
41
+ // TODO(burdon): Reconcile with @dxos/echo.
37
42
  export type ExcludeId<S extends Schema.Schema.AnyNoContext> = Omit<Schema.Schema.Type<S>, 'id'>;
38
43
 
39
44
  // TODO(burdon): Move to @dxos/schema (re-export here).
40
45
  export const omitId = <S extends Schema.Schema.AnyNoContext>(schema: S): Schema.Schema<ExcludeId<S>, ExcludeId<S>> =>
41
46
  schema.pipe(Schema.omit('id')) as any;
42
47
 
48
+ /**
49
+ * Drop fields annotated with `FormInputAnnotation.set(false)` from a schema so
50
+ * the form's validator doesn't trip on required-but-hidden fields. Used by
51
+ * the picker's inline create form, where a `FactoryAnnotation` typically
52
+ * supplies the hidden values (e.g. a backing-object Ref) outside the form.
53
+ */
54
+ export const omitHiddenFormFields = <S extends Schema.Schema.AnyNoContext>(schema: S): S => {
55
+ const properties = SchemaAST.getPropertySignatures(schema.ast);
56
+ const hidden = properties
57
+ .filter((prop) => Option.getOrElse(EchoAnnotation.FormInputAnnotation.getFromAst(prop.type), () => true) === false)
58
+ .map((prop) => prop.name as string);
59
+ return hidden.length === 0 ? schema : (schema.pipe(Schema.omit(...(hidden as [string, ...string[]]))) as any);
60
+ };
61
+
43
62
  //
44
63
  // Context
45
64
  //
@@ -61,7 +80,7 @@ type FormContextValue<T extends AnyProperties = any> = {
61
80
  testId?: string;
62
81
  } & Pick<
63
82
  NaturalFormFieldSetProps<T>,
64
- 'readonly' | 'layout' | 'fieldMap' | 'fieldProvider' | 'projection' | 'createTypename' | 'createFieldMap'
83
+ 'readonly' | 'layout' | 'fieldMap' | 'fieldProvider' | 'projection' | 'createTypename' | 'createFieldMap' | 'db'
65
84
  >;
66
85
 
67
86
  const [FormContextProvider, useFormContext] = createContext<FormContextValue>('Form');
@@ -192,7 +211,10 @@ const FormContent = composable<HTMLDivElement, FormContentProps>(({ children, ..
192
211
 
193
212
  return (
194
213
  <div
195
- {...composableProps(props, { role: 'form', classNames: 'flex flex-col w-full pb-form-gap' })}
214
+ {...composableProps(props, {
215
+ role: 'form',
216
+ classNames: mx(withColumn.center(), 'flex flex-col w-full pb-form-gap'),
217
+ })}
196
218
  data-testid={testId}
197
219
  ref={mergedRef}
198
220
  >
@@ -238,11 +260,14 @@ const FormActions = ({ classNames }: FormActionsProps) => {
238
260
  if (readonly || layout === 'static') {
239
261
  return null;
240
262
  }
263
+
241
264
  // TODO(burdon): Currently onCancel is a no-op; implement "revert values".
242
265
  // Deprecate FormSubmit ans use FormActions without Cancel button if no callback is supplied.
243
266
 
244
267
  return (
245
- <div role='none' className={mx('grid grid-flow-col gap-form-gap auto-cols-fr py-form-padding', classNames)}>
268
+ <div
269
+ className={mx(withColumn.center(), 'grid grid-flow-col gap-form-gap auto-cols-fr py-form-padding', classNames)}
270
+ >
246
271
  {onCancel && (
247
272
  <IconButton
248
273
  icon='ph--x--regular'
@@ -270,6 +295,31 @@ const FormActions = ({ classNames }: FormActionsProps) => {
270
295
 
271
296
  FormActions.displayName = FORM_ACTIONS_NAME;
272
297
 
298
+ //
299
+ // Section
300
+ //
301
+
302
+ const FORM_SECTION_NAME = 'Form.Section';
303
+
304
+ type FormSectionProps = ThemedClassName<{ label: string; description?: string }>;
305
+
306
+ const FormSection = composable<HTMLDivElement, FormSectionProps>(
307
+ ({ children, label, description, ...props }, forwardedRef) => {
308
+ return (
309
+ <div
310
+ {...composableProps(props, { classNames: 'flex flex-col pt-form-section-gap first:pt-0' })}
311
+ ref={forwardedRef}
312
+ >
313
+ <h2 className='text-lg'>{label}</h2>
314
+ {description && <p className='text-description'>{description}</p>}
315
+ {children}
316
+ </div>
317
+ );
318
+ },
319
+ );
320
+
321
+ FormSection.displayName = FORM_SECTION_NAME;
322
+
273
323
  //
274
324
  // Submit
275
325
  //
@@ -291,7 +341,7 @@ const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
291
341
  }
292
342
 
293
343
  return (
294
- <div role='none' className={mx('flex w-full pt-form-padding', classNames)}>
344
+ <div className={mx('flex w-full pt-form-padding', classNames)}>
295
345
  <IconButton
296
346
  classNames='w-full'
297
347
  type='submit'
@@ -317,6 +367,7 @@ export const Form = {
317
367
  Root: FormRoot,
318
368
  Viewport: FormViewport,
319
369
  Content: FormContent,
370
+ Section: FormSection,
320
371
  FieldSet: FormFieldSet,
321
372
  Label: FormFieldLabel,
322
373
  Actions: FormActions,
@@ -329,6 +380,7 @@ export type {
329
380
  FormRootProps,
330
381
  FormViewportProps,
331
382
  FormContentProps,
383
+ FormSectionProps,
332
384
  FormFieldSetProps,
333
385
  FormFieldLabelProps,
334
386
  FormActionsProps,
@@ -23,7 +23,8 @@ import {
23
23
  import { useTranslation } from '@dxos/react-ui';
24
24
  import { type ProjectionModel } from '@dxos/schema';
25
25
 
26
- import { translationKey } from '../../translations';
26
+ import { translationKey } from '#translations';
27
+
27
28
  import { getRefProps } from '../../util';
28
29
  import {
29
30
  ArrayField,
@@ -91,7 +92,7 @@ export type FormFieldProps = {
91
92
  | 'createInitialValuePath'
92
93
  | 'createFieldMap'
93
94
  | 'db'
94
- | 'schemaHook'
95
+ | 'useSchema'
95
96
  | 'getOptions'
96
97
  | 'onCreate'
97
98
  >;
@@ -114,8 +115,8 @@ export const FormField = (props: FormFieldProps) => {
114
115
  createInitialValuePath,
115
116
  createFieldMap,
116
117
  db,
117
- schemaHook,
118
- getOptions: getRefOptions,
118
+ useSchema: schemaHook,
119
+ getOptions,
119
120
  onCreate,
120
121
  } = props;
121
122
  const { t } = useTranslation(translationKey);
@@ -137,6 +138,7 @@ export const FormField = (props: FormFieldProps) => {
137
138
  label,
138
139
  placeholder,
139
140
  layout,
141
+ db,
140
142
  ...fieldState,
141
143
  };
142
144
 
@@ -177,7 +179,7 @@ export const FormField = (props: FormFieldProps) => {
177
179
  // Select field.
178
180
  //
179
181
 
180
- const options = getOptions(type);
182
+ const options = getSelectOptions(type);
181
183
  if (options) {
182
184
  // Resolve labels from projection metadata when available.
183
185
  const fieldProjections = projection?.getFieldProjections();
@@ -214,8 +216,8 @@ export const FormField = (props: FormFieldProps) => {
214
216
  createInitialValuePath={isCreateTarget ? createInitialValuePath : undefined}
215
217
  createFieldMap={isCreateTarget ? createFieldMap : undefined}
216
218
  db={db}
217
- schemaHook={schemaHook}
218
- getOptions={getRefOptions}
219
+ useSchema={schemaHook}
220
+ getOptions={getOptions}
219
221
  onCreate={onCreate}
220
222
  />
221
223
  );
@@ -247,8 +249,8 @@ export const FormField = (props: FormFieldProps) => {
247
249
  createOptionIcon={createOptionIcon}
248
250
  createInitialValuePath={createInitialValuePath}
249
251
  db={db}
250
- schemaHook={schemaHook}
251
- getOptions={getRefOptions}
252
+ useSchema={schemaHook}
253
+ getOptions={getOptions}
252
254
  onCreate={onCreate}
253
255
  />
254
256
  );
@@ -295,7 +297,7 @@ const getFormField = ({ type, format }: FormFieldComponentProps): FormFieldCompo
295
297
  }
296
298
  };
297
299
 
298
- const getOptions = (ast: SchemaAST.AST): Format.Options[] | undefined => {
300
+ const getSelectOptions = (ast: SchemaAST.AST): Format.Options[] | undefined => {
299
301
  if (isLiteralUnion(ast)) {
300
302
  return ast.types.map((type) => type.literal).filter((v): v is string | number => v !== null);
301
303
  }
@@ -13,6 +13,7 @@ import React, {
13
13
  type ReactNode,
14
14
  } from 'react';
15
15
 
16
+ import { type Database } from '@dxos/echo';
16
17
  import { type Format } from '@dxos/echo/internal';
17
18
  import { Icon, Input, Tooltip } from '@dxos/react-ui';
18
19
  import { inputTextLabel } from '@dxos/ui-theme';
@@ -50,6 +51,8 @@ export type FormFieldComponentProps<T = any> = {
50
51
  placeholder?: string;
51
52
  autoFocus?: boolean;
52
53
  layout?: Presentation;
54
+ /** Database the form is editing against; populated for fields whose value is an ECHO object/Ref. */
55
+ db?: Database.Database;
53
56
  } & FormFieldStateProps<T>;
54
57
 
55
58
  /**
@@ -77,7 +80,7 @@ export type FormFieldLabelProps = {
77
80
  export const FormFieldLabel = ({ label, error, readonly, asChild }: FormFieldLabelProps) => {
78
81
  const Label = readonly || asChild ? 'span' : Input.Label;
79
82
  return (
80
- <div role='none' className='flex items-center justify-between'>
83
+ <div className='flex items-center justify-between'>
81
84
  <Label className={inputTextLabel}>{label}</Label>
82
85
  {error && (
83
86
  <Tooltip.Trigger asChild content={error} side='bottom'>
@@ -113,7 +116,7 @@ export const FormFieldWrapper = <T,>(props: FormFieldWrapperProps<T>) => {
113
116
  const str = String(value ?? '');
114
117
 
115
118
  return (
116
- <div role='none' className='contents'>
119
+ <div className='contents'>
117
120
  <Input.Root validationValence={status}>
118
121
  {layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
119
122
  {layout === 'static' ? <p>{str}</p> : children ? children({ value }) : null}
@@ -156,7 +159,7 @@ export class FormFieldErrorBoundary extends Component<FormFieldErrorBoundaryProp
156
159
  if (this.state.error) {
157
160
  return (
158
161
  <div className='flex gap-2 border border-rose-fill font-mono text-sm'>
159
- <span className='bg-rose-fill text-surface-text px-1 font-thin'>ERROR</span>
162
+ <span className='bg-rose-fill text-base-foreground px-1 font-thin'>ERROR</span>
160
163
  {String(this.props.path?.join('.'))}
161
164
  </div>
162
165
  );
@@ -35,7 +35,7 @@ export type FormFieldSetProps<T extends AnyProperties> = {
35
35
  | 'createInitialValuePath'
36
36
  | 'createFieldMap'
37
37
  | 'db'
38
- | 'schemaHook'
38
+ | 'useSchema'
39
39
  | 'getOptions'
40
40
  | 'onCreate'
41
41
  >;
@@ -55,10 +55,54 @@ export const FormFieldSet = ({
55
55
  ...props
56
56
  }: FormFieldSetProps<any>) => {
57
57
  const values = useFormValues(FORM_FIELDSET_NAME, path);
58
+ const properties = useFormFieldSetProperties({ schema, values, exclude, sort, projection });
59
+ if ((readonly || layout === 'static') && values == null) {
60
+ return null;
61
+ }
58
62
 
63
+ return (
64
+ <>
65
+ {layout !== 'inline' && label && <FormFieldLabel label={label} asChild />}
66
+ {properties.map((property) => {
67
+ const name = property.name.toString();
68
+ return (
69
+ <FormFieldErrorBoundary key={name} path={[...(path ?? []), name]}>
70
+ <FormField
71
+ type={property.type}
72
+ name={name}
73
+ path={[...(path ?? []), name]}
74
+ readonly={readonly}
75
+ layout={layout}
76
+ projection={projection}
77
+ {...props}
78
+ />
79
+ </FormFieldErrorBoundary>
80
+ );
81
+ })}
82
+ </>
83
+ );
84
+ };
85
+
86
+ FormFieldSet.displayName = FORM_FIELDSET_NAME;
87
+
88
+ type UseFormFieldSetPropertiesParams = Pick<FormFieldSetProps<any>, 'schema' | 'exclude' | 'projection' | 'sort'> & {
89
+ values: AnyProperties | undefined;
90
+ sort?: string[];
91
+ };
92
+
93
+ /**
94
+ * Resolves ordered schema properties for a field set (projection order, exclude, or sort).
95
+ */
96
+ const useFormFieldSetProperties = ({
97
+ schema,
98
+ exclude,
99
+ projection,
100
+ values,
101
+ sort,
102
+ }: UseFormFieldSetPropertiesParams): SchemaProperty[] => {
59
103
  // TODO(burdon): Updates on every value change.
60
104
  // Remove values dep if can remove from getSchemaProperties.
61
- const properties = useMemo(() => {
105
+ return useMemo(() => {
62
106
  if (!schema) {
63
107
  return [];
64
108
  }
@@ -79,7 +123,7 @@ export const FormFieldSet = ({
79
123
  // Add properties in projection field order.
80
124
  for (const fieldProjection of fieldProjections) {
81
125
  const fieldPath = String(fieldProjection.field.path);
82
- const prop = visibleProps.find((p) => p.name === fieldPath);
126
+ const prop = visibleProps.find((prop) => prop.name === fieldPath);
83
127
  if (prop) {
84
128
  orderedProps.push(prop);
85
129
  }
@@ -97,32 +141,4 @@ export const FormFieldSet = ({
97
141
  ? [...filteredProps].sort(({ name: a }, { name: b }) => sort.indexOf(a.toString()) - sort.indexOf(b.toString()))
98
142
  : filteredProps;
99
143
  }, [schema, values, exclude, sort, projection]);
100
-
101
- if ((readonly || layout === 'static') && values == null) {
102
- return null;
103
- }
104
-
105
- return (
106
- <>
107
- {layout !== 'inline' && label && <FormFieldLabel label={label} asChild />}
108
- {properties.map((property) => {
109
- const name = property.name.toString();
110
- return (
111
- <FormFieldErrorBoundary key={name} path={[...(path ?? []), name]}>
112
- <FormField
113
- type={property.type}
114
- name={name}
115
- path={[...(path ?? []), name]}
116
- readonly={readonly}
117
- layout={layout}
118
- projection={projection}
119
- {...props}
120
- />
121
- </FormFieldErrorBoundary>
122
- );
123
- })}
124
- </>
125
- );
126
144
  };
127
-
128
- FormFieldSet.displayName = FORM_FIELDSET_NAME;
@@ -9,7 +9,8 @@ import React, { useCallback } from 'react';
9
9
  import { findNode, getArrayElementType, getDiscriminatedType, isDiscriminatedUnion, isNestedType } from '@dxos/effect';
10
10
  import { IconButton, useTranslation } from '@dxos/react-ui';
11
11
 
12
- import { translationKey } from '../../../translations';
12
+ import { translationKey } from '#translations';
13
+
13
14
  import { getFormProperties } from '../../../util';
14
15
  import { useFormValues } from '../Form';
15
16
  import { FormField, type FormFieldProps } from '../FormField';
@@ -77,10 +78,10 @@ export const ArrayField = ({
77
78
  <FormFieldLabel readonly={readonly} label={label} asChild />
78
79
  )}
79
80
 
80
- <div role='none' className='flex flex-col gap-form-gap'>
81
+ <div className='flex flex-col gap-form-gap'>
81
82
  {values?.map((_, index) => {
82
83
  return (
83
- <div role='none' key={index} className='flex gap-form-gap last:mb-form-gap'>
84
+ <div key={index} className='grid grid-cols-[1fr_min-content] gap-form-gap last:mb-form-gap items-center'>
84
85
  <FormField
85
86
  autoFocus={index === values.length - 1}
86
87
  type={elementType}
@@ -91,9 +92,9 @@ export const ArrayField = ({
91
92
  />
92
93
 
93
94
  {!readonly && layout !== 'static' && (
94
- <div role='none' className='flex flex-col h-full justify-end'>
95
+ <div className='flex flex-col h-full justify-end'>
95
96
  {/* NOTE: Aligns with center of last field if multi-field object. */}
96
- <div role='none' className='flex items-center h-[2rem]'>
97
+ <div className='flex items-center h-[2rem]'>
97
98
  <IconButton
98
99
  icon='ph--x--regular'
99
100
  iconOnly
@@ -114,7 +115,7 @@ export const ArrayField = ({
114
115
  <IconButton
115
116
  classNames='flex w-full'
116
117
  icon='ph--plus--regular'
117
- label={t('add-field.button')}
118
+ label={t('add-item.button')}
118
119
  onClick={handleAdd}
119
120
  />
120
121
  )}