@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.
- package/LICENSE +102 -5
- package/dist/lib/browser/index.mjs +873 -446
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +82 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +873 -446
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +84 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +63 -61
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +34 -12
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +73 -71
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +5 -5
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +8 -6
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +5 -8
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +4 -4
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +8 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +84 -0
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +10 -4
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +63 -61
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +9 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
- package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +17 -3
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +63 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +127 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.d.ts +24 -12
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts +64 -61
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +3 -4
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +63 -61
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +4 -4
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +63 -61
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field-type.d.ts +19 -0
- package/dist/types/src/util/field-type.d.ts.map +1 -0
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts.map +1 -1
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +40 -38
- package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -1
- package/src/components/FieldEditor/FieldEditor.tsx +3 -2
- package/src/components/Form/Form.stories.tsx +9 -19
- package/src/components/Form/Form.tsx +59 -7
- package/src/components/Form/FormField.tsx +12 -10
- package/src/components/Form/FormFieldComponent.tsx +6 -3
- package/src/components/Form/FormFieldSet.tsx +47 -31
- package/src/components/Form/fields/ArrayField.tsx +7 -6
- package/src/components/Form/fields/GeoPointField.tsx +3 -2
- package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
- package/src/components/Form/fields/MarkdownField.tsx +135 -45
- package/src/components/Form/fields/RefField.tsx +61 -30
- package/src/components/Form/fields/SelectOptionField.stories.tsx +8 -5
- package/src/components/Form/fields/SelectOptionField.tsx +6 -5
- package/src/components/Form/fields/TextField.tsx +2 -2
- package/src/components/ObjectForm/ObjectForm.tsx +114 -0
- package/src/components/ObjectForm/index.ts +5 -0
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +9 -6
- package/src/components/ObjectPicker/ObjectPicker.tsx +28 -12
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +362 -0
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
- package/src/components/ObjectProperties/ObjectProperties.tsx +141 -0
- package/src/components/ObjectProperties/index.ts +5 -0
- package/src/components/Settings/AUDIT.md +52 -0
- package/src/components/Settings/Settings.stories.tsx +50 -1
- package/src/components/Settings/Settings.tsx +7 -56
- package/src/components/Settings/SettingsFieldSet.tsx +257 -0
- package/src/components/Settings/SettingsItem.tsx +58 -0
- package/src/components/Settings/SettingsSelectField.tsx +59 -0
- package/src/components/Settings/index.ts +2 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +10 -10
- package/src/components/ViewEditor/ViewEditor.tsx +19 -26
- package/src/components/index.ts +2 -0
- package/src/components/testing.tsx +20 -21
- package/src/index.ts +0 -4
- package/src/translations.ts +7 -3
- package/src/util/field-type.ts +60 -0
- 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.
|
|
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": "
|
|
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/
|
|
40
|
-
"@dxos/echo": "0.8.4-main.
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/echo-
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/keys": "0.8.4-main.
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/react-
|
|
50
|
-
"@dxos/react-ui-editor": "0.8.4-main.
|
|
51
|
-
"@dxos/react-ui-list": "0.8.4-main.
|
|
52
|
-
"@dxos/react-ui-
|
|
53
|
-
"@dxos/react-ui-
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/
|
|
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": "^
|
|
70
|
-
"@dxos/random": "0.8.4-main.
|
|
71
|
-
"@dxos/react-
|
|
72
|
-
"@dxos/ui
|
|
73
|
-
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.
|
|
74
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
75
|
-
"@dxos/
|
|
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.
|
|
82
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
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 '
|
|
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 '
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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, {
|
|
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
|
|
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
|
|
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 '
|
|
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
|
-
| '
|
|
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
|
|
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 =
|
|
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
|
-
|
|
218
|
-
getOptions={
|
|
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
|
-
|
|
251
|
-
getOptions={
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
-
| '
|
|
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
|
-
|
|
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((
|
|
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 '
|
|
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
|
|
81
|
+
<div className='flex flex-col gap-form-gap'>
|
|
81
82
|
{values?.map((_, index) => {
|
|
82
83
|
return (
|
|
83
|
-
<div
|
|
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
|
|
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
|
|
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-
|
|
118
|
+
label={t('add-item.button')}
|
|
118
119
|
onClick={handleAdd}
|
|
119
120
|
/>
|
|
120
121
|
)}
|