@dxos/react-ui-form 0.7.4 → 0.7.5-labs.071a3e2
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 +841 -521
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +877 -571
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +841 -521
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- 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/Form/ArrayField.d.ts +16 -0
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/Defaults.d.ts +5 -6
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +14 -17
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +7 -1
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormActions.d.ts +7 -0
- package/dist/types/src/components/Form/FormActions.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormContent.d.ts +26 -0
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormContext.d.ts +20 -0
- package/dist/types/src/components/Form/FormContext.d.ts.map +1 -0
- package/dist/types/src/components/Form/Input.d.ts +5 -6
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +4 -0
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -0
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +4 -0
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -0
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +7 -0
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +6 -0
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -0
- package/dist/types/src/components/Form/custom/index.d.ts +4 -0
- package/dist/types/src/components/Form/custom/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/factory.d.ts +2 -2
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/Form/index.d.ts +2 -2
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +6 -5
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/deprecated/FormInput.d.ts +5 -0
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
- package/dist/types/src/hooks/useForm.d.ts +13 -15
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +6 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +28 -27
- package/src/components/FieldEditor/FieldEditor.stories.tsx +3 -3
- package/src/components/FieldEditor/FieldEditor.tsx +77 -51
- package/src/components/Form/ArrayField.tsx +111 -0
- package/src/components/Form/Defaults.tsx +20 -33
- package/src/components/Form/Form.stories.tsx +48 -6
- package/src/components/Form/Form.tsx +49 -329
- package/src/components/Form/FormActions.tsx +41 -0
- package/src/components/Form/FormContent.tsx +173 -0
- package/src/components/Form/FormContext.tsx +48 -0
- package/src/components/Form/Input.tsx +5 -6
- package/src/components/Form/custom/GeoPointInput.tsx +44 -0
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +58 -0
- package/src/components/Form/custom/SelectOptionsInput.tsx +192 -0
- package/src/components/Form/custom/TupleInput.tsx +49 -0
- package/src/components/Form/custom/index.ts +7 -0
- package/src/components/Form/factory.tsx +4 -7
- package/src/components/Form/index.ts +3 -2
- package/src/components/ViewEditor/ViewEditor.stories.tsx +30 -8
- package/src/components/ViewEditor/ViewEditor.tsx +18 -10
- package/src/components/deprecated/FormInput.tsx +20 -7
- package/src/hooks/useForm.ts +67 -79
- package/src/translations.ts +8 -0
- package/dist/types/src/components/Form/Custom.d.ts +0 -5
- package/dist/types/src/components/Form/Custom.d.ts.map +0 -1
- package/src/components/Form/Custom.tsx +0 -65
|
@@ -1,45 +1,43 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
3
|
-
import { type SimpleType, type S } from '@dxos/effect';
|
|
1
|
+
import { type BaseObject } from '@dxos/echo-schema';
|
|
2
|
+
import { type S, type SimpleType, type JsonPath } from '@dxos/effect';
|
|
4
3
|
import { type ValidationError } from '@dxos/schema';
|
|
5
4
|
import { type MaybePromise } from '@dxos/util';
|
|
6
|
-
export type FormPath<T extends object = {}> = PropertyKey<T> | `${PropertyKey<T>}.${number}`;
|
|
7
5
|
/**
|
|
8
6
|
* Return type from `useForm` hook.
|
|
9
7
|
*/
|
|
10
8
|
export type FormHandler<T extends BaseObject> = {
|
|
11
|
-
values: T
|
|
12
|
-
errors: Record<
|
|
13
|
-
touched: Record<
|
|
14
|
-
changed: Record<
|
|
9
|
+
values: Partial<T>;
|
|
10
|
+
errors: Record<JsonPath, string>;
|
|
11
|
+
touched: Record<JsonPath, boolean>;
|
|
12
|
+
changed: Record<JsonPath, boolean>;
|
|
15
13
|
canSave: boolean;
|
|
16
14
|
handleSave: () => void;
|
|
17
|
-
getStatus: (
|
|
15
|
+
getStatus: (path: string | (string | number)[]) => {
|
|
18
16
|
status?: 'error';
|
|
19
17
|
error?: string;
|
|
20
18
|
};
|
|
21
|
-
getValue: <V>(
|
|
22
|
-
onValueChange: <V>(
|
|
23
|
-
|
|
19
|
+
getValue: <V>(path: (string | number)[]) => V | undefined;
|
|
20
|
+
onValueChange: <V>(path: (string | number)[], type: SimpleType, value: V) => void;
|
|
21
|
+
onTouched: (path: (string | number)[]) => void;
|
|
24
22
|
};
|
|
25
23
|
/**
|
|
26
24
|
* Hook options.
|
|
27
25
|
*/
|
|
28
26
|
export interface FormOptions<T extends BaseObject> {
|
|
29
27
|
/**
|
|
30
|
-
* Effect schema.
|
|
28
|
+
* Effect schema (Type literal).
|
|
31
29
|
*/
|
|
32
30
|
schema: S.Schema<T>;
|
|
33
31
|
/**
|
|
34
32
|
* Initial values (which may not pass validation).
|
|
35
33
|
*/
|
|
36
|
-
initialValues: T
|
|
34
|
+
initialValues: Partial<T>;
|
|
37
35
|
/**
|
|
38
36
|
* Callback for value changes. Note: This is called even when values are invalid.
|
|
39
37
|
* Sometimes the parent component may want to know about changes even if the form is
|
|
40
38
|
* in an invalid state.
|
|
41
39
|
*/
|
|
42
|
-
onValuesChanged?: (values: T) => void;
|
|
40
|
+
onValuesChanged?: (values: Partial<T>) => void;
|
|
43
41
|
/**
|
|
44
42
|
* Custom validation function that runs only after schema validation passes.
|
|
45
43
|
* Use this for complex validation logic that can't be expressed in the schema.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useForm.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useForm.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useForm.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useForm.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAO,KAAK,CAAC,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAA4C,MAAM,cAAc,CAAC;AAGrH,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,UAAU,IAAI;IAK9C,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,IAAI,CAAC;IAMvB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxF,QAAQ,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;IAC1D,aAAa,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAClF,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,UAAU;IAC/C;;OAEG;IACH,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAEpB;;OAEG;IACH,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAE1B;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAE/C;;;;OAIG;IAEH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,eAAe,EAAE,GAAG,SAAS,CAAC;IAE1D;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAE5E;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;KAAE,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;CAC1F;AAED;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,UAAU,4EAOzC,WAAW,CAAC,CAAC,CAAC,KAAG,WAAW,CAAC,CAAC,CA8IhC,CAAC"}
|
|
@@ -30,6 +30,7 @@ declare const _default: {
|
|
|
30
30
|
'format ref': string;
|
|
31
31
|
'format regex': string;
|
|
32
32
|
'format string': string;
|
|
33
|
+
'format single-select': string;
|
|
33
34
|
'format text': string;
|
|
34
35
|
'format time': string;
|
|
35
36
|
'format timestamp': string;
|
|
@@ -39,6 +40,11 @@ declare const _default: {
|
|
|
39
40
|
'format uuid': string;
|
|
40
41
|
'placeholder latitude': string;
|
|
41
42
|
'placeholder longitude': string;
|
|
43
|
+
'select option label': string;
|
|
44
|
+
'select option label placeholder': string;
|
|
45
|
+
'select option color': string;
|
|
46
|
+
'select option delete': string;
|
|
47
|
+
'select option add': string;
|
|
42
48
|
};
|
|
43
49
|
};
|
|
44
50
|
}[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,kBAAkB,CAAC
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE9C,wBA4DE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"5.7.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-form",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5-labs.071a3e2",
|
|
4
4
|
"description": "Data components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -25,49 +25,50 @@
|
|
|
25
25
|
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
|
|
26
26
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
|
27
27
|
"@effect/schema": "^0.75.5",
|
|
28
|
-
"@radix-ui/react-context": "
|
|
28
|
+
"@radix-ui/react-context": "1.1.1",
|
|
29
29
|
"date-fns": "^3.3.1",
|
|
30
|
-
"effect": "^3.9.2",
|
|
31
30
|
"jsonpath": "^1.1.1",
|
|
32
|
-
"@dxos/async": "0.7.
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/echo-schema": "0.7.
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/log": "0.7.
|
|
41
|
-
"@dxos/react-client": "0.7.
|
|
42
|
-
"@dxos/react-ui-list": "0.7.
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/react-ui-types": "0.7.
|
|
45
|
-
"@dxos/
|
|
31
|
+
"@dxos/async": "0.7.5-labs.071a3e2",
|
|
32
|
+
"@dxos/debug": "0.7.5-labs.071a3e2",
|
|
33
|
+
"@dxos/echo-db": "0.7.5-labs.071a3e2",
|
|
34
|
+
"@dxos/echo-schema": "0.7.5-labs.071a3e2",
|
|
35
|
+
"@dxos/effect": "0.7.5-labs.071a3e2",
|
|
36
|
+
"@dxos/keys": "0.7.5-labs.071a3e2",
|
|
37
|
+
"@dxos/invariant": "0.7.5-labs.071a3e2",
|
|
38
|
+
"@dxos/live-object": "0.7.5-labs.071a3e2",
|
|
39
|
+
"@dxos/log": "0.7.5-labs.071a3e2",
|
|
40
|
+
"@dxos/react-client": "0.7.5-labs.071a3e2",
|
|
41
|
+
"@dxos/react-ui-list": "0.7.5-labs.071a3e2",
|
|
42
|
+
"@dxos/react-ui-pickers": "0.7.5-labs.071a3e2",
|
|
43
|
+
"@dxos/react-ui-types": "0.7.5-labs.071a3e2",
|
|
44
|
+
"@dxos/schema": "0.7.5-labs.071a3e2",
|
|
45
|
+
"@dxos/util": "0.7.5-labs.071a3e2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@phosphor-icons/react": "^2.1.5",
|
|
49
|
-
"@types/jsonpath": "
|
|
49
|
+
"@types/jsonpath": "^0.2.4",
|
|
50
50
|
"@types/react": "~18.2.0",
|
|
51
51
|
"@types/react-dom": "~18.2.0",
|
|
52
|
+
"effect": "^3.12.3",
|
|
52
53
|
"fast-check": "^3.19.0",
|
|
53
54
|
"react": "~18.2.0",
|
|
54
55
|
"react-dom": "~18.2.0",
|
|
55
56
|
"vite": "5.4.7",
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/react-ui-syntax-highlighter": "0.7.
|
|
59
|
-
"@dxos/
|
|
60
|
-
"@dxos/react-ui
|
|
61
|
-
"@dxos/storybook-utils": "0.7.
|
|
57
|
+
"@dxos/random": "0.7.5-labs.071a3e2",
|
|
58
|
+
"@dxos/echo-signals": "0.7.5-labs.071a3e2",
|
|
59
|
+
"@dxos/react-ui-syntax-highlighter": "0.7.5-labs.071a3e2",
|
|
60
|
+
"@dxos/react-ui-theme": "0.7.5-labs.071a3e2",
|
|
61
|
+
"@dxos/react-ui": "0.7.5-labs.071a3e2",
|
|
62
|
+
"@dxos/storybook-utils": "0.7.5-labs.071a3e2"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
64
65
|
"@effect/schema": "^0.75.5",
|
|
65
66
|
"@phosphor-icons/react": "^2.1.5",
|
|
66
|
-
"effect": "^3.
|
|
67
|
+
"effect": "^3.12.3",
|
|
67
68
|
"react": "~18.2.0",
|
|
68
69
|
"react-dom": "~18.2.0",
|
|
69
|
-
"@dxos/react-ui": "0.7.
|
|
70
|
-
"@dxos/react-ui-theme": "0.7.
|
|
70
|
+
"@dxos/react-ui": "0.7.5-labs.071a3e2",
|
|
71
|
+
"@dxos/react-ui-theme": "0.7.5-labs.071a3e2"
|
|
71
72
|
},
|
|
72
73
|
"publishConfig": {
|
|
73
74
|
"access": "public"
|
|
@@ -7,7 +7,7 @@ import '@dxos-theme';
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
8
|
import React from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import { ViewProjection } from '@dxos/schema';
|
|
13
13
|
import { TestSchema, testView } from '@dxos/schema/testing';
|
|
@@ -37,7 +37,7 @@ const meta: Meta<StoryProps> = {
|
|
|
37
37
|
title: 'ui/react-ui-form/FieldEditor',
|
|
38
38
|
component: FieldEditor,
|
|
39
39
|
render: DefaultStory,
|
|
40
|
-
decorators: [withLayout({ fullscreen: true }), withTheme],
|
|
40
|
+
decorators: [withLayout({ fullscreen: true, tooltips: true }), withTheme],
|
|
41
41
|
parameters: {
|
|
42
42
|
translations,
|
|
43
43
|
},
|
|
@@ -49,7 +49,7 @@ type Story = StoryObj<StoryProps>;
|
|
|
49
49
|
|
|
50
50
|
export const Default: Story = {
|
|
51
51
|
args: {
|
|
52
|
-
projection: new ViewProjection(
|
|
52
|
+
projection: new ViewProjection(createEchoSchema(TestSchema), testView),
|
|
53
53
|
view: testView,
|
|
54
54
|
field: testView.fields[0],
|
|
55
55
|
},
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
8
|
-
import { FormatEnum, FormatEnums, formatToType, type
|
|
7
|
+
import { type SchemaRegistry } from '@dxos/echo-db';
|
|
8
|
+
import { FormatEnum, FormatEnums, formatToType, type EchoSchema } from '@dxos/echo-schema';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
10
|
import { useTranslation } from '@dxos/react-ui';
|
|
11
11
|
import {
|
|
@@ -16,16 +16,17 @@ import {
|
|
|
16
16
|
type PropertyType,
|
|
17
17
|
type ViewType,
|
|
18
18
|
type ViewProjection,
|
|
19
|
+
type SchemaProperty,
|
|
19
20
|
} from '@dxos/schema';
|
|
20
21
|
|
|
21
22
|
import { translationKey } from '../../translations';
|
|
22
|
-
import { Form, type FormProps, SelectInput } from '../Form';
|
|
23
|
+
import { Form, type FormProps, type InputComponent, SelectInput, SelectOptionInput } from '../Form';
|
|
23
24
|
|
|
24
25
|
export type FieldEditorProps = {
|
|
25
26
|
view: ViewType;
|
|
26
27
|
projection: ViewProjection;
|
|
27
28
|
field: FieldType;
|
|
28
|
-
registry?:
|
|
29
|
+
registry?: SchemaRegistry;
|
|
29
30
|
onSave: () => void;
|
|
30
31
|
onCancel?: () => void;
|
|
31
32
|
};
|
|
@@ -38,15 +39,17 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
|
|
|
38
39
|
const [props, setProps] = useState<PropertyType>(projection.getFieldProjection(field.id).props);
|
|
39
40
|
useEffect(() => setProps(projection.getFieldProjection(field.id).props), [field, projection]);
|
|
40
41
|
|
|
41
|
-
const [schemas, setSchemas] = useState<
|
|
42
|
+
const [schemas, setSchemas] = useState<EchoSchema[]>([]);
|
|
42
43
|
useEffect(() => {
|
|
43
44
|
if (!registry) {
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
const subscription = registry.subscribe(setSchemas);
|
|
48
|
+
const subscription = registry.query().subscribe((query) => setSchemas(query.results), { fire: true });
|
|
49
|
+
|
|
50
|
+
// TODO(dmaretskyi): This shouldn't be needed.
|
|
48
51
|
const t = setTimeout(async () => {
|
|
49
|
-
const schemas = await registry.query();
|
|
52
|
+
const schemas = await registry.query().run();
|
|
50
53
|
setSchemas(schemas);
|
|
51
54
|
});
|
|
52
55
|
return () => {
|
|
@@ -55,24 +58,36 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
|
|
|
55
58
|
};
|
|
56
59
|
}, [registry]);
|
|
57
60
|
|
|
58
|
-
const [
|
|
61
|
+
const [referenceSchema, setReferenceSchema] = useState<EchoSchema>();
|
|
59
62
|
useEffect(() => {
|
|
60
|
-
|
|
63
|
+
setReferenceSchema(schemas.find((schema) => schema.typename === props?.referenceSchema));
|
|
61
64
|
}, [schemas, props?.referenceSchema]);
|
|
62
65
|
|
|
63
66
|
// TODO(burdon): Need to wrap otherwise throws error:
|
|
64
67
|
// Class constructor SchemaClass cannot be invoked without 'new'.
|
|
65
68
|
const [{ fieldSchema }, setFieldSchema] = useState({ fieldSchema: getFormatSchema(props?.format) });
|
|
69
|
+
|
|
66
70
|
const handleValuesChanged = useCallback<NonNullable<FormProps<PropertyType>['onValuesChanged']>>(
|
|
67
71
|
(_props) => {
|
|
68
|
-
// Update schema if format changed.
|
|
69
72
|
// TODO(burdon): Callback should pass `changed` to indicate which fields have changed.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
// TODO(Zaymon): Workout why old and new format values are the same sometimes even when
|
|
74
|
+
// selecting novel format values.
|
|
75
|
+
setFieldSchema((prev) => {
|
|
76
|
+
const fieldSchema = getFormatSchema(_props.format);
|
|
77
|
+
if (prev.fieldSchema === fieldSchema) {
|
|
78
|
+
return prev;
|
|
79
|
+
}
|
|
80
|
+
return { fieldSchema };
|
|
81
|
+
});
|
|
82
|
+
setReferenceSchema((prev) => {
|
|
83
|
+
if (_props.referenceSchema !== prev?.typename) {
|
|
84
|
+
const newSchema = schemas.find((schema) => schema.typename === _props.referenceSchema);
|
|
85
|
+
if (newSchema) {
|
|
86
|
+
return newSchema;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return prev;
|
|
90
|
+
});
|
|
76
91
|
|
|
77
92
|
setProps((props) => {
|
|
78
93
|
const type = formatToType[_props.format as keyof typeof formatToType];
|
|
@@ -80,10 +95,10 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
|
|
|
80
95
|
return { ...props, ..._props, type };
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
return
|
|
98
|
+
return _props as PropertyType;
|
|
84
99
|
});
|
|
85
100
|
},
|
|
86
|
-
[schemas, props],
|
|
101
|
+
[schemas, props.format, props.referenceSchema],
|
|
87
102
|
);
|
|
88
103
|
|
|
89
104
|
const handleValidate = useCallback<NonNullable<FormProps<PropertyType>['onValidate']>>(
|
|
@@ -114,6 +129,47 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
|
|
|
114
129
|
onSave();
|
|
115
130
|
}, [onSave]);
|
|
116
131
|
|
|
132
|
+
const Custom: Partial<Record<string, InputComponent>> = useMemo(
|
|
133
|
+
() => ({
|
|
134
|
+
['format' satisfies keyof PropertyType]: (props) => (
|
|
135
|
+
<SelectInput
|
|
136
|
+
{...props}
|
|
137
|
+
options={FormatEnums.filter((value) => value !== FormatEnum.None).map((value) => ({
|
|
138
|
+
value,
|
|
139
|
+
label: t(`format ${value}`),
|
|
140
|
+
}))}
|
|
141
|
+
/>
|
|
142
|
+
),
|
|
143
|
+
['referenceSchema' satisfies keyof PropertyType]: (props) => (
|
|
144
|
+
<SelectInput
|
|
145
|
+
{...props}
|
|
146
|
+
options={schemas.map((schema) => ({
|
|
147
|
+
value: schema.typename,
|
|
148
|
+
}))}
|
|
149
|
+
/>
|
|
150
|
+
),
|
|
151
|
+
['referencePath' satisfies keyof PropertyType]: (props) => (
|
|
152
|
+
<SelectInput
|
|
153
|
+
{...props}
|
|
154
|
+
options={
|
|
155
|
+
referenceSchema
|
|
156
|
+
? getSchemaProperties(referenceSchema.ast)
|
|
157
|
+
.sort(sortProperties)
|
|
158
|
+
.map((p) => ({ value: p.name }))
|
|
159
|
+
: []
|
|
160
|
+
}
|
|
161
|
+
/>
|
|
162
|
+
),
|
|
163
|
+
['options' satisfies keyof PropertyType]: (props) => <SelectOptionInput {...props} />,
|
|
164
|
+
}),
|
|
165
|
+
[t, schemas, referenceSchema],
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const propIsNotType = useCallback(
|
|
169
|
+
(props: SchemaProperty<PropertyType>[]) => props.filter((p) => p.name !== 'type'),
|
|
170
|
+
[],
|
|
171
|
+
);
|
|
172
|
+
|
|
117
173
|
if (!fieldSchema) {
|
|
118
174
|
log.warn('invalid format', { props });
|
|
119
175
|
return null;
|
|
@@ -125,43 +181,13 @@ export const FieldEditor = ({ view, projection, field, registry, onSave, onCance
|
|
|
125
181
|
autoFocus
|
|
126
182
|
values={props}
|
|
127
183
|
schema={fieldSchema}
|
|
128
|
-
filter={
|
|
184
|
+
filter={propIsNotType}
|
|
129
185
|
sort={['property', 'format']}
|
|
130
186
|
onValuesChanged={handleValuesChanged}
|
|
131
187
|
onValidate={handleValidate}
|
|
132
188
|
onSave={handleSave}
|
|
133
189
|
onCancel={handleCancel}
|
|
134
|
-
Custom={
|
|
135
|
-
['format' satisfies keyof PropertyType]: (props) => (
|
|
136
|
-
<SelectInput<PropertyType>
|
|
137
|
-
{...props}
|
|
138
|
-
options={FormatEnums.filter((value) => value !== FormatEnum.None).map((value) => ({
|
|
139
|
-
value,
|
|
140
|
-
label: t(`format ${value}`),
|
|
141
|
-
}))}
|
|
142
|
-
/>
|
|
143
|
-
),
|
|
144
|
-
['referenceSchema' satisfies keyof PropertyType]: (props) => (
|
|
145
|
-
<SelectInput<PropertyType>
|
|
146
|
-
{...props}
|
|
147
|
-
options={schemas.map((schema) => ({
|
|
148
|
-
value: schema.typename,
|
|
149
|
-
}))}
|
|
150
|
-
/>
|
|
151
|
-
),
|
|
152
|
-
['referencePath' satisfies keyof PropertyType]: (props) => (
|
|
153
|
-
<SelectInput<PropertyType>
|
|
154
|
-
{...props}
|
|
155
|
-
options={
|
|
156
|
-
schema
|
|
157
|
-
? getSchemaProperties(schema.schema.ast)
|
|
158
|
-
.sort(sortProperties)
|
|
159
|
-
.map((p) => ({ value: p.name }))
|
|
160
|
-
: []
|
|
161
|
-
}
|
|
162
|
-
/>
|
|
163
|
-
),
|
|
164
|
-
}}
|
|
190
|
+
Custom={Custom}
|
|
165
191
|
/>
|
|
166
192
|
);
|
|
167
193
|
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { pipe } from 'effect';
|
|
6
|
+
import { capitalize } from 'effect/String';
|
|
7
|
+
import React, { useCallback, useMemo } from 'react';
|
|
8
|
+
|
|
9
|
+
import { AST } from '@dxos/echo-schema';
|
|
10
|
+
import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from '@dxos/effect';
|
|
11
|
+
import { invariant } from '@dxos/invariant';
|
|
12
|
+
import { IconButton, useTranslation } from '@dxos/react-ui';
|
|
13
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
14
|
+
import { getSchemaProperties, type SchemaProperty } from '@dxos/schema';
|
|
15
|
+
|
|
16
|
+
import { type ComponentLookup } from './Form';
|
|
17
|
+
import { FormField } from './FormContent';
|
|
18
|
+
import { useFormValues, type FormInputStateProps } from './FormContext';
|
|
19
|
+
import { InputHeader, type InputComponent } from './Input';
|
|
20
|
+
import { translationKey } from '../../translations';
|
|
21
|
+
|
|
22
|
+
const padding = 'px-2';
|
|
23
|
+
|
|
24
|
+
type ArrayFieldProps = {
|
|
25
|
+
property: SchemaProperty<any>;
|
|
26
|
+
readonly?: boolean;
|
|
27
|
+
inputProps: FormInputStateProps;
|
|
28
|
+
path?: (string | number)[];
|
|
29
|
+
Custom?: Partial<Record<string, InputComponent>>;
|
|
30
|
+
lookupComponent?: ComponentLookup;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const ArrayField = ({ property, readonly, path, inputProps, Custom, lookupComponent }: ArrayFieldProps) => {
|
|
34
|
+
const { t } = useTranslation(translationKey);
|
|
35
|
+
const { ast, name, type, title } = property;
|
|
36
|
+
const values = useFormValues(path ?? []) as any[];
|
|
37
|
+
invariant(Array.isArray(values), `Values at path ${path?.join('.')} must be an array.`);
|
|
38
|
+
const label = title ?? pipe(name, capitalize);
|
|
39
|
+
|
|
40
|
+
const tupleType = findNode(ast, AST.isTupleType);
|
|
41
|
+
const elementType = (tupleType as AST.TupleType | undefined)?.rest[0]?.type;
|
|
42
|
+
|
|
43
|
+
const getDefaultObjectValue = (typeNode: AST.AST): any => {
|
|
44
|
+
const baseNode = findNode(typeNode, isDiscriminatedUnion);
|
|
45
|
+
const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode, AST.isTypeLiteral);
|
|
46
|
+
if (!typeLiteral) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Object.fromEntries(getSchemaProperties(typeLiteral, {}).map((prop) => [prop.name, prop.defaultValue]));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const newValue = useMemo(() => {
|
|
54
|
+
if (type === 'object' && elementType) {
|
|
55
|
+
return getDefaultObjectValue(elementType);
|
|
56
|
+
} else {
|
|
57
|
+
return SimpleType.getDefaultValue(type);
|
|
58
|
+
}
|
|
59
|
+
}, [type, elementType]);
|
|
60
|
+
|
|
61
|
+
const handleAdd = useCallback(() => {
|
|
62
|
+
inputProps.onValueChange(type, [...values, newValue]);
|
|
63
|
+
}, [type, elementType, inputProps, newValue, values]);
|
|
64
|
+
|
|
65
|
+
const handleRemove = useCallback(
|
|
66
|
+
(index: number) => {
|
|
67
|
+
const newValues = values.filter((_, i) => i !== index);
|
|
68
|
+
inputProps.onValueChange(type, newValues);
|
|
69
|
+
},
|
|
70
|
+
[type, inputProps, values],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (!elementType) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div role='none' className={mx(padding)}>
|
|
79
|
+
<InputHeader>{label}</InputHeader>
|
|
80
|
+
<div role='none' className='flex flex-col gap-1'>
|
|
81
|
+
{values.map((_value, index) => (
|
|
82
|
+
<div key={index} role='none' className='flex items-center gap-1'>
|
|
83
|
+
<div role='none' className='flex-1'>
|
|
84
|
+
<FormField
|
|
85
|
+
property={{
|
|
86
|
+
...property,
|
|
87
|
+
array: false, // NOTE(ZaymonFC): This breaks arrays of arrays but ¯\_(ツ)_/¯. Ping me if you need that.
|
|
88
|
+
ast: elementType,
|
|
89
|
+
}}
|
|
90
|
+
path={[...(path ?? []), index]}
|
|
91
|
+
readonly={readonly}
|
|
92
|
+
inline
|
|
93
|
+
Custom={Custom}
|
|
94
|
+
lookupComponent={lookupComponent}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
<IconButton
|
|
98
|
+
icon='ph--trash--regular'
|
|
99
|
+
iconOnly
|
|
100
|
+
label={t('button remove')}
|
|
101
|
+
onClick={() => handleRemove(index)}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
<div role='none' className='flex justify-between items-center plb-1'>
|
|
107
|
+
<IconButton icon='ph--plus--regular' iconOnly label={t('button add')} onClick={handleAdd} />
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -4,13 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { type BaseObject } from '@dxos/echo-schema';
|
|
8
7
|
import { Input, Select } from '@dxos/react-ui';
|
|
9
8
|
|
|
10
9
|
import { type InputProps, InputHeader } from './Input';
|
|
11
10
|
|
|
12
|
-
export const TextInput =
|
|
13
|
-
property,
|
|
11
|
+
export const TextInput = ({
|
|
14
12
|
type,
|
|
15
13
|
label,
|
|
16
14
|
inputOnly,
|
|
@@ -20,8 +18,8 @@ export const TextInput = <T extends BaseObject>({
|
|
|
20
18
|
getValue,
|
|
21
19
|
onValueChange,
|
|
22
20
|
onBlur,
|
|
23
|
-
}: InputProps
|
|
24
|
-
const { status, error } = getStatus
|
|
21
|
+
}: InputProps) => {
|
|
22
|
+
const { status, error } = getStatus();
|
|
25
23
|
|
|
26
24
|
return (
|
|
27
25
|
<Input.Root validationValence={status}>
|
|
@@ -33,8 +31,8 @@ export const TextInput = <T extends BaseObject>({
|
|
|
33
31
|
<Input.TextInput
|
|
34
32
|
disabled={disabled}
|
|
35
33
|
placeholder={placeholder}
|
|
36
|
-
value={getValue(
|
|
37
|
-
onChange={(event) => onValueChange(
|
|
34
|
+
value={getValue() ?? ''}
|
|
35
|
+
onChange={(event) => onValueChange(type, event.target.value)}
|
|
38
36
|
onBlur={onBlur}
|
|
39
37
|
/>
|
|
40
38
|
{inputOnly && <Input.Validation>{error}</Input.Validation>}
|
|
@@ -42,8 +40,7 @@ export const TextInput = <T extends BaseObject>({
|
|
|
42
40
|
);
|
|
43
41
|
};
|
|
44
42
|
|
|
45
|
-
export const NumberInput =
|
|
46
|
-
property,
|
|
43
|
+
export const NumberInput = ({
|
|
47
44
|
type,
|
|
48
45
|
label,
|
|
49
46
|
inputOnly,
|
|
@@ -53,8 +50,8 @@ export const NumberInput = <T extends BaseObject>({
|
|
|
53
50
|
getValue,
|
|
54
51
|
onValueChange,
|
|
55
52
|
onBlur,
|
|
56
|
-
}: InputProps
|
|
57
|
-
const { status, error } = getStatus
|
|
53
|
+
}: InputProps) => {
|
|
54
|
+
const { status, error } = getStatus();
|
|
58
55
|
|
|
59
56
|
return (
|
|
60
57
|
<Input.Root validationValence={status}>
|
|
@@ -67,8 +64,8 @@ export const NumberInput = <T extends BaseObject>({
|
|
|
67
64
|
type='number'
|
|
68
65
|
disabled={disabled}
|
|
69
66
|
placeholder={placeholder}
|
|
70
|
-
value={getValue(
|
|
71
|
-
onChange={(event) => onValueChange(
|
|
67
|
+
value={getValue() ?? 0}
|
|
68
|
+
onChange={(event) => onValueChange(type, event.target.value)}
|
|
72
69
|
onBlur={onBlur}
|
|
73
70
|
/>
|
|
74
71
|
{inputOnly && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
@@ -76,17 +73,11 @@ export const NumberInput = <T extends BaseObject>({
|
|
|
76
73
|
);
|
|
77
74
|
};
|
|
78
75
|
|
|
79
|
-
export const BooleanInput =
|
|
80
|
-
|
|
81
|
-
type,
|
|
82
|
-
label,
|
|
83
|
-
inputOnly,
|
|
84
|
-
getStatus,
|
|
85
|
-
getValue,
|
|
86
|
-
onValueChange,
|
|
87
|
-
}: InputProps<T>) => {
|
|
88
|
-
const { status, error } = getStatus?.(property);
|
|
76
|
+
export const BooleanInput = ({ type, label, inputOnly, getStatus, getValue, onValueChange }: InputProps) => {
|
|
77
|
+
const { status, error } = getStatus();
|
|
89
78
|
|
|
79
|
+
// TODO(burdon): Runtime error:
|
|
80
|
+
// Warning: Unknown event handler property `onCheckedChange`. It will be ignored.
|
|
90
81
|
return (
|
|
91
82
|
<Input.Root validationValence={status}>
|
|
92
83
|
{!inputOnly && (
|
|
@@ -94,21 +85,17 @@ export const BooleanInput = <T extends BaseObject>({
|
|
|
94
85
|
<Input.Label>{label}</Input.Label>
|
|
95
86
|
</InputHeader>
|
|
96
87
|
)}
|
|
97
|
-
<Input.Switch
|
|
98
|
-
checked={getValue<boolean>(property)}
|
|
99
|
-
onCheckedChange={(value) => onValueChange(property, type, value)}
|
|
100
|
-
/>
|
|
88
|
+
<Input.Switch checked={getValue()} onCheckedChange={(value) => onValueChange(type, value)} />
|
|
101
89
|
{inputOnly && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
102
90
|
</Input.Root>
|
|
103
91
|
);
|
|
104
92
|
};
|
|
105
93
|
|
|
106
|
-
export type SelectInputOptions
|
|
94
|
+
export type SelectInputOptions = InputProps & {
|
|
107
95
|
options?: Array<{ value: string | number; label?: string }>;
|
|
108
96
|
};
|
|
109
97
|
|
|
110
|
-
export const SelectInput =
|
|
111
|
-
property,
|
|
98
|
+
export const SelectInput = ({
|
|
112
99
|
type,
|
|
113
100
|
label,
|
|
114
101
|
inputOnly,
|
|
@@ -118,8 +105,8 @@ export const SelectInput = <T extends BaseObject>({
|
|
|
118
105
|
getStatus,
|
|
119
106
|
getValue,
|
|
120
107
|
onValueChange,
|
|
121
|
-
}: SelectInputOptions
|
|
122
|
-
const { status, error } = getStatus
|
|
108
|
+
}: SelectInputOptions) => {
|
|
109
|
+
const { status, error } = getStatus();
|
|
123
110
|
|
|
124
111
|
return (
|
|
125
112
|
<Input.Root validationValence={status}>
|
|
@@ -128,7 +115,7 @@ export const SelectInput = <T extends BaseObject>({
|
|
|
128
115
|
<Input.Label>{label}</Input.Label>
|
|
129
116
|
</InputHeader>
|
|
130
117
|
)}
|
|
131
|
-
<Select.Root value={getValue(
|
|
118
|
+
<Select.Root value={getValue()} onValueChange={(value) => onValueChange(type, value)}>
|
|
132
119
|
{/* TODO(burdon): Placeholder not working? */}
|
|
133
120
|
<Select.TriggerButton classNames='is-full' disabled={disabled} placeholder={placeholder} />
|
|
134
121
|
<Select.Portal>
|