@dxos/react-ui-form 0.8.4-main.d05539e30a → 0.8.4-main.d9fc60f731
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 +1335 -561
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +2 -1
- package/dist/lib/browser/translations.mjs.map +2 -2
- package/dist/lib/node-esm/index.mjs +1335 -561
- 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 +2 -1
- package/dist/lib/node-esm/translations.mjs.map +2 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +2 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +2 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +35 -9
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +76 -16
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +14 -4
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +22 -10
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +8 -2
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormTooltipsContext.d.ts +19 -0
- package/dist/types/src/components/Form/FormTooltipsContext.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.d.ts +50 -0
- package/dist/types/src/components/Form/Layout/FormLayout.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts +119 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/annotation.d.ts +30 -0
- package/dist/types/src/components/Form/Layout/annotation.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/index.d.ts +4 -0
- package/dist/types/src/components/Form/Layout/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/parser.d.ts +31 -0
- package/dist/types/src/components/Form/Layout/parser.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/parser.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/parser.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/DateField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/DateField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/InlineRefField.d.ts +13 -0
- package/dist/types/src/components/Form/fields/InlineRefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +1 -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 +2 -1
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectField.d.ts +4 -1
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +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 +2 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/index.d.ts +2 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/index.d.ts +3 -0
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/meta-tags.d.ts +30 -0
- package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -0
- package/dist/types/src/components/Form/meta-tags.test.d.ts +2 -0
- package/dist/types/src/components/Form/meta-tags.test.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +3 -4
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +2 -2
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +2 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +2 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectTree/ObjectTree.d.ts +9 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.d.ts.map +1 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts +87 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectTree/index.d.ts +2 -0
- package/dist/types/src/components/ObjectTree/index.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts +2 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +7 -8
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +4 -4
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +9 -0
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +11 -0
- package/dist/types/src/util/properties.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +37 -32
- package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -2
- package/src/components/FieldEditor/FieldEditor.test.tsx +4 -1
- package/src/components/FieldEditor/FieldEditor.tsx +19 -14
- package/src/components/Form/Form.stories.tsx +21 -27
- package/src/components/Form/Form.tsx +106 -19
- package/src/components/Form/FormField.tsx +85 -28
- package/src/components/Form/FormFieldComponent.tsx +75 -20
- package/src/components/Form/FormFieldSet.tsx +32 -3
- package/src/components/Form/FormTooltipsContext.ts +25 -0
- package/src/components/Form/Layout/FormLayout.stories.test.tsx +51 -0
- package/src/components/Form/Layout/FormLayout.stories.tsx +422 -0
- package/src/components/Form/Layout/FormLayout.test.ts +59 -0
- package/src/components/Form/Layout/FormLayout.tsx +251 -0
- package/src/components/Form/Layout/annotation.ts +38 -0
- package/src/components/Form/Layout/index.ts +7 -0
- package/src/components/Form/Layout/parser.test.ts +120 -0
- package/src/components/Form/Layout/parser.ts +167 -0
- package/src/components/Form/fields/ArrayField.tsx +61 -38
- package/src/components/Form/fields/DateField.tsx +101 -0
- package/src/components/Form/fields/GeoPointField.tsx +4 -1
- package/src/components/Form/fields/InlineRefField.tsx +146 -0
- package/src/components/Form/fields/MarkdownField.tsx +3 -3
- package/src/components/Form/fields/RefField.tsx +29 -23
- package/src/components/Form/fields/SelectField.tsx +17 -5
- package/src/components/Form/fields/SelectOptionField.tsx +4 -3
- package/src/components/Form/fields/TextAreaField.tsx +1 -1
- package/src/components/Form/fields/TupleField.tsx +2 -1
- package/src/components/Form/fields/index.ts +2 -0
- package/src/components/Form/index.ts +3 -0
- package/src/components/Form/meta-tags.test.ts +47 -0
- package/src/components/Form/meta-tags.ts +33 -0
- package/src/components/ObjectForm/ObjectForm.tsx +21 -26
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +2 -2
- package/src/components/ObjectPicker/ObjectPicker.tsx +2 -2
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +14 -26
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +20 -10
- package/src/components/ObjectProperties/ObjectProperties.tsx +26 -30
- package/src/components/ObjectTree/ObjectTree.stories.tsx +134 -0
- package/src/components/ObjectTree/ObjectTree.tsx +190 -0
- package/src/components/ObjectTree/index.ts +5 -0
- package/src/components/Settings/Settings.tsx +2 -1
- package/src/components/Settings/SettingsItem.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +35 -40
- package/src/components/ViewEditor/ViewEditor.test.tsx +11 -8
- package/src/components/ViewEditor/ViewEditor.tsx +29 -32
- package/src/components/index.ts +1 -0
- package/src/components/testing.tsx +4 -4
- package/src/translations.ts +2 -1
- package/src/util/properties.test.ts +67 -9
- package/src/util/properties.ts +36 -4
- package/src/vite-env.d.ts +10 -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.d9fc60f731",
|
|
4
4
|
"description": "Data components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -34,54 +34,59 @@
|
|
|
34
34
|
"src"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@codemirror/lang-xml": "^6.1.0",
|
|
37
38
|
"@effect-atom/atom-react": "^0.5.0",
|
|
38
39
|
"@fluentui/react-tabster": "9.26.11",
|
|
39
40
|
"@radix-ui/react-context": "1.1.1",
|
|
40
41
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
41
42
|
"date-fns": "^3.6.0",
|
|
42
43
|
"tabster": "^8.5.5",
|
|
43
|
-
"@dxos/async": "0.8.4-main.
|
|
44
|
-
"@dxos/echo": "0.8.4-main.
|
|
45
|
-
"@dxos/echo-
|
|
46
|
-
"@dxos/echo
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/echo-react": "0.8.4-main.
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/react-
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/react-ui-
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/react-ui-search": "0.8.4-main.
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/
|
|
60
|
-
"@dxos/
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/
|
|
44
|
+
"@dxos/async": "0.8.4-main.d9fc60f731",
|
|
45
|
+
"@dxos/echo-db": "0.8.4-main.d9fc60f731",
|
|
46
|
+
"@dxos/echo-atom": "0.8.4-main.d9fc60f731",
|
|
47
|
+
"@dxos/echo": "0.8.4-main.d9fc60f731",
|
|
48
|
+
"@dxos/echo-protocol": "0.8.4-main.d9fc60f731",
|
|
49
|
+
"@dxos/echo-react": "0.8.4-main.d9fc60f731",
|
|
50
|
+
"@dxos/invariant": "0.8.4-main.d9fc60f731",
|
|
51
|
+
"@dxos/keys": "0.8.4-main.d9fc60f731",
|
|
52
|
+
"@dxos/effect": "0.8.4-main.d9fc60f731",
|
|
53
|
+
"@dxos/lit-ui": "0.8.4-main.d9fc60f731",
|
|
54
|
+
"@dxos/react-client": "0.8.4-main.d9fc60f731",
|
|
55
|
+
"@dxos/log": "0.8.4-main.d9fc60f731",
|
|
56
|
+
"@dxos/react-ui-components": "0.8.4-main.d9fc60f731",
|
|
57
|
+
"@dxos/react-ui-editor": "0.8.4-main.d9fc60f731",
|
|
58
|
+
"@dxos/react-ui-search": "0.8.4-main.d9fc60f731",
|
|
59
|
+
"@dxos/react-ui-list": "0.8.4-main.d9fc60f731",
|
|
60
|
+
"@dxos/schema": "0.8.4-main.d9fc60f731",
|
|
61
|
+
"@dxos/ui-editor": "0.8.4-main.d9fc60f731",
|
|
62
|
+
"@dxos/types": "0.8.4-main.d9fc60f731",
|
|
63
|
+
"@dxos/react-ui-pickers": "0.8.4-main.d9fc60f731",
|
|
64
|
+
"@dxos/util": "0.8.4-main.d9fc60f731"
|
|
63
65
|
},
|
|
64
66
|
"devDependencies": {
|
|
65
67
|
"@types/react": "~19.2.7",
|
|
66
68
|
"@types/react-dom": "~19.2.3",
|
|
67
|
-
"effect": "3.
|
|
69
|
+
"effect": "3.21.2",
|
|
68
70
|
"fast-check": "^3.19.0",
|
|
69
71
|
"react": "~19.2.3",
|
|
70
72
|
"react-dom": "~19.2.3",
|
|
71
|
-
"vite": "^8.0.
|
|
72
|
-
"@dxos/
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/react-
|
|
76
|
-
"@dxos/ui
|
|
77
|
-
"@dxos/
|
|
73
|
+
"vite": "^8.0.14",
|
|
74
|
+
"@dxos/effect-proto": "0.8.4-main.d9fc60f731",
|
|
75
|
+
"@dxos/protocols": "0.8.4-main.d9fc60f731",
|
|
76
|
+
"@dxos/random": "0.8.4-main.d9fc60f731",
|
|
77
|
+
"@dxos/react-client": "0.8.4-main.d9fc60f731",
|
|
78
|
+
"@dxos/react-ui": "0.8.4-main.d9fc60f731",
|
|
79
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.d9fc60f731",
|
|
80
|
+
"@dxos/storybook-utils": "0.8.4-main.d9fc60f731",
|
|
81
|
+
"@dxos/ui-theme": "0.8.4-main.d9fc60f731",
|
|
82
|
+
"@dxos/ui-types": "0.8.4-main.d9fc60f731"
|
|
78
83
|
},
|
|
79
84
|
"peerDependencies": {
|
|
80
|
-
"effect": "3.
|
|
85
|
+
"effect": "3.21.2",
|
|
81
86
|
"react": "~19.2.3",
|
|
82
87
|
"react-dom": "~19.2.3",
|
|
83
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
84
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
88
|
+
"@dxos/ui-theme": "0.8.4-main.d9fc60f731",
|
|
89
|
+
"@dxos/react-ui": "0.8.4-main.d9fc60f731"
|
|
85
90
|
},
|
|
86
91
|
"publishConfig": {
|
|
87
92
|
"access": "public"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import React, { useEffect, useMemo } from 'react';
|
|
7
7
|
|
|
8
|
-
import { Filter, Query } from '@dxos/echo';
|
|
8
|
+
import { Filter, Query, Type } from '@dxos/echo';
|
|
9
9
|
import { createEchoSchema } from '@dxos/echo/testing';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
@@ -24,7 +24,7 @@ export type FieldEditorDebugObjects = {
|
|
|
24
24
|
|
|
25
25
|
const useTestProjection = () => {
|
|
26
26
|
return useMemo(() => {
|
|
27
|
-
const schema = createEchoSchema(Example);
|
|
27
|
+
const schema = createEchoSchema(Type.getSchema(Example));
|
|
28
28
|
const view = ViewModel.make({
|
|
29
29
|
name: 'Test',
|
|
30
30
|
query: Query.select(Filter.type(Example)),
|
|
@@ -15,7 +15,10 @@ import { type FieldEditorDebugObjects } from './FieldEditor.stories';
|
|
|
15
15
|
const { Default } = composeStories(stories);
|
|
16
16
|
|
|
17
17
|
describe('FieldEditor', () => {
|
|
18
|
-
afterEach(() => {
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
// Flush pending React scheduler work before teardown to prevent
|
|
20
|
+
// "window is not defined" errors from setImmediate callbacks firing after happy-dom cleanup.
|
|
21
|
+
await act(async () => {});
|
|
19
22
|
cleanup();
|
|
20
23
|
});
|
|
21
24
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { Obj, type
|
|
8
|
-
import {
|
|
7
|
+
import { Filter, Obj, type Registry, Type, type View } from '@dxos/echo';
|
|
8
|
+
import { Format, FormatEnums, formatToType } from '@dxos/echo/internal';
|
|
9
9
|
import { type SchemaProperty } from '@dxos/effect';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { useAsyncEffect, useTranslation } from '@dxos/react-ui';
|
|
@@ -24,7 +24,7 @@ import { Form, type FormFieldMap, type FormRootProps, SelectField, SelectOptionF
|
|
|
24
24
|
export type FieldEditorProps = {
|
|
25
25
|
projection: ProjectionModel;
|
|
26
26
|
field: View.FieldType;
|
|
27
|
-
registry?:
|
|
27
|
+
registry?: Registry.Registry;
|
|
28
28
|
view?: Obj.Unknown;
|
|
29
29
|
onSave: () => void;
|
|
30
30
|
onCancel?: () => void;
|
|
@@ -38,24 +38,26 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
|
|
|
38
38
|
const [props, setProps] = useState<PropertyType>(projection.getFieldProjection(field.id).props);
|
|
39
39
|
useEffect(() => setProps(projection.getFieldProjection(field.id).props), [field, projection]);
|
|
40
40
|
|
|
41
|
-
const [schemas, setSchemas] = useState<
|
|
41
|
+
const [schemas, setSchemas] = useState<Type.Type[]>([]);
|
|
42
42
|
useAsyncEffect(async () => {
|
|
43
43
|
if (!registry) {
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const subscription = registry
|
|
47
|
+
const subscription = registry
|
|
48
|
+
.query(Filter.type(Type.Type))
|
|
49
|
+
.subscribe((query) => setSchemas(query.results), { fire: true });
|
|
48
50
|
|
|
49
51
|
// TODO(dmaretskyi): This shouldn't be needed.
|
|
50
|
-
const schemas = await registry.query().run();
|
|
52
|
+
const schemas = await registry.query(Filter.type(Type.Type)).run();
|
|
51
53
|
setSchemas(schemas);
|
|
52
54
|
|
|
53
55
|
return () => subscription?.();
|
|
54
56
|
}, [registry]);
|
|
55
57
|
|
|
56
|
-
const [referenceSchema, setReferenceSchema] = useState<
|
|
58
|
+
const [referenceSchema, setReferenceSchema] = useState<Type.Type>();
|
|
57
59
|
useEffect(() => {
|
|
58
|
-
setReferenceSchema(schemas.find((schema) => schema
|
|
60
|
+
setReferenceSchema(schemas.find((schema) => Type.getTypename(schema) === props?.referenceSchema));
|
|
59
61
|
}, [schemas, props?.referenceSchema]);
|
|
60
62
|
|
|
61
63
|
// TODO(burdon): Need to wrap otherwise throws error:
|
|
@@ -76,9 +78,12 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
|
|
|
76
78
|
['referenceSchema' satisfies keyof PropertyType]: (props) => (
|
|
77
79
|
<SelectField
|
|
78
80
|
{...props}
|
|
79
|
-
options={schemas
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
options={schemas
|
|
82
|
+
.map((schema) => Type.getTypename(schema))
|
|
83
|
+
.filter((typename): typename is string => typename != null)
|
|
84
|
+
.map((typename) => ({
|
|
85
|
+
value: typename,
|
|
86
|
+
}))}
|
|
82
87
|
/>
|
|
83
88
|
),
|
|
84
89
|
['referencePath' satisfies keyof PropertyType]: (props) => (
|
|
@@ -86,7 +91,7 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
|
|
|
86
91
|
{...props}
|
|
87
92
|
options={
|
|
88
93
|
referenceSchema
|
|
89
|
-
? getFormProperties(referenceSchema.ast)
|
|
94
|
+
? getFormProperties(Type.getSchema(referenceSchema).ast)
|
|
90
95
|
.sort((a, b) => a.name.toString().localeCompare(b.name.toString()))
|
|
91
96
|
.map((p) => ({ value: p.name.toString() }))
|
|
92
97
|
: []
|
|
@@ -112,8 +117,8 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
|
|
|
112
117
|
});
|
|
113
118
|
|
|
114
119
|
setReferenceSchema((prev) => {
|
|
115
|
-
if (_props.referenceSchema !== prev
|
|
116
|
-
const newSchema = schemas.find((schema) => schema
|
|
120
|
+
if (_props.referenceSchema !== (prev ? Type.getTypename(prev) : undefined)) {
|
|
121
|
+
const newSchema = schemas.find((schema) => Type.getTypename(schema) === _props.referenceSchema);
|
|
117
122
|
if (newSchema) {
|
|
118
123
|
return newSchema;
|
|
119
124
|
}
|
|
@@ -6,7 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import React, { useCallback, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import { Annotation, Format, Obj, Ref, Tag, Type } from '@dxos/echo';
|
|
9
|
+
import { DXN, Annotation, Format, Obj, Ref, Tag, Type } from '@dxos/echo';
|
|
10
10
|
import { type AnyProperties } from '@dxos/echo/internal';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import { useSpaces } from '@dxos/react-client/echo';
|
|
@@ -21,14 +21,9 @@ import { type ExcludeId, Form, type FormRootProps, omitId } from './Form';
|
|
|
21
21
|
|
|
22
22
|
const Organization = Schema.Struct({
|
|
23
23
|
name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
|
|
24
|
-
}).pipe(
|
|
25
|
-
Type.object({
|
|
26
|
-
typename: 'com.example.type.organization',
|
|
27
|
-
version: '0.1.0',
|
|
28
|
-
}),
|
|
29
|
-
);
|
|
24
|
+
}).pipe(Type.makeObject(DXN.make('com.example.type.organization', '0.1.0')));
|
|
30
25
|
|
|
31
|
-
export
|
|
26
|
+
export type Organization = Type.InstanceType<typeof Organization>;
|
|
32
27
|
|
|
33
28
|
const Person = Schema.Struct({
|
|
34
29
|
name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
|
|
@@ -51,6 +46,9 @@ const Person = Schema.Struct({
|
|
|
51
46
|
status: Schema.optional(Schema.Literal('active', 'inactive').annotations({ title: 'Status' })),
|
|
52
47
|
notes: Schema.optional(Format.Text.annotations({ title: 'Notes' })),
|
|
53
48
|
location: Schema.optional(Format.GeoPoint.annotations({ title: 'Location' })),
|
|
49
|
+
birthday: Schema.optional(Format.DateOnly.annotations({ title: 'Birthday' })),
|
|
50
|
+
meetingAt: Schema.optional(Format.DateTime.annotations({ title: 'Next meeting' })),
|
|
51
|
+
reminderAt: Schema.optional(Format.TimeOnly.annotations({ title: 'Reminder time' })),
|
|
54
52
|
tasks: Schema.optional(Schema.Array(Schema.String).annotations({ title: 'Tasks' })),
|
|
55
53
|
locations: Schema.optional(Schema.Array(Format.GeoPoint).annotations({ title: 'Locations' })),
|
|
56
54
|
identities: Schema.optional(
|
|
@@ -63,22 +61,15 @@ const Person = Schema.Struct({
|
|
|
63
61
|
title: 'Identities',
|
|
64
62
|
}),
|
|
65
63
|
),
|
|
66
|
-
}).pipe(
|
|
67
|
-
Type.object({
|
|
68
|
-
typename: 'org.dxos.type.person', // TODO(burdon): Change all types to /schema
|
|
69
|
-
version: '0.1.0',
|
|
70
|
-
}),
|
|
71
|
-
);
|
|
64
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.type.person', '0.1.0')));
|
|
72
65
|
|
|
73
|
-
export
|
|
66
|
+
export type Person = Type.InstanceType<typeof Person>;
|
|
74
67
|
|
|
75
68
|
type DefaultStoryProps<T extends AnyProperties> = {
|
|
76
69
|
schema?: Schema.Schema<T>;
|
|
77
|
-
debug?: boolean;
|
|
78
70
|
} & FormRootProps<T>;
|
|
79
71
|
|
|
80
72
|
const DefaultStory = <T extends AnyProperties = AnyProperties>({
|
|
81
|
-
debug,
|
|
82
73
|
schema,
|
|
83
74
|
values: valuesProp,
|
|
84
75
|
...props
|
|
@@ -86,7 +77,6 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
|
|
|
86
77
|
const [values, setValues] = useState<Partial<T>>(valuesProp ?? {});
|
|
87
78
|
const spaces = useSpaces();
|
|
88
79
|
const space = spaces[0];
|
|
89
|
-
|
|
90
80
|
const handleSave = useCallback<NonNullable<FormRootProps<T>['onSave']>>((values) => {
|
|
91
81
|
log.info('save', { values, meta });
|
|
92
82
|
setValues(values);
|
|
@@ -105,7 +95,6 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
|
|
|
105
95
|
<Tooltip.Provider>
|
|
106
96
|
<TestLayout json={{ values, schema: schema?.ast }}>
|
|
107
97
|
<Form.Root
|
|
108
|
-
debug={debug}
|
|
109
98
|
schema={schema}
|
|
110
99
|
defaultValues={values}
|
|
111
100
|
db={space.db}
|
|
@@ -113,7 +102,7 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
|
|
|
113
102
|
onCancel={handleCancel}
|
|
114
103
|
{...props}
|
|
115
104
|
>
|
|
116
|
-
<Form.Viewport>
|
|
105
|
+
<Form.Viewport scroll>
|
|
117
106
|
<Form.Content>
|
|
118
107
|
<Form.Section label='Section' description='This is a section' />
|
|
119
108
|
<Form.FieldSet />
|
|
@@ -155,36 +144,41 @@ export default meta;
|
|
|
155
144
|
|
|
156
145
|
type Story<T extends AnyProperties> = StoryObj<DefaultStoryProps<T>>;
|
|
157
146
|
|
|
147
|
+
const PersonSchema = Type.getSchema(Person);
|
|
148
|
+
|
|
158
149
|
const values: Partial<Person> = {
|
|
159
150
|
name: 'Alice',
|
|
160
151
|
location: [40.7128, -74.006],
|
|
161
152
|
tasks: ['task 1', 'task 2'],
|
|
153
|
+
birthday: '1990-05-12',
|
|
154
|
+
meetingAt: '2026-06-01T15:30:00.000Z',
|
|
155
|
+
reminderAt: '09:00:00',
|
|
162
156
|
};
|
|
163
157
|
|
|
164
|
-
export const Default: Story<ExcludeId<typeof
|
|
158
|
+
export const Default: Story<ExcludeId<typeof PersonSchema>> = {
|
|
165
159
|
args: {
|
|
166
|
-
schema: omitId(
|
|
160
|
+
schema: omitId(PersonSchema),
|
|
167
161
|
values,
|
|
168
162
|
autoSave: true,
|
|
169
163
|
},
|
|
170
164
|
};
|
|
171
165
|
|
|
172
|
-
export const Readonly: Story<ExcludeId<typeof
|
|
166
|
+
export const Readonly: Story<ExcludeId<typeof PersonSchema>> = {
|
|
173
167
|
args: {
|
|
174
|
-
schema: omitId(
|
|
168
|
+
schema: omitId(PersonSchema),
|
|
175
169
|
values,
|
|
176
170
|
readonly: true,
|
|
177
171
|
},
|
|
178
172
|
};
|
|
179
173
|
|
|
180
|
-
export const Static: Story<ExcludeId<typeof
|
|
174
|
+
export const Static: Story<ExcludeId<typeof PersonSchema>> = {
|
|
181
175
|
args: {
|
|
182
|
-
schema: omitId(
|
|
176
|
+
schema: omitId(PersonSchema),
|
|
183
177
|
values,
|
|
184
178
|
layout: 'static',
|
|
185
179
|
},
|
|
186
180
|
};
|
|
187
181
|
|
|
188
|
-
export const Empty: Story<ExcludeId<typeof
|
|
182
|
+
export const Empty: Story<ExcludeId<typeof PersonSchema>> = {
|
|
189
183
|
args: {},
|
|
190
184
|
};
|
|
@@ -8,18 +8,22 @@ import * as Schema from 'effect/Schema';
|
|
|
8
8
|
import * as SchemaAST from 'effect/SchemaAST';
|
|
9
9
|
import React, { type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
|
|
10
10
|
|
|
11
|
-
import { Annotation as EchoAnnotation } from '@dxos/echo';
|
|
11
|
+
import { Annotation as EchoAnnotation, Type } from '@dxos/echo';
|
|
12
12
|
import { type AnyProperties } from '@dxos/echo/internal';
|
|
13
13
|
import { createJsonPath, getValue as getValue$ } from '@dxos/effect';
|
|
14
14
|
import {
|
|
15
|
+
Column,
|
|
16
|
+
type ColumnRootProps,
|
|
15
17
|
IconButton,
|
|
16
18
|
type IconButtonProps,
|
|
19
|
+
Input,
|
|
17
20
|
ScrollArea,
|
|
18
21
|
type ThemedClassName,
|
|
19
22
|
useMergeRefs,
|
|
20
23
|
useTranslation,
|
|
21
24
|
} from '@dxos/react-ui';
|
|
22
|
-
import { composable, composableProps,
|
|
25
|
+
import { composable, composableProps, withColumn } from '@dxos/react-ui';
|
|
26
|
+
import { mx } from '@dxos/ui-theme';
|
|
23
27
|
|
|
24
28
|
import { translationKey } from '#translations';
|
|
25
29
|
|
|
@@ -35,15 +39,31 @@ import {
|
|
|
35
39
|
FormFieldSet as NaturalFormFieldSet,
|
|
36
40
|
type FormFieldSetProps as NaturalFormFieldSetProps,
|
|
37
41
|
} from './FormFieldSet';
|
|
42
|
+
import { FormTooltipsContext } from './FormTooltipsContext';
|
|
43
|
+
import { FormLayout as NaturalFormLayout } from './Layout';
|
|
44
|
+
import { type FormLayoutProps as NaturalFormLayoutProps } from './Layout/FormLayout';
|
|
38
45
|
|
|
39
46
|
// TODO(burdon): Move styles to form.ts (as with ui-theme).
|
|
40
47
|
|
|
41
48
|
// TODO(burdon): Reconcile with @dxos/echo.
|
|
42
|
-
export type ExcludeId<S extends Schema.Schema.AnyNoContext> = Omit<
|
|
49
|
+
export type ExcludeId<S extends Schema.Schema.AnyNoContext | Type.AnyEntity> = Omit<
|
|
50
|
+
S extends Type.AnyEntity
|
|
51
|
+
? Type.InstanceType<S>
|
|
52
|
+
: S extends Schema.Schema.AnyNoContext
|
|
53
|
+
? Schema.Schema.Type<S>
|
|
54
|
+
: never,
|
|
55
|
+
'id'
|
|
56
|
+
>;
|
|
43
57
|
|
|
44
58
|
// TODO(burdon): Move to @dxos/schema (re-export here).
|
|
45
|
-
export const omitId = <S extends Schema.Schema.AnyNoContext
|
|
46
|
-
|
|
59
|
+
export const omitId = <S extends Schema.Schema.AnyNoContext | Type.AnyEntity>(
|
|
60
|
+
schemaOrType: S,
|
|
61
|
+
): Schema.Schema<ExcludeId<S>, ExcludeId<S>> => {
|
|
62
|
+
const schema = Type.isType(schemaOrType)
|
|
63
|
+
? Type.getSchema(schemaOrType)
|
|
64
|
+
: (schemaOrType as Schema.Schema.AnyNoContext);
|
|
65
|
+
return schema.pipe(Schema.omit('id')) as any;
|
|
66
|
+
};
|
|
47
67
|
|
|
48
68
|
/**
|
|
49
69
|
* Drop fields annotated with `FormInputAnnotation.set(false)` from a schema so
|
|
@@ -70,9 +90,10 @@ type FormContextValue<T extends AnyProperties = any> = {
|
|
|
70
90
|
form: FormHandler<T>;
|
|
71
91
|
|
|
72
92
|
/**
|
|
73
|
-
* Show
|
|
93
|
+
* Show field tooltips (currently: JSON path on each label). Defaults to
|
|
94
|
+
* `true`; pass `false` to suppress.
|
|
74
95
|
*/
|
|
75
|
-
|
|
96
|
+
tooltips?: boolean;
|
|
76
97
|
|
|
77
98
|
/**
|
|
78
99
|
* Testing.
|
|
@@ -169,9 +190,11 @@ const FormRoot = <T extends AnyProperties = AnyProperties>({
|
|
|
169
190
|
const form = useFormHandler({ schema, values, onSave, onCancel, ...props });
|
|
170
191
|
|
|
171
192
|
return (
|
|
172
|
-
<
|
|
173
|
-
{
|
|
174
|
-
|
|
193
|
+
<FormTooltipsContext.Provider value={props.tooltips ?? true}>
|
|
194
|
+
<FormContextProvider form={form} {...props}>
|
|
195
|
+
{children}
|
|
196
|
+
</FormContextProvider>
|
|
197
|
+
</FormTooltipsContext.Provider>
|
|
175
198
|
);
|
|
176
199
|
};
|
|
177
200
|
|
|
@@ -183,15 +206,32 @@ FormRoot.displayName = 'Form.Root';
|
|
|
183
206
|
|
|
184
207
|
const FORM_VIEWPORT_NAME = 'Form.Viewport';
|
|
185
208
|
|
|
186
|
-
type FormViewportProps = {};
|
|
209
|
+
type FormViewportProps = { scroll?: boolean; gutter?: ColumnRootProps['gutter'] };
|
|
210
|
+
|
|
211
|
+
// The viewing window: owns the gutter Column (chrome/side-padding). Content-height by default;
|
|
212
|
+
// `scroll` makes it fill its parent and scroll (the gutter then hosts the scrollbar).
|
|
213
|
+
const FormViewport = composable<HTMLDivElement, FormViewportProps>(
|
|
214
|
+
({ children, scroll, gutter = 'sm', ...props }, forwardedRef) => {
|
|
215
|
+
// Span the full width when nested inside another Column grid (e.g. Card.Root) instead of
|
|
216
|
+
// landing in a single narrow track.
|
|
217
|
+
const span = '[.dx-column-root_&]:col-span-full';
|
|
218
|
+
if (scroll) {
|
|
219
|
+
return (
|
|
220
|
+
<Column.Root gutter={gutter} classNames={['dx-expander', span]}>
|
|
221
|
+
<ScrollArea.Root {...composableProps(props)} orientation='vertical' centered padding thin ref={forwardedRef}>
|
|
222
|
+
<ScrollArea.Viewport>{children}</ScrollArea.Viewport>
|
|
223
|
+
</ScrollArea.Root>
|
|
224
|
+
</Column.Root>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
187
227
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
228
|
+
return (
|
|
229
|
+
<Column.Root {...composableProps(props)} gutter={gutter} classNames={['w-full min-w-0', span]} ref={forwardedRef}>
|
|
230
|
+
{children}
|
|
231
|
+
</Column.Root>
|
|
232
|
+
);
|
|
233
|
+
},
|
|
234
|
+
);
|
|
195
235
|
|
|
196
236
|
FormViewport.displayName = FORM_VIEWPORT_NAME;
|
|
197
237
|
|
|
@@ -203,6 +243,7 @@ const FORM_CONTENT_NAME = 'Form.Content';
|
|
|
203
243
|
|
|
204
244
|
type FormContentProps = ThemedClassName<PropsWithChildren<{}>>;
|
|
205
245
|
|
|
246
|
+
// The viewed body: centered in the viewport's gutter. Pure body — the gutter Column is owned by `Form.Viewport`.
|
|
206
247
|
const FormContent = composable<HTMLDivElement, FormContentProps>(({ children, ...props }, forwardedRef) => {
|
|
207
248
|
const { form, testId } = useFormContext(FORM_CONTENT_NAME);
|
|
208
249
|
const localRef = useRef<HTMLDivElement>(null);
|
|
@@ -213,7 +254,7 @@ const FormContent = composable<HTMLDivElement, FormContentProps>(({ children, ..
|
|
|
213
254
|
<div
|
|
214
255
|
{...composableProps(props, {
|
|
215
256
|
role: 'form',
|
|
216
|
-
classNames: mx(withColumn.center(), 'flex flex-col w-full
|
|
257
|
+
classNames: mx(withColumn.center(), 'flex flex-col w-full'),
|
|
217
258
|
})}
|
|
218
259
|
data-testid={testId}
|
|
219
260
|
ref={mergedRef}
|
|
@@ -241,6 +282,26 @@ const FormFieldSet = (props: FormFieldSetProps) => {
|
|
|
241
282
|
|
|
242
283
|
FormFieldSet.displayName = FORM_FIELDSET_NAME;
|
|
243
284
|
|
|
285
|
+
//
|
|
286
|
+
// Layout
|
|
287
|
+
//
|
|
288
|
+
|
|
289
|
+
const FORM_LAYOUT_NAME = 'Form.Layout';
|
|
290
|
+
|
|
291
|
+
type FormLayoutProps = Omit<NaturalFormLayoutProps, 'schema'> & { schema?: NaturalFormLayoutProps['schema'] };
|
|
292
|
+
|
|
293
|
+
const FormLayout = ({ schema, ...props }: FormLayoutProps) => {
|
|
294
|
+
const { form, ...contextProps } = useFormContext(FORM_LAYOUT_NAME);
|
|
295
|
+
const resolvedSchema = schema ?? form.schema;
|
|
296
|
+
if (!resolvedSchema) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return <NaturalFormLayout schema={resolvedSchema} {...contextProps} {...props} />;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
FormLayout.displayName = FORM_LAYOUT_NAME;
|
|
304
|
+
|
|
244
305
|
//
|
|
245
306
|
// Actions
|
|
246
307
|
//
|
|
@@ -358,6 +419,29 @@ const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
|
|
|
358
419
|
|
|
359
420
|
FormSubmit.displayName = FORM_SUBMIT_NAME;
|
|
360
421
|
|
|
422
|
+
//
|
|
423
|
+
// Error
|
|
424
|
+
//
|
|
425
|
+
|
|
426
|
+
const FORM_ERROR_NAME = 'Form.Error';
|
|
427
|
+
|
|
428
|
+
type FormErrorProps = ThemedClassName<PropsWithChildren>;
|
|
429
|
+
|
|
430
|
+
/** Form-level error/validation message (e.g. a failed submit), styled via the error valence. */
|
|
431
|
+
const FormError = ({ children, classNames }: FormErrorProps) => {
|
|
432
|
+
if (!children) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return (
|
|
437
|
+
<Input.Root validationValence='error'>
|
|
438
|
+
<Input.Validation classNames={classNames}>{children}</Input.Validation>
|
|
439
|
+
</Input.Root>
|
|
440
|
+
);
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
FormError.displayName = FORM_ERROR_NAME;
|
|
444
|
+
|
|
361
445
|
//
|
|
362
446
|
// Form
|
|
363
447
|
// https://www.radix-ui.com/primitives/docs/guides/composition
|
|
@@ -369,9 +453,11 @@ export const Form = {
|
|
|
369
453
|
Content: FormContent,
|
|
370
454
|
Section: FormSection,
|
|
371
455
|
FieldSet: FormFieldSet,
|
|
456
|
+
Layout: FormLayout,
|
|
372
457
|
Label: FormFieldLabel,
|
|
373
458
|
Actions: FormActions,
|
|
374
459
|
Submit: FormSubmit,
|
|
460
|
+
Error: FormError,
|
|
375
461
|
};
|
|
376
462
|
|
|
377
463
|
export { useFormContext, useFormValues, useFormFieldState };
|
|
@@ -382,6 +468,7 @@ export type {
|
|
|
382
468
|
FormContentProps,
|
|
383
469
|
FormSectionProps,
|
|
384
470
|
FormFieldSetProps,
|
|
471
|
+
FormLayoutProps,
|
|
385
472
|
FormFieldLabelProps,
|
|
386
473
|
FormActionsProps,
|
|
387
474
|
FormSubmitProps,
|