@dxos/react-ui-form 0.8.4-main.e098934 → 0.8.4-main.e8ec1fe
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 +480 -300
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +480 -300
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +5 -5
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +5 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +4 -3
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +15 -15
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +5 -5
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContext.d.ts +2 -2
- package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts +1 -1
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/RefField.d.ts +10 -6
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +5 -5
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/factory.d.ts +1 -1
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +41 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +81 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +20 -7
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +10 -10
- 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/hooks/useForm.d.ts +2 -2
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +9 -3
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +4 -4
- package/dist/types/src/util/field.d.ts +1 -1
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +1 -1
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/refs.d.ts +1 -1
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +40 -39
- package/src/components/ControlSection/ControlSection.stories.tsx +4 -5
- package/src/components/ControlSection/ControlSection.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.stories.tsx +6 -5
- package/src/components/FieldEditor/FieldEditor.tsx +5 -2
- package/src/components/Form/ArrayField.tsx +4 -3
- package/src/components/Form/Defaults.tsx +1 -1
- package/src/components/Form/Form.stories.tsx +35 -35
- package/src/components/Form/Form.tsx +16 -6
- package/src/components/Form/FormActions.tsx +2 -2
- package/src/components/Form/FormContent.tsx +82 -22
- package/src/components/Form/FormContext.tsx +3 -3
- package/src/components/Form/Input.tsx +1 -1
- package/src/components/Form/RefField.tsx +48 -64
- package/src/components/Form/custom/GeoPointInput.tsx +1 -1
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +5 -6
- package/src/components/Form/custom/SelectOptionsInput.tsx +2 -2
- package/src/components/Form/factory.tsx +1 -1
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +207 -0
- package/src/components/ObjectPicker/ObjectPicker.tsx +161 -0
- package/src/components/ObjectPicker/index.ts +5 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +98 -55
- package/src/components/ViewEditor/ViewEditor.test.tsx +6 -5
- package/src/components/ViewEditor/ViewEditor.tsx +275 -231
- package/src/components/index.ts +1 -0
- package/src/components/testing.tsx +2 -2
- package/src/hooks/useForm.ts +7 -4
- package/src/hooks/useQueryRefOptions.ts +15 -6
- package/src/hooks/useRefQueryLookupHandler.ts +3 -3
- package/src/translations.ts +4 -4
- package/src/util/field.ts +1 -1
- package/src/util/formatting.ts +1 -1
- package/src/util/refs.ts +7 -7
- package/src/util/schema.ts +2 -4
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.e8ec1fe",
|
|
4
4
|
"description": "Data components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -28,54 +28,55 @@
|
|
|
28
28
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
|
29
29
|
"@fluentui/react-tabster": "^9.24.2",
|
|
30
30
|
"@preact-signals/safe-react": "^0.9.0",
|
|
31
|
+
"@preact/signals-react": "^3.2.0",
|
|
31
32
|
"@radix-ui/react-context": "1.1.1",
|
|
32
33
|
"date-fns": "^3.3.1",
|
|
33
34
|
"jsonpath": "^1.1.1",
|
|
34
|
-
"@dxos/async": "0.8.4-main.
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/echo-
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/lit-ui": "0.8.4-main.
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/react-
|
|
46
|
-
"@dxos/react-ui-
|
|
47
|
-
"@dxos/react-ui-
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/react-ui-list": "0.8.4-main.
|
|
50
|
-
"@dxos/react-ui-searchlist": "0.8.4-main.
|
|
51
|
-
"@dxos/react-ui-stack": "0.8.4-main.
|
|
52
|
-
"@dxos/schema": "0.8.4-main.
|
|
53
|
-
"@dxos/react-ui-types": "0.8.4-main.
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
35
|
+
"@dxos/async": "0.8.4-main.e8ec1fe",
|
|
36
|
+
"@dxos/echo": "0.8.4-main.e8ec1fe",
|
|
37
|
+
"@dxos/debug": "0.8.4-main.e8ec1fe",
|
|
38
|
+
"@dxos/echo-db": "0.8.4-main.e8ec1fe",
|
|
39
|
+
"@dxos/effect": "0.8.4-main.e8ec1fe",
|
|
40
|
+
"@dxos/echo-query": "0.8.4-main.e8ec1fe",
|
|
41
|
+
"@dxos/keys": "0.8.4-main.e8ec1fe",
|
|
42
|
+
"@dxos/lit-ui": "0.8.4-main.e8ec1fe",
|
|
43
|
+
"@dxos/live-object": "0.8.4-main.e8ec1fe",
|
|
44
|
+
"@dxos/invariant": "0.8.4-main.e8ec1fe",
|
|
45
|
+
"@dxos/react-client": "0.8.4-main.e8ec1fe",
|
|
46
|
+
"@dxos/react-ui-components": "0.8.4-main.e8ec1fe",
|
|
47
|
+
"@dxos/react-ui-editor": "0.8.4-main.e8ec1fe",
|
|
48
|
+
"@dxos/react-ui-pickers": "0.8.4-main.e8ec1fe",
|
|
49
|
+
"@dxos/log": "0.8.4-main.e8ec1fe",
|
|
50
|
+
"@dxos/react-ui-list": "0.8.4-main.e8ec1fe",
|
|
51
|
+
"@dxos/react-ui-searchlist": "0.8.4-main.e8ec1fe",
|
|
52
|
+
"@dxos/react-ui-stack": "0.8.4-main.e8ec1fe",
|
|
53
|
+
"@dxos/schema": "0.8.4-main.e8ec1fe",
|
|
54
|
+
"@dxos/react-ui-types": "0.8.4-main.e8ec1fe",
|
|
55
|
+
"@dxos/types": "0.8.4-main.e8ec1fe",
|
|
56
|
+
"@dxos/util": "0.8.4-main.e8ec1fe"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@types/jsonpath": "^0.2.4",
|
|
59
|
-
"@types/react": "~
|
|
60
|
-
"@types/react-dom": "~
|
|
61
|
-
"effect": "3.
|
|
60
|
+
"@types/react": "~19.2.2",
|
|
61
|
+
"@types/react-dom": "~19.2.2",
|
|
62
|
+
"effect": "3.18.3",
|
|
62
63
|
"fast-check": "^3.19.0",
|
|
63
|
-
"react": "~
|
|
64
|
-
"react-dom": "~
|
|
65
|
-
"vite": "7.1.
|
|
66
|
-
"@dxos/echo-signals": "0.8.4-main.
|
|
67
|
-
"@dxos/random": "0.8.4-main.
|
|
68
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
69
|
-
"@dxos/react-ui-
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/
|
|
64
|
+
"react": "~19.2.0",
|
|
65
|
+
"react-dom": "~19.2.0",
|
|
66
|
+
"vite": "7.1.9",
|
|
67
|
+
"@dxos/echo-signals": "0.8.4-main.e8ec1fe",
|
|
68
|
+
"@dxos/random": "0.8.4-main.e8ec1fe",
|
|
69
|
+
"@dxos/react-ui": "0.8.4-main.e8ec1fe",
|
|
70
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.e8ec1fe",
|
|
71
|
+
"@dxos/react-ui-theme": "0.8.4-main.e8ec1fe",
|
|
72
|
+
"@dxos/storybook-utils": "0.8.4-main.e8ec1fe"
|
|
72
73
|
},
|
|
73
74
|
"peerDependencies": {
|
|
74
75
|
"effect": "^3.13.3",
|
|
75
|
-
"react": "
|
|
76
|
-
"react-dom": "
|
|
77
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
78
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
76
|
+
"react": "^19.0.0",
|
|
77
|
+
"react-dom": "^19.0.0",
|
|
78
|
+
"@dxos/react-ui": "0.8.4-main.e8ec1fe",
|
|
79
|
+
"@dxos/react-ui-theme": "0.8.4-main.e8ec1fe"
|
|
79
80
|
},
|
|
80
81
|
"publishConfig": {
|
|
81
82
|
"access": "public"
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import React from 'react';
|
|
9
7
|
|
|
10
|
-
import {
|
|
8
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
11
9
|
|
|
12
10
|
import { translations } from '../../translations';
|
|
13
11
|
|
|
@@ -17,12 +15,13 @@ const meta = {
|
|
|
17
15
|
title: 'ui/react-ui-form/ControlSection',
|
|
18
16
|
component: ControlSection,
|
|
19
17
|
render: (args) => (
|
|
20
|
-
<ControlPage classNames='
|
|
18
|
+
<ControlPage classNames='is-[40rem]'>
|
|
21
19
|
<ControlSection {...args} />
|
|
22
20
|
</ControlPage>
|
|
23
21
|
),
|
|
24
|
-
decorators: [
|
|
22
|
+
decorators: [withTheme],
|
|
25
23
|
parameters: {
|
|
24
|
+
layout: 'centered',
|
|
26
25
|
translations,
|
|
27
26
|
},
|
|
28
27
|
} satisfies Meta<typeof ControlSection>;
|
|
@@ -22,7 +22,7 @@ export type ControlPageProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
|
|
|
22
22
|
|
|
23
23
|
export const ControlPage = ({ children, classNames, ...props }: ControlPageProps) => {
|
|
24
24
|
return (
|
|
25
|
-
<StackItem.Content classNames='
|
|
25
|
+
<StackItem.Content scrollable classNames='[--control-spacing:var(--dx-trimMd)]'>
|
|
26
26
|
<div role='none' className={mx('pli-cardSpacingInline pbe-trimLg', classNames)} {...props}>
|
|
27
27
|
{children}
|
|
28
28
|
</div>
|
|
@@ -2,16 +2,14 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import React, { useEffect, useMemo } from 'react';
|
|
9
7
|
|
|
10
|
-
import { createEchoSchema } from '@dxos/
|
|
8
|
+
import { createEchoSchema } from '@dxos/echo/testing';
|
|
11
9
|
import { log } from '@dxos/log';
|
|
10
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
12
11
|
import { ProjectionModel } from '@dxos/schema';
|
|
13
12
|
import { TestSchema, testView } from '@dxos/schema/testing';
|
|
14
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
15
13
|
|
|
16
14
|
import { translations } from '../../translations';
|
|
17
15
|
import { FIELD_EDITOR_DEBUG_SYMBOL, TestLayout, TestPanel } from '../testing';
|
|
@@ -31,6 +29,8 @@ const DefaultStory = (props: FieldEditorProps) => {
|
|
|
31
29
|
|
|
32
30
|
// Expose objects on window for test access.
|
|
33
31
|
useEffect(() => {
|
|
32
|
+
props.projection.normalizeView();
|
|
33
|
+
|
|
34
34
|
if (typeof window !== 'undefined') {
|
|
35
35
|
(window as any)[FIELD_EDITOR_DEBUG_SYMBOL] = {
|
|
36
36
|
props,
|
|
@@ -58,8 +58,9 @@ const meta = {
|
|
|
58
58
|
title: 'ui/react-ui-form/FieldEditor',
|
|
59
59
|
component: FieldEditor as any,
|
|
60
60
|
render: DefaultStory,
|
|
61
|
-
decorators: [
|
|
61
|
+
decorators: [withTheme],
|
|
62
62
|
parameters: {
|
|
63
|
+
layout: 'fullscreen',
|
|
63
64
|
translations,
|
|
64
65
|
},
|
|
65
66
|
} satisfies Meta<typeof DefaultStory>;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
+
import { type EchoSchema, FormatEnum, FormatEnums, formatToType } from '@dxos/echo/internal';
|
|
7
8
|
import { type SchemaRegistry } from '@dxos/echo-db';
|
|
8
|
-
import { type EchoSchema, FormatEnum, FormatEnums, formatToType } from '@dxos/echo-schema';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
10
|
import { useAsyncEffect, useTranslation } from '@dxos/react-ui';
|
|
11
11
|
import {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type ProjectionModel,
|
|
14
14
|
type PropertyType,
|
|
15
15
|
type SchemaProperty,
|
|
16
|
+
formatToAdditionalPropertyAttributes,
|
|
16
17
|
getFormatSchema,
|
|
17
18
|
getSchemaProperties,
|
|
18
19
|
sortProperties,
|
|
@@ -94,8 +95,10 @@ export const FieldEditor = ({
|
|
|
94
95
|
|
|
95
96
|
setProps((props) => {
|
|
96
97
|
const type = formatToType[_props.format as keyof typeof formatToType];
|
|
98
|
+
const additionalProps =
|
|
99
|
+
formatToAdditionalPropertyAttributes[_props.format as keyof typeof formatToAdditionalPropertyAttributes];
|
|
97
100
|
if (props.type !== type) {
|
|
98
|
-
return { ...props, ..._props, type };
|
|
101
|
+
return { ...props, ..._props, ...additionalProps, type } as PropertyType;
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
return _props as PropertyType;
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import * as Function from 'effect/Function';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
import * as String from 'effect/String';
|
|
7
8
|
import React, { Fragment, useCallback } from 'react';
|
|
8
9
|
|
|
9
10
|
import { SimpleType, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
|
|
@@ -34,7 +35,7 @@ export const ArrayField = ({ property, readonly, path, inputProps, Custom, looku
|
|
|
34
35
|
// It doesn't become live until another field is touched, but that's better than the whole form crashing.
|
|
35
36
|
const values = (useFormValues(path ?? []) ?? []) as any[];
|
|
36
37
|
invariant(Array.isArray(values), `Values at path ${path?.join('.')} must be an array.`);
|
|
37
|
-
const label = title ?? pipe(name, capitalize);
|
|
38
|
+
const label = title ?? Function.pipe(name, String.capitalize);
|
|
38
39
|
|
|
39
40
|
const elementType = findArrayElementType(ast);
|
|
40
41
|
|
|
@@ -192,7 +192,7 @@ export const MarkdownInput = ({
|
|
|
192
192
|
disabled={!!readonly}
|
|
193
193
|
placeholder={placeholder}
|
|
194
194
|
value={getValue() ?? ''}
|
|
195
|
-
classNames={'min-bs-auto max-
|
|
195
|
+
classNames={'min-bs-auto max-bs-40 overflow-auto'}
|
|
196
196
|
onChange={(event) => onValueChange(type, event.target.value)}
|
|
197
197
|
onBlur={onBlur}
|
|
198
198
|
style={{ resize: 'none' }}
|
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
9
7
|
import React, { useCallback, useState } from 'react';
|
|
10
8
|
|
|
11
9
|
import { ContactType } from '@dxos/client/testing';
|
|
12
10
|
import { type Type } from '@dxos/echo';
|
|
13
|
-
import { type BaseObject, Expando, Format, Ref, type TypeAnnotation, getObjectDXN } from '@dxos/echo
|
|
14
|
-
import { live } from '@dxos/
|
|
15
|
-
import {
|
|
11
|
+
import { type BaseObject, Expando, Format, Ref, type TypeAnnotation, getObjectDXN } from '@dxos/echo/internal';
|
|
12
|
+
import { live } from '@dxos/echo/internal';
|
|
13
|
+
import { Tooltip } from '@dxos/react-ui';
|
|
14
|
+
import { withLayoutVariants, withTheme } from '@dxos/react-ui/testing';
|
|
16
15
|
import { Testing } from '@dxos/schema/testing';
|
|
17
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
18
16
|
|
|
19
17
|
import { translations } from '../../translations';
|
|
20
18
|
import { TestLayout, TestPanel } from '../testing';
|
|
@@ -40,11 +38,13 @@ const DefaultStory = <T extends BaseObject = any>({
|
|
|
40
38
|
|
|
41
39
|
if (debug) {
|
|
42
40
|
return (
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
<Tooltip.Provider>
|
|
42
|
+
<TestLayout json={{ values, schema: schema.ast }}>
|
|
43
|
+
<TestPanel>
|
|
44
|
+
<Form<T> schema={schema} values={values} onSave={handleSave} {...props} />
|
|
45
|
+
</TestPanel>
|
|
46
|
+
</TestLayout>
|
|
47
|
+
</Tooltip.Provider>
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -54,7 +54,7 @@ const DefaultStory = <T extends BaseObject = any>({
|
|
|
54
54
|
const RefStory = <T extends BaseObject = any>(props: StoryProps<T>) => {
|
|
55
55
|
const onQueryRefOptions = useCallback((typeInfo: TypeAnnotation) => {
|
|
56
56
|
switch (typeInfo.typename) {
|
|
57
|
-
case
|
|
57
|
+
case Testing.Person.typename:
|
|
58
58
|
return [
|
|
59
59
|
{ dxn: getObjectDXN(contact1)!, label: 'John Coltraine' },
|
|
60
60
|
{ dxn: getObjectDXN(contact2)!, label: 'Erykah Badu' },
|
|
@@ -67,24 +67,6 @@ const RefStory = <T extends BaseObject = any>(props: StoryProps<T>) => {
|
|
|
67
67
|
return <DefaultStory<T> onQueryRefOptions={onQueryRefOptions} {...props} />;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
const meta = {
|
|
71
|
-
title: 'ui/react-ui-form/Form',
|
|
72
|
-
component: Form as any,
|
|
73
|
-
render: DefaultStory,
|
|
74
|
-
decorators: [withLayout({ fullscreen: true }), withTheme],
|
|
75
|
-
parameters: {
|
|
76
|
-
translations,
|
|
77
|
-
},
|
|
78
|
-
argTypes: {
|
|
79
|
-
readonly: {
|
|
80
|
-
control: 'boolean',
|
|
81
|
-
description: 'Readonly',
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
} satisfies Meta<StoryProps<any>>;
|
|
85
|
-
|
|
86
|
-
export default meta;
|
|
87
|
-
|
|
88
70
|
const AddressSchema = Schema.Struct({
|
|
89
71
|
street: Schema.optional(Schema.String.annotations({ title: 'Street' })),
|
|
90
72
|
city: Schema.optional(Schema.String.annotations({ title: 'City' })),
|
|
@@ -102,10 +84,28 @@ const ContactSchema = Schema.Struct({
|
|
|
102
84
|
|
|
103
85
|
type ContactSchema = Schema.Schema.Type<typeof ContactSchema>;
|
|
104
86
|
|
|
105
|
-
|
|
87
|
+
const meta = {
|
|
88
|
+
title: 'ui/react-ui-form/Form',
|
|
89
|
+
component: Form as any,
|
|
90
|
+
render: DefaultStory,
|
|
91
|
+
decorators: [withTheme, withLayoutVariants()],
|
|
92
|
+
parameters: {
|
|
93
|
+
layout: 'fullscreen',
|
|
94
|
+
translations,
|
|
95
|
+
},
|
|
96
|
+
argTypes: {
|
|
97
|
+
readonly: {
|
|
98
|
+
control: 'boolean',
|
|
99
|
+
description: 'Readonly',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
} satisfies Meta<StoryProps<any>>;
|
|
103
|
+
|
|
104
|
+
export default meta;
|
|
105
|
+
|
|
106
|
+
type Story = StoryObj<typeof meta>;
|
|
106
107
|
|
|
107
108
|
export const Default: Story = {
|
|
108
|
-
decorators: [withSurfaceVariantsLayout(), withTheme],
|
|
109
109
|
args: {
|
|
110
110
|
schema: ContactSchema,
|
|
111
111
|
values: {
|
|
@@ -147,10 +147,10 @@ export const OrganizationAutoSave: Story = {
|
|
|
147
147
|
// TODO(burdon): Type issue with employer reference.
|
|
148
148
|
// TODO(burdon): Test table/form with compound values (e.g., address).
|
|
149
149
|
// Property name is missing in type EncodedReference but required in type
|
|
150
|
-
export const
|
|
150
|
+
export const Person: Story = {
|
|
151
151
|
args: {
|
|
152
152
|
debug: true,
|
|
153
|
-
schema: Testing.
|
|
153
|
+
schema: Testing.Person,
|
|
154
154
|
values: {
|
|
155
155
|
name: 'Bot',
|
|
156
156
|
},
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { useFocusFinders } from '@fluentui/react-tabster';
|
|
6
|
-
import
|
|
7
|
-
import React, { type ReactElement,
|
|
6
|
+
import type * as Schema from 'effect/Schema';
|
|
7
|
+
import React, { type ReactElement, useCallback, useEffect, useRef } from 'react';
|
|
8
8
|
|
|
9
|
-
import { type BaseObject, type PropertyKey } from '@dxos/echo
|
|
9
|
+
import { type BaseObject, type PropertyKey } from '@dxos/echo/internal';
|
|
10
10
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
11
11
|
import { cardDialogOverflow, cardSpacing } from '@dxos/react-ui-stack';
|
|
12
12
|
import { type ProjectionModel, type SchemaProperty } from '@dxos/schema';
|
|
13
13
|
|
|
14
|
-
import { type FormOptions } from '../../hooks';
|
|
14
|
+
import { type FormHandler, type FormOptions } from '../../hooks';
|
|
15
15
|
|
|
16
16
|
import { FormActions, type FormOuterSpacing } from './FormActions';
|
|
17
17
|
import { FormFields, type FormFieldsProps } from './FormContent';
|
|
@@ -29,6 +29,7 @@ export type ComponentLookup = (args: {
|
|
|
29
29
|
export type CustomInputMap = Partial<Record<string, InputComponent>>;
|
|
30
30
|
|
|
31
31
|
export type FormProps<T extends BaseObject> = ThemedClassName<{
|
|
32
|
+
id?: string;
|
|
32
33
|
values: Partial<T>;
|
|
33
34
|
// TODO(burdon): Autofocus first input.
|
|
34
35
|
autoFocus?: boolean;
|
|
@@ -41,10 +42,12 @@ export type FormProps<T extends BaseObject> = ThemedClassName<{
|
|
|
41
42
|
onCancel?: () => void;
|
|
42
43
|
}> &
|
|
43
44
|
Pick<FormOptions<T>, 'schema' | 'onValuesChanged' | 'onValidate' | 'onSave'> &
|
|
45
|
+
// TODO(wittjosiah): This needs to support different ref field options per field.
|
|
44
46
|
FormFieldsProps;
|
|
45
47
|
|
|
46
48
|
export const Form = <T extends BaseObject>({
|
|
47
49
|
classNames,
|
|
50
|
+
id,
|
|
48
51
|
testId,
|
|
49
52
|
values: initialValues,
|
|
50
53
|
autoFocus,
|
|
@@ -72,7 +75,14 @@ export const Form = <T extends BaseObject>({
|
|
|
72
75
|
}, [autoFocus]);
|
|
73
76
|
|
|
74
77
|
// TODO(burdon): Name?
|
|
75
|
-
const handleValid =
|
|
78
|
+
const handleValid = useCallback(
|
|
79
|
+
async (values: any, meta: { changed: FormHandler<T>['changed'] }) => {
|
|
80
|
+
if (autoSave) {
|
|
81
|
+
await onSave?.(values, meta);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[autoSave, onSave],
|
|
85
|
+
);
|
|
76
86
|
|
|
77
87
|
return (
|
|
78
88
|
<FormProvider
|
|
@@ -85,7 +95,7 @@ export const Form = <T extends BaseObject>({
|
|
|
85
95
|
onValid={handleValid}
|
|
86
96
|
onSave={onSave}
|
|
87
97
|
>
|
|
88
|
-
<div role='none' className='contents' data-testid={testId}>
|
|
98
|
+
<div role='none' className='contents' {...(id && { 'data-object-id': id })} data-testid={testId}>
|
|
89
99
|
<FormFields
|
|
90
100
|
{...props}
|
|
91
101
|
ref={formRef}
|
|
@@ -37,7 +37,7 @@ export const FormActions = ({ readonly, onCancel, outerSpacing = true }: FormAct
|
|
|
37
37
|
)}
|
|
38
38
|
>
|
|
39
39
|
{onCancel && !readonly && (
|
|
40
|
-
<IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('button
|
|
40
|
+
<IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('cancel button')} onClick={onCancel} />
|
|
41
41
|
)}
|
|
42
42
|
{handleSave && (
|
|
43
43
|
<IconButton
|
|
@@ -45,7 +45,7 @@ export const FormActions = ({ readonly, onCancel, outerSpacing = true }: FormAct
|
|
|
45
45
|
data-testid='save-button'
|
|
46
46
|
disabled={!canSave}
|
|
47
47
|
icon='ph--check--regular'
|
|
48
|
-
label={t('button
|
|
48
|
+
label={t('save button')}
|
|
49
49
|
onClick={handleSave}
|
|
50
50
|
/>
|
|
51
51
|
)}
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
5
|
+
import * as Function from 'effect/Function';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
8
|
+
import * as StringEffect from 'effect/String';
|
|
9
|
+
import React, { Component, type PropsWithChildren, type ReactNode, forwardRef, useMemo } from 'react';
|
|
8
10
|
|
|
9
11
|
import { createJsonPath, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
|
|
10
12
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
11
13
|
import { mx } from '@dxos/react-ui-theme';
|
|
12
14
|
import { type ProjectionModel, type SchemaProperty, getSchemaProperties } from '@dxos/schema';
|
|
13
|
-
import {
|
|
15
|
+
import { isTruthy } from '@dxos/util';
|
|
14
16
|
|
|
15
17
|
import { type QueryRefOptions } from '../../hooks';
|
|
16
18
|
import { getRefProps } from '../../util';
|
|
@@ -33,7 +35,13 @@ export type FormFieldProps = {
|
|
|
33
35
|
Custom?: Partial<Record<string, InputComponent>>;
|
|
34
36
|
} & Pick<
|
|
35
37
|
RefFieldProps,
|
|
36
|
-
|
|
38
|
+
| 'readonly'
|
|
39
|
+
| 'onQueryRefOptions'
|
|
40
|
+
| 'createOptionLabel'
|
|
41
|
+
| 'createOptionIcon'
|
|
42
|
+
| 'onCreate'
|
|
43
|
+
| 'createSchema'
|
|
44
|
+
| 'createInitialValuePath'
|
|
37
45
|
>;
|
|
38
46
|
|
|
39
47
|
export const FormField = ({
|
|
@@ -45,14 +53,16 @@ export const FormField = ({
|
|
|
45
53
|
onQueryRefOptions,
|
|
46
54
|
createOptionLabel,
|
|
47
55
|
createOptionIcon,
|
|
48
|
-
|
|
56
|
+
onCreate,
|
|
57
|
+
createSchema,
|
|
58
|
+
createInitialValuePath,
|
|
49
59
|
lookupComponent,
|
|
50
60
|
Custom,
|
|
51
61
|
}: FormFieldProps) => {
|
|
52
62
|
const { ast, name, type, format, title, description, options, examples, array } = property;
|
|
53
63
|
const inputProps = useInputProps(path);
|
|
54
64
|
|
|
55
|
-
const label = useMemo(() => title ?? pipe(name, capitalize), [title, name]);
|
|
65
|
+
const label = useMemo(() => title ?? Function.pipe(name, StringEffect.capitalize), [title, name]);
|
|
56
66
|
const placeholder = useMemo(
|
|
57
67
|
() => (examples?.length ? `Example: "${examples[0]}"` : description),
|
|
58
68
|
[examples, description],
|
|
@@ -114,7 +124,9 @@ export const FormField = ({
|
|
|
114
124
|
onQueryRefOptions={onQueryRefOptions}
|
|
115
125
|
createOptionLabel={createOptionLabel}
|
|
116
126
|
createOptionIcon={createOptionIcon}
|
|
117
|
-
|
|
127
|
+
onCreate={onCreate}
|
|
128
|
+
createSchema={createSchema}
|
|
129
|
+
createInitialValuePath={createInitialValuePath}
|
|
118
130
|
{...inputProps}
|
|
119
131
|
/>
|
|
120
132
|
);
|
|
@@ -188,7 +200,7 @@ export const FormField = ({
|
|
|
188
200
|
onQueryRefOptions={onQueryRefOptions}
|
|
189
201
|
createOptionLabel={createOptionLabel}
|
|
190
202
|
createOptionIcon={createOptionIcon}
|
|
191
|
-
|
|
203
|
+
onCreate={onCreate}
|
|
192
204
|
Custom={Custom}
|
|
193
205
|
lookupComponent={lookupComponent}
|
|
194
206
|
/>
|
|
@@ -222,7 +234,15 @@ export type FormFieldsProps = ThemedClassName<
|
|
|
222
234
|
Custom?: Partial<Record<string, InputComponent>>;
|
|
223
235
|
onQueryRefOptions?: QueryRefOptions;
|
|
224
236
|
} & Pick<FormFieldProps, 'readonly'> &
|
|
225
|
-
Pick<
|
|
237
|
+
Pick<
|
|
238
|
+
RefFieldProps,
|
|
239
|
+
| 'onQueryRefOptions'
|
|
240
|
+
| 'createOptionLabel'
|
|
241
|
+
| 'createOptionIcon'
|
|
242
|
+
| 'onCreate'
|
|
243
|
+
| 'createSchema'
|
|
244
|
+
| 'createInitialValuePath'
|
|
245
|
+
>
|
|
226
246
|
>;
|
|
227
247
|
|
|
228
248
|
export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
|
|
@@ -282,21 +302,61 @@ export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
|
|
|
282
302
|
{properties
|
|
283
303
|
.map((property) => {
|
|
284
304
|
return (
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
305
|
+
<ErrorBoundary key={property.name} path={[...(path ?? []), property.name]}>
|
|
306
|
+
<FormField
|
|
307
|
+
property={property}
|
|
308
|
+
path={[...(path ?? []), property.name]}
|
|
309
|
+
readonly={readonly}
|
|
310
|
+
projection={projection}
|
|
311
|
+
onQueryRefOptions={onQueryRefOptions}
|
|
312
|
+
lookupComponent={lookupComponent}
|
|
313
|
+
Custom={Custom}
|
|
314
|
+
{...props}
|
|
315
|
+
/>
|
|
316
|
+
</ErrorBoundary>
|
|
296
317
|
);
|
|
297
318
|
})
|
|
298
|
-
.filter(
|
|
319
|
+
.filter(isTruthy)}
|
|
299
320
|
</div>
|
|
300
321
|
);
|
|
301
322
|
},
|
|
302
323
|
);
|
|
324
|
+
|
|
325
|
+
type ErrorState = {
|
|
326
|
+
error: Error | undefined;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
type ErrorBoundaryProps = PropsWithChildren<{
|
|
330
|
+
path?: (string | number)[];
|
|
331
|
+
}>;
|
|
332
|
+
|
|
333
|
+
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorState> {
|
|
334
|
+
static getDerivedStateFromError(error: Error): { error: Error } {
|
|
335
|
+
return { error };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
override state = { error: undefined };
|
|
339
|
+
|
|
340
|
+
override componentDidUpdate(prevProps: ErrorBoundaryProps): void {
|
|
341
|
+
if (prevProps.path !== this.props.path) {
|
|
342
|
+
this.resetError();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
override render(): ReactNode {
|
|
347
|
+
if (this.state.error) {
|
|
348
|
+
return (
|
|
349
|
+
<div className='flex gap-2 border border-roseFill font-mono text-sm'>
|
|
350
|
+
<span className='bg-roseFill text-surfaceText pli-1 font-thin'>ERROR</span>
|
|
351
|
+
{String(this.props.path?.join('.'))}
|
|
352
|
+
</div>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return this.props.children;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private resetError(): void {
|
|
360
|
+
this.setState({ error: undefined });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -14,7 +14,7 @@ import React, {
|
|
|
14
14
|
} from 'react';
|
|
15
15
|
|
|
16
16
|
import { raise } from '@dxos/debug';
|
|
17
|
-
import { type BaseObject, getValue } from '@dxos/echo
|
|
17
|
+
import { type BaseObject, getValue } from '@dxos/echo/internal';
|
|
18
18
|
import { type SimpleType, createJsonPath } from '@dxos/effect';
|
|
19
19
|
|
|
20
20
|
import { type FormHandler, type FormOptions, useForm } from '../../hooks';
|
|
@@ -62,7 +62,7 @@ export const FormProvider = ({
|
|
|
62
62
|
...formOptions
|
|
63
63
|
}: PropsWithChildren<
|
|
64
64
|
FormOptions<any> & {
|
|
65
|
-
formRef?: RefObject<HTMLDivElement>;
|
|
65
|
+
formRef?: RefObject<HTMLDivElement | null>;
|
|
66
66
|
autoSave?: boolean;
|
|
67
67
|
}
|
|
68
68
|
>) => {
|
|
@@ -92,7 +92,7 @@ export const FormProvider = ({
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
|
-
[form, autoSave],
|
|
95
|
+
[form.canSave, form.formIsValid, form.handleSave, autoSave],
|
|
96
96
|
);
|
|
97
97
|
|
|
98
98
|
useEffect(() => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type FC } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type FormatEnum } from '@dxos/echo
|
|
7
|
+
import { type FormatEnum } from '@dxos/echo/internal';
|
|
8
8
|
import { type SimpleType } from '@dxos/effect';
|
|
9
9
|
import { Icon, Input, Tooltip } from '@dxos/react-ui';
|
|
10
10
|
import { labelSpacing } from '@dxos/react-ui-stack';
|