@dxos/react-ui-form 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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/dist/lib/browser/index.mjs +620 -440
- 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 +620 -440
- 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 -62
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +27 -4
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +73 -72
- 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 -62
- 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.map +1 -1
- 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 -62
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +108 -64
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -1
- 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/Settings/Settings.d.ts +24 -8
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts +64 -62
- 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 +2 -2
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +63 -62
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.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 -62
- 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 +39 -38
- package/src/components/FieldEditor/FieldEditor.stories.tsx +3 -4
- package/src/components/FieldEditor/FieldEditor.tsx +3 -2
- package/src/components/Form/Form.stories.tsx +5 -16
- package/src/components/Form/Form.tsx +30 -7
- package/src/components/Form/FormField.tsx +7 -5
- package/src/components/Form/FormFieldComponent.tsx +3 -0
- package/src/components/Form/FormFieldSet.tsx +1 -3
- package/src/components/Form/fields/ArrayField.tsx +8 -3
- package/src/components/Form/fields/GeoPointField.tsx +2 -1
- 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 +3 -2
- package/src/components/Form/fields/TextField.tsx +2 -2
- package/src/components/ObjectForm/ObjectForm.tsx +6 -5
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +5 -2
- package/src/components/ObjectPicker/ObjectPicker.tsx +28 -12
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +321 -22
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
- package/src/components/ObjectProperties/ObjectProperties.tsx +26 -14
- package/src/components/Settings/AUDIT.md +52 -0
- package/src/components/Settings/Settings.stories.tsx +50 -1
- package/src/components/Settings/Settings.tsx +6 -53
- package/src/components/Settings/SettingsFieldSet.tsx +257 -0
- package/src/components/Settings/SettingsItem.tsx +62 -0
- package/src/components/Settings/SettingsSelectField.tsx +59 -0
- package/src/components/Settings/index.ts +2 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +17 -18
- package/src/components/ViewEditor/ViewEditor.tsx +4 -3
- package/src/components/testing.tsx +20 -21
- package/src/index.ts +0 -4
- package/src/translations.ts +7 -4
- 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.abd8ff62ef",
|
|
4
4
|
"description": "Data components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -11,54 +11,55 @@
|
|
|
11
11
|
"license": "MIT",
|
|
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/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/keys": "0.8.4-main.
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/lit-ui": "0.8.4-main.
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/react-
|
|
51
|
-
"@dxos/react-ui-
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/react-ui-
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/react-ui-
|
|
59
|
-
"@dxos/types": "0.8.4-main.9be5663bfe",
|
|
60
|
-
"@dxos/ui-types": "0.8.4-main.9be5663bfe",
|
|
61
|
-
"@dxos/util": "0.8.4-main.9be5663bfe"
|
|
43
|
+
"@dxos/async": "0.8.4-main.abd8ff62ef",
|
|
44
|
+
"@dxos/echo": "0.8.4-main.abd8ff62ef",
|
|
45
|
+
"@dxos/echo-atom": "0.8.4-main.abd8ff62ef",
|
|
46
|
+
"@dxos/echo-react": "0.8.4-main.abd8ff62ef",
|
|
47
|
+
"@dxos/effect": "0.8.4-main.abd8ff62ef",
|
|
48
|
+
"@dxos/invariant": "0.8.4-main.abd8ff62ef",
|
|
49
|
+
"@dxos/keys": "0.8.4-main.abd8ff62ef",
|
|
50
|
+
"@dxos/echo-db": "0.8.4-main.abd8ff62ef",
|
|
51
|
+
"@dxos/lit-ui": "0.8.4-main.abd8ff62ef",
|
|
52
|
+
"@dxos/react-client": "0.8.4-main.abd8ff62ef",
|
|
53
|
+
"@dxos/log": "0.8.4-main.abd8ff62ef",
|
|
54
|
+
"@dxos/react-ui-list": "0.8.4-main.abd8ff62ef",
|
|
55
|
+
"@dxos/react-ui-pickers": "0.8.4-main.abd8ff62ef",
|
|
56
|
+
"@dxos/react-ui-components": "0.8.4-main.abd8ff62ef",
|
|
57
|
+
"@dxos/react-ui-search": "0.8.4-main.abd8ff62ef",
|
|
58
|
+
"@dxos/schema": "0.8.4-main.abd8ff62ef",
|
|
59
|
+
"@dxos/types": "0.8.4-main.abd8ff62ef",
|
|
60
|
+
"@dxos/ui-editor": "0.8.4-main.abd8ff62ef",
|
|
61
|
+
"@dxos/util": "0.8.4-main.abd8ff62ef",
|
|
62
|
+
"@dxos/react-ui-editor": "0.8.4-main.abd8ff62ef"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@types/react": "~19.2.7",
|
|
@@ -67,20 +68,20 @@
|
|
|
67
68
|
"fast-check": "^3.19.0",
|
|
68
69
|
"react": "~19.2.3",
|
|
69
70
|
"react-dom": "~19.2.3",
|
|
70
|
-
"vite": "^
|
|
71
|
-
"@dxos/random": "0.8.4-main.
|
|
72
|
-
"@dxos/react-client": "0.8.4-main.
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/react-ui
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/ui-
|
|
71
|
+
"vite": "^8.0.10",
|
|
72
|
+
"@dxos/random": "0.8.4-main.abd8ff62ef",
|
|
73
|
+
"@dxos/react-client": "0.8.4-main.abd8ff62ef",
|
|
74
|
+
"@dxos/storybook-utils": "0.8.4-main.abd8ff62ef",
|
|
75
|
+
"@dxos/react-ui": "0.8.4-main.abd8ff62ef",
|
|
76
|
+
"@dxos/ui-theme": "0.8.4-main.abd8ff62ef",
|
|
77
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.abd8ff62ef"
|
|
77
78
|
},
|
|
78
79
|
"peerDependencies": {
|
|
79
80
|
"effect": "3.20.0",
|
|
80
81
|
"react": "~19.2.3",
|
|
81
82
|
"react-dom": "~19.2.3",
|
|
82
|
-
"@dxos/
|
|
83
|
-
"@dxos/ui
|
|
83
|
+
"@dxos/ui-theme": "0.8.4-main.abd8ff62ef",
|
|
84
|
+
"@dxos/react-ui": "0.8.4-main.abd8ff62ef"
|
|
84
85
|
},
|
|
85
86
|
"publishConfig": {
|
|
86
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
|
|
|
@@ -70,9 +71,7 @@ const DefaultStory = () => {
|
|
|
70
71
|
|
|
71
72
|
return (
|
|
72
73
|
<TestLayout json={json}>
|
|
73
|
-
<
|
|
74
|
-
<FieldEditor {...props} />
|
|
75
|
-
</div>
|
|
74
|
+
<FieldEditor {...props} />
|
|
76
75
|
</TestLayout>
|
|
77
76
|
);
|
|
78
77
|
};
|
|
@@ -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 {
|
|
@@ -14,7 +14,8 @@ import { withClientProvider } from '@dxos/react-client/testing';
|
|
|
14
14
|
import { Tooltip } from '@dxos/react-ui';
|
|
15
15
|
import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
16
16
|
|
|
17
|
-
import { translations } from '
|
|
17
|
+
import { translations } from '#translations';
|
|
18
|
+
|
|
18
19
|
import { TestLayout } from '../testing';
|
|
19
20
|
import { type ExcludeId, Form, type FormRootProps, omitId } from './Form';
|
|
20
21
|
|
|
@@ -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>({
|
|
@@ -102,7 +103,7 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
|
|
|
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}
|
|
@@ -129,7 +130,6 @@ const meta = {
|
|
|
129
130
|
title: 'ui/react-ui-form/Form',
|
|
130
131
|
component: Form.Root,
|
|
131
132
|
render: DefaultStory,
|
|
132
|
-
|
|
133
133
|
decorators: [
|
|
134
134
|
withTheme(),
|
|
135
135
|
withLayout({ layout: 'fullscreen' }),
|
|
@@ -186,16 +186,5 @@ export const Static: Story<ExcludeId<typeof Person>> = {
|
|
|
186
186
|
};
|
|
187
187
|
|
|
188
188
|
export const Empty: Story<ExcludeId<typeof Person>> = {
|
|
189
|
-
|
|
190
|
-
<TestLayout>
|
|
191
|
-
<Form.Root>
|
|
192
|
-
<Form.Viewport>
|
|
193
|
-
<Form.Content>
|
|
194
|
-
<Form.FieldSet />
|
|
195
|
-
<Form.Actions />
|
|
196
|
-
</Form.Content>
|
|
197
|
-
</Form.Viewport>
|
|
198
|
-
</Form.Root>
|
|
199
|
-
</TestLayout>
|
|
200
|
-
),
|
|
189
|
+
args: {},
|
|
201
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,22 +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 styles to form.ts.
|
|
39
|
+
// TODO(burdon): Move styles to form.ts (as with ui-theme).
|
|
37
40
|
|
|
38
|
-
// TODO(burdon):
|
|
41
|
+
// TODO(burdon): Reconcile with @dxos/echo.
|
|
39
42
|
export type ExcludeId<S extends Schema.Schema.AnyNoContext> = Omit<Schema.Schema.Type<S>, 'id'>;
|
|
40
43
|
|
|
41
44
|
// TODO(burdon): Move to @dxos/schema (re-export here).
|
|
42
45
|
export const omitId = <S extends Schema.Schema.AnyNoContext>(schema: S): Schema.Schema<ExcludeId<S>, ExcludeId<S>> =>
|
|
43
46
|
schema.pipe(Schema.omit('id')) as any;
|
|
44
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
|
+
|
|
45
62
|
//
|
|
46
63
|
// Context
|
|
47
64
|
//
|
|
@@ -63,7 +80,7 @@ type FormContextValue<T extends AnyProperties = any> = {
|
|
|
63
80
|
testId?: string;
|
|
64
81
|
} & Pick<
|
|
65
82
|
NaturalFormFieldSetProps<T>,
|
|
66
|
-
'readonly' | 'layout' | 'fieldMap' | 'fieldProvider' | 'projection' | 'createTypename' | 'createFieldMap'
|
|
83
|
+
'readonly' | 'layout' | 'fieldMap' | 'fieldProvider' | 'projection' | 'createTypename' | 'createFieldMap' | 'db'
|
|
67
84
|
>;
|
|
68
85
|
|
|
69
86
|
const [FormContextProvider, useFormContext] = createContext<FormContextValue>('Form');
|
|
@@ -194,7 +211,10 @@ const FormContent = composable<HTMLDivElement, FormContentProps>(({ children, ..
|
|
|
194
211
|
|
|
195
212
|
return (
|
|
196
213
|
<div
|
|
197
|
-
{...composableProps(props, {
|
|
214
|
+
{...composableProps(props, {
|
|
215
|
+
role: 'form',
|
|
216
|
+
classNames: mx(withColumn.center(), 'flex flex-col w-full pb-form-gap'),
|
|
217
|
+
})}
|
|
198
218
|
data-testid={testId}
|
|
199
219
|
ref={mergedRef}
|
|
200
220
|
>
|
|
@@ -245,7 +265,10 @@ const FormActions = ({ classNames }: FormActionsProps) => {
|
|
|
245
265
|
// Deprecate FormSubmit ans use FormActions without Cancel button if no callback is supplied.
|
|
246
266
|
|
|
247
267
|
return (
|
|
248
|
-
<div
|
|
268
|
+
<div
|
|
269
|
+
role='none'
|
|
270
|
+
className={mx(withColumn.center(), 'grid grid-flow-col gap-form-gap auto-cols-fr py-form-padding', classNames)}
|
|
271
|
+
>
|
|
249
272
|
{onCancel && (
|
|
250
273
|
<IconButton
|
|
251
274
|
icon='ph--x--regular'
|
|
@@ -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,7 +115,7 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
114
115
|
createInitialValuePath,
|
|
115
116
|
createFieldMap,
|
|
116
117
|
db,
|
|
117
|
-
schemaHook,
|
|
118
|
+
useSchema: schemaHook,
|
|
118
119
|
getOptions,
|
|
119
120
|
onCreate,
|
|
120
121
|
} = props;
|
|
@@ -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
|
|
|
@@ -214,7 +216,7 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
214
216
|
createInitialValuePath={isCreateTarget ? createInitialValuePath : undefined}
|
|
215
217
|
createFieldMap={isCreateTarget ? createFieldMap : undefined}
|
|
216
218
|
db={db}
|
|
217
|
-
|
|
219
|
+
useSchema={schemaHook}
|
|
218
220
|
getOptions={getOptions}
|
|
219
221
|
onCreate={onCreate}
|
|
220
222
|
/>
|
|
@@ -247,7 +249,7 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
247
249
|
createOptionIcon={createOptionIcon}
|
|
248
250
|
createInitialValuePath={createInitialValuePath}
|
|
249
251
|
db={db}
|
|
250
|
-
|
|
252
|
+
useSchema={schemaHook}
|
|
251
253
|
getOptions={getOptions}
|
|
252
254
|
onCreate={onCreate}
|
|
253
255
|
/>
|
|
@@ -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
|
/**
|
|
@@ -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,9 +55,7 @@ export const FormFieldSet = ({
|
|
|
55
55
|
...props
|
|
56
56
|
}: FormFieldSetProps<any>) => {
|
|
57
57
|
const values = useFormValues(FORM_FIELDSET_NAME, path);
|
|
58
|
-
|
|
59
58
|
const properties = useFormFieldSetProperties({ schema, values, exclude, sort, projection });
|
|
60
|
-
|
|
61
59
|
if ((readonly || layout === 'static') && values == null) {
|
|
62
60
|
return null;
|
|
63
61
|
}
|
|
@@ -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';
|
|
@@ -80,7 +81,11 @@ export const ArrayField = ({
|
|
|
80
81
|
<div role='none' className='flex flex-col gap-form-gap'>
|
|
81
82
|
{values?.map((_, index) => {
|
|
82
83
|
return (
|
|
83
|
-
<div
|
|
84
|
+
<div
|
|
85
|
+
role='none'
|
|
86
|
+
key={index}
|
|
87
|
+
className='grid grid-cols-[1fr_min-content] gap-form-gap last:mb-form-gap items-center'
|
|
88
|
+
>
|
|
84
89
|
<FormField
|
|
85
90
|
autoFocus={index === values.length - 1}
|
|
86
91
|
type={elementType}
|
|
@@ -114,7 +119,7 @@ export const ArrayField = ({
|
|
|
114
119
|
<IconButton
|
|
115
120
|
classNames='flex w-full'
|
|
116
121
|
icon='ph--plus--regular'
|
|
117
|
-
label={t('add-
|
|
122
|
+
label={t('add-item.button')}
|
|
118
123
|
onClick={handleAdd}
|
|
119
124
|
/>
|
|
120
125
|
)}
|
|
@@ -8,7 +8,8 @@ import { GeoLocation, type GeoPoint } from '@dxos/echo/internal';
|
|
|
8
8
|
import { Input, useTranslation } from '@dxos/react-ui';
|
|
9
9
|
import { safeParseFloat } from '@dxos/util';
|
|
10
10
|
|
|
11
|
-
import { translationKey } from '
|
|
11
|
+
import { translationKey } from '#translations';
|
|
12
|
+
|
|
12
13
|
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
13
14
|
|
|
14
15
|
export const GeoPointField = ({
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import React, { useMemo } from 'react';
|
|
8
|
+
import { expect, userEvent } from 'storybook/test';
|
|
9
|
+
|
|
10
|
+
import { Filter, Format, Obj, Ref } from '@dxos/echo';
|
|
11
|
+
import { useQuery } from '@dxos/react-client/echo';
|
|
12
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
13
|
+
import { Panel } from '@dxos/react-ui';
|
|
14
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
15
|
+
import { Text } from '@dxos/schema';
|
|
16
|
+
|
|
17
|
+
import { translations } from '#translations';
|
|
18
|
+
|
|
19
|
+
import { TestLayout } from '../../testing';
|
|
20
|
+
import { Form } from '../Form';
|
|
21
|
+
|
|
22
|
+
//
|
|
23
|
+
// String-backed markdown.
|
|
24
|
+
//
|
|
25
|
+
|
|
26
|
+
const StringSchema = Schema.Struct({
|
|
27
|
+
notes: Schema.String.pipe(Format.FormatAnnotation.set(Format.TypeFormat.Markdown)).annotations({ title: 'Notes' }),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const StringStory = () => (
|
|
31
|
+
<TestLayout>
|
|
32
|
+
<Form.Root schema={StringSchema} defaultValues={{ notes: '# Hello\n\nMarkdown text *here*.' }}>
|
|
33
|
+
<Form.Viewport>
|
|
34
|
+
<Form.Content>
|
|
35
|
+
<Form.FieldSet />
|
|
36
|
+
</Form.Content>
|
|
37
|
+
</Form.Viewport>
|
|
38
|
+
</Form.Root>
|
|
39
|
+
</TestLayout>
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
//
|
|
43
|
+
// Ref<Text>-backed markdown.
|
|
44
|
+
//
|
|
45
|
+
|
|
46
|
+
const TextNoteHolder = Schema.Struct({
|
|
47
|
+
instructions: Ref.Ref(Text.Text)
|
|
48
|
+
.pipe(Format.FormatAnnotation.set(Format.TypeFormat.Markdown))
|
|
49
|
+
.annotations({ title: 'Instructions' }),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const RefStory = () => {
|
|
53
|
+
const { space } = useClientStory();
|
|
54
|
+
const [text] = useQuery(space?.db, Filter.type(Text.Text));
|
|
55
|
+
const values = useMemo(() => (text ? { instructions: Ref.make(text) } : undefined), [text]);
|
|
56
|
+
|
|
57
|
+
if (!space || !values) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<TestLayout>
|
|
63
|
+
<Panel.Root>
|
|
64
|
+
<Panel.Content asChild>
|
|
65
|
+
<Form.Root schema={TextNoteHolder} values={values as any} db={space.db}>
|
|
66
|
+
<Form.Viewport>
|
|
67
|
+
<Form.Content>
|
|
68
|
+
<Form.FieldSet />
|
|
69
|
+
</Form.Content>
|
|
70
|
+
</Form.Viewport>
|
|
71
|
+
</Form.Root>
|
|
72
|
+
</Panel.Content>
|
|
73
|
+
</Panel.Root>
|
|
74
|
+
</TestLayout>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
//
|
|
79
|
+
// Ref<Text> empty — shows the create button.
|
|
80
|
+
//
|
|
81
|
+
|
|
82
|
+
const EmptyRefSchema = Schema.Struct({
|
|
83
|
+
instructions: Schema.optional(
|
|
84
|
+
Ref.Ref(Text.Text)
|
|
85
|
+
.pipe(Format.FormatAnnotation.set(Format.TypeFormat.Markdown))
|
|
86
|
+
.annotations({ title: 'Instructions' }),
|
|
87
|
+
),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const EmptyRefStory = () => {
|
|
91
|
+
const { space } = useClientStory();
|
|
92
|
+
if (!space) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<TestLayout>
|
|
98
|
+
<Panel.Root>
|
|
99
|
+
<Panel.Content asChild>
|
|
100
|
+
<Form.Root schema={EmptyRefSchema} defaultValues={{}} db={space.db}>
|
|
101
|
+
<Form.Viewport>
|
|
102
|
+
<Form.Content>
|
|
103
|
+
<Form.FieldSet />
|
|
104
|
+
</Form.Content>
|
|
105
|
+
</Form.Viewport>
|
|
106
|
+
</Form.Root>
|
|
107
|
+
</Panel.Content>
|
|
108
|
+
</Panel.Root>
|
|
109
|
+
</TestLayout>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const meta = {
|
|
114
|
+
title: 'ui/react-ui-form/MarkdownField',
|
|
115
|
+
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
116
|
+
parameters: {
|
|
117
|
+
layout: 'fullscreen',
|
|
118
|
+
translations,
|
|
119
|
+
},
|
|
120
|
+
} satisfies Meta;
|
|
121
|
+
|
|
122
|
+
export default meta;
|
|
123
|
+
|
|
124
|
+
type Story = StoryObj<typeof meta>;
|
|
125
|
+
|
|
126
|
+
export const StringBacked: Story = {
|
|
127
|
+
render: () => <StringStory />,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Regression test: keystrokes into a string-backed markdown field must not
|
|
131
|
+
// blur the editor. Previously the EditorView was destroyed and recreated on
|
|
132
|
+
// every change because `onChange` was a useTextEditor dep and form callbacks
|
|
133
|
+
// are unstable across renders.
|
|
134
|
+
export const StringBackedKeepsFocus: Story = {
|
|
135
|
+
render: () => (
|
|
136
|
+
<TestLayout>
|
|
137
|
+
<Form.Root schema={StringSchema} defaultValues={{ notes: '' }}>
|
|
138
|
+
<Form.Viewport>
|
|
139
|
+
<Form.Content>
|
|
140
|
+
<Form.FieldSet />
|
|
141
|
+
</Form.Content>
|
|
142
|
+
</Form.Viewport>
|
|
143
|
+
</Form.Root>
|
|
144
|
+
</TestLayout>
|
|
145
|
+
),
|
|
146
|
+
play: async ({ canvasElement }) => {
|
|
147
|
+
const waitFor = async <T,>(predicate: () => T | null | undefined, message: string): Promise<T> => {
|
|
148
|
+
for (let attempt = 0; attempt < 50; attempt++) {
|
|
149
|
+
const value = predicate();
|
|
150
|
+
if (value) {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
154
|
+
}
|
|
155
|
+
throw new Error(message);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const editorRoot = await waitFor(
|
|
159
|
+
() => canvasElement.querySelector<HTMLElement>('.cm-editor'),
|
|
160
|
+
'CodeMirror editor root (.cm-editor) not found',
|
|
161
|
+
);
|
|
162
|
+
const editorContent = editorRoot.querySelector<HTMLElement>('.cm-content')!;
|
|
163
|
+
await expect(editorContent).toBeVisible();
|
|
164
|
+
|
|
165
|
+
// Focus editor and wait for focus to settle inside the editor root.
|
|
166
|
+
editorContent.focus();
|
|
167
|
+
await waitFor(() => (editorRoot.contains(document.activeElement) ? true : null), 'Editor did not receive focus');
|
|
168
|
+
|
|
169
|
+
// Type several characters; focus must be preserved inside the editor root
|
|
170
|
+
// after every keystroke. Regression: previously the EditorView was
|
|
171
|
+
// destroyed and recreated on every change, which detached the focused
|
|
172
|
+
// contentDOM and blurred the input.
|
|
173
|
+
const phrase = 'hello world';
|
|
174
|
+
for (const ch of phrase) {
|
|
175
|
+
await userEvent.keyboard(ch);
|
|
176
|
+
await expect(editorRoot.contains(document.activeElement)).toBe(true);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await expect(editorContent).toHaveTextContent(phrase);
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const RefPopulated: Story = {
|
|
184
|
+
render: () => <RefStory />,
|
|
185
|
+
decorators: [
|
|
186
|
+
withClientProvider({
|
|
187
|
+
createIdentity: true,
|
|
188
|
+
createSpace: true,
|
|
189
|
+
types: [Text.Text],
|
|
190
|
+
onCreateSpace: async ({ space }) => {
|
|
191
|
+
space.db.add(Obj.make(Text.Text, { content: '# Magazine brief\n\nLong-form text describing what to gather.' }));
|
|
192
|
+
},
|
|
193
|
+
}),
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const RefEmpty: Story = {
|
|
198
|
+
render: () => <EmptyRefStory />,
|
|
199
|
+
decorators: [
|
|
200
|
+
withClientProvider({
|
|
201
|
+
createIdentity: true,
|
|
202
|
+
createSpace: true,
|
|
203
|
+
types: [Text.Text],
|
|
204
|
+
}),
|
|
205
|
+
],
|
|
206
|
+
};
|