@dxos/react-ui-form 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6
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/README.md +0 -1
- package/dist/lib/browser/index.mjs +620 -583
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +620 -583
- 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 +3 -3
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +61 -61
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +18 -15
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +67 -67
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +7 -1
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +0 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +2 -2
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +9 -10
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +7 -5
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +61 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +10 -2
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +61 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +39 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts +81 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +5 -5
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +65 -64
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +61 -61
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/refs.d.ts +1 -0
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +36 -38
- package/src/components/FieldEditor/FieldEditor.stories.tsx +4 -6
- package/src/components/FieldEditor/FieldEditor.tsx +3 -4
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +23 -24
- package/src/components/Form/Form.tsx +58 -65
- package/src/components/Form/FormField.tsx +27 -8
- package/src/components/Form/FormFieldComponent.stories.tsx +5 -5
- package/src/components/Form/FormFieldComponent.tsx +17 -20
- package/src/components/Form/FormFieldSet.tsx +108 -104
- package/src/components/Form/fields/ArrayField.tsx +11 -8
- package/src/components/Form/fields/GeoPointField.tsx +6 -6
- package/src/components/Form/fields/MarkdownField.tsx +1 -1
- package/src/components/Form/fields/RefField.tsx +35 -17
- package/src/components/Form/fields/SelectField.tsx +1 -1
- package/src/components/Form/fields/SelectOptionField.stories.tsx +2 -2
- package/src/components/Form/fields/SelectOptionField.tsx +9 -10
- package/src/components/Form/fields/TupleField.tsx +1 -1
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +10 -14
- package/src/components/ObjectPicker/ObjectPicker.tsx +71 -60
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +129 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +22 -20
- package/src/components/ViewEditor/ViewEditor.test.tsx +3 -6
- package/src/components/ViewEditor/ViewEditor.tsx +99 -70
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +20 -8
- package/src/hooks/useFormHandler.ts +42 -24
- package/src/translations.ts +62 -63
- package/src/util/index.ts +0 -1
- package/src/util/refs.ts +7 -3
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -87
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/index.d.ts +0 -2
- package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/Form-old.stories.d.ts +0 -131
- package/dist/types/src/components/Form/Form-old.stories.d.ts.map +0 -1
- package/dist/types/src/util/echo.d.ts +0 -6
- package/dist/types/src/util/echo.d.ts.map +0 -1
- package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
- package/src/components/ControlSection/ControlSection.tsx +0 -153
- package/src/components/Form/Form-old.stories.tsx +0 -314
- package/src/util/echo.ts +0 -25
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.bcb3aa67d6",
|
|
4
4
|
"description": "Data components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -30,58 +30,56 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@atlaskit/pragmatic-drag-and-drop": "1.7.7",
|
|
32
32
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
|
|
33
|
-
"@effect-atom/atom-react": "^0.
|
|
33
|
+
"@effect-atom/atom-react": "^0.5.0",
|
|
34
34
|
"@fluentui/react-tabster": "9.26.11",
|
|
35
35
|
"@radix-ui/react-context": "1.1.1",
|
|
36
36
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
37
|
-
"date-fns": "^3.
|
|
38
|
-
"jsonpath": "^1.1.1",
|
|
37
|
+
"date-fns": "^3.6.0",
|
|
39
38
|
"tabster": "^8.5.5",
|
|
40
|
-
"@dxos/async": "0.8.4-main.
|
|
41
|
-
"@dxos/debug": "0.8.4-main.
|
|
42
|
-
"@dxos/echo": "0.8.4-main.
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/invariant": "0.8.4-main.
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/log": "0.8.4-main.
|
|
50
|
-
"@dxos/react-client": "0.8.4-main.
|
|
51
|
-
"@dxos/react-ui-components": "0.8.4-main.
|
|
52
|
-
"@dxos/react-ui-editor": "0.8.4-main.
|
|
53
|
-
"@dxos/react-ui-
|
|
54
|
-
"@dxos/react-ui-pickers": "0.8.4-main.
|
|
55
|
-
"@dxos/react-ui-
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/react-ui-
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/types": "0.8.4-main.
|
|
60
|
-
"@dxos/
|
|
61
|
-
"@dxos/util": "0.8.4-main.
|
|
39
|
+
"@dxos/async": "0.8.4-main.bcb3aa67d6",
|
|
40
|
+
"@dxos/debug": "0.8.4-main.bcb3aa67d6",
|
|
41
|
+
"@dxos/echo": "0.8.4-main.bcb3aa67d6",
|
|
42
|
+
"@dxos/effect": "0.8.4-main.bcb3aa67d6",
|
|
43
|
+
"@dxos/echo-query": "0.8.4-main.bcb3aa67d6",
|
|
44
|
+
"@dxos/invariant": "0.8.4-main.bcb3aa67d6",
|
|
45
|
+
"@dxos/lit-ui": "0.8.4-main.bcb3aa67d6",
|
|
46
|
+
"@dxos/keys": "0.8.4-main.bcb3aa67d6",
|
|
47
|
+
"@dxos/echo-react": "0.8.4-main.bcb3aa67d6",
|
|
48
|
+
"@dxos/log": "0.8.4-main.bcb3aa67d6",
|
|
49
|
+
"@dxos/react-client": "0.8.4-main.bcb3aa67d6",
|
|
50
|
+
"@dxos/react-ui-components": "0.8.4-main.bcb3aa67d6",
|
|
51
|
+
"@dxos/react-ui-editor": "0.8.4-main.bcb3aa67d6",
|
|
52
|
+
"@dxos/react-ui-list": "0.8.4-main.bcb3aa67d6",
|
|
53
|
+
"@dxos/react-ui-pickers": "0.8.4-main.bcb3aa67d6",
|
|
54
|
+
"@dxos/react-ui-mosaic": "0.8.4-main.bcb3aa67d6",
|
|
55
|
+
"@dxos/react-ui-search": "0.8.4-main.bcb3aa67d6",
|
|
56
|
+
"@dxos/react-ui-stack": "0.8.4-main.bcb3aa67d6",
|
|
57
|
+
"@dxos/schema": "0.8.4-main.bcb3aa67d6",
|
|
58
|
+
"@dxos/ui-types": "0.8.4-main.bcb3aa67d6",
|
|
59
|
+
"@dxos/types": "0.8.4-main.bcb3aa67d6",
|
|
60
|
+
"@dxos/util": "0.8.4-main.bcb3aa67d6"
|
|
62
61
|
},
|
|
63
62
|
"devDependencies": {
|
|
64
|
-
"@types/jsonpath": "^0.2.4",
|
|
65
63
|
"@types/react": "~19.2.7",
|
|
66
64
|
"@types/react-dom": "~19.2.3",
|
|
67
|
-
"effect": "3.
|
|
65
|
+
"effect": "3.20.0",
|
|
68
66
|
"fast-check": "^3.19.0",
|
|
69
67
|
"react": "~19.2.3",
|
|
70
68
|
"react-dom": "~19.2.3",
|
|
71
|
-
"vite": "7.1.
|
|
72
|
-
"@dxos/react-client": "0.8.4-main.
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/react-ui
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
77
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
69
|
+
"vite": "^7.1.11",
|
|
70
|
+
"@dxos/react-client": "0.8.4-main.bcb3aa67d6",
|
|
71
|
+
"@dxos/random": "0.8.4-main.bcb3aa67d6",
|
|
72
|
+
"@dxos/react-ui": "0.8.4-main.bcb3aa67d6",
|
|
73
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.bcb3aa67d6",
|
|
74
|
+
"@dxos/ui-theme": "0.8.4-main.bcb3aa67d6",
|
|
75
|
+
"@dxos/storybook-utils": "0.8.4-main.bcb3aa67d6"
|
|
78
76
|
},
|
|
79
77
|
"peerDependencies": {
|
|
80
|
-
"effect": "3.
|
|
78
|
+
"effect": "3.20.0",
|
|
81
79
|
"react": "~19.2.3",
|
|
82
80
|
"react-dom": "~19.2.3",
|
|
83
|
-
"@dxos/ui
|
|
84
|
-
"@dxos/
|
|
81
|
+
"@dxos/react-ui": "0.8.4-main.bcb3aa67d6",
|
|
82
|
+
"@dxos/ui-theme": "0.8.4-main.bcb3aa67d6"
|
|
85
83
|
},
|
|
86
84
|
"publishConfig": {
|
|
87
85
|
"access": "public"
|
|
@@ -8,8 +8,8 @@ import React, { useEffect, useMemo } from 'react';
|
|
|
8
8
|
import { Filter, Query } from '@dxos/echo';
|
|
9
9
|
import { createEchoSchema } from '@dxos/echo/testing';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
12
|
-
import { ProjectionModel,
|
|
11
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
12
|
+
import { ProjectionModel, ViewModel, createEchoChangeCallback } from '@dxos/schema';
|
|
13
13
|
import { Example } from '@dxos/schema/testing';
|
|
14
14
|
|
|
15
15
|
import { translations } from '../../translations';
|
|
@@ -17,17 +17,15 @@ import { FIELD_EDITOR_DEBUG_SYMBOL, TestLayout } from '../testing';
|
|
|
17
17
|
|
|
18
18
|
import { FieldEditor, type FieldEditorProps } from './FieldEditor';
|
|
19
19
|
|
|
20
|
-
// Type definition for debug objects exposed to tests.
|
|
21
20
|
export type FieldEditorDebugObjects = {
|
|
22
21
|
props: FieldEditorProps;
|
|
23
22
|
projection: ProjectionModel;
|
|
24
23
|
};
|
|
25
24
|
|
|
26
|
-
// TODO(wittjosiah): ECHO objects don't work when passed via Storybook args.
|
|
27
25
|
const useTestProjection = () => {
|
|
28
26
|
return useMemo(() => {
|
|
29
27
|
const schema = createEchoSchema(Example);
|
|
30
|
-
const view =
|
|
28
|
+
const view = ViewModel.make({
|
|
31
29
|
name: 'Test',
|
|
32
30
|
query: Query.select(Filter.type(Example)),
|
|
33
31
|
jsonSchema: schema.jsonSchema,
|
|
@@ -82,7 +80,7 @@ const meta = {
|
|
|
82
80
|
title: 'ui/react-ui-form/FieldEditor',
|
|
83
81
|
component: FieldEditor as any,
|
|
84
82
|
render: DefaultStory,
|
|
85
|
-
decorators: [withTheme],
|
|
83
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
86
84
|
parameters: {
|
|
87
85
|
layout: 'fullscreen',
|
|
88
86
|
translations,
|
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { Obj, type SchemaRegistry } from '@dxos/echo';
|
|
7
|
+
import { Obj, type SchemaRegistry, type View } from '@dxos/echo';
|
|
8
8
|
import { type EchoSchema, 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';
|
|
12
12
|
import {
|
|
13
|
-
type FieldType,
|
|
14
13
|
type ProjectionModel,
|
|
15
14
|
type PropertyType,
|
|
16
15
|
formatToAdditionalPropertyAttributes,
|
|
@@ -23,7 +22,7 @@ import { Form, type FormFieldMap, type FormRootProps, SelectField, SelectOptionF
|
|
|
23
22
|
|
|
24
23
|
export type FieldEditorProps = {
|
|
25
24
|
projection: ProjectionModel;
|
|
26
|
-
field: FieldType;
|
|
25
|
+
field: View.FieldType;
|
|
27
26
|
registry?: SchemaRegistry.SchemaRegistry;
|
|
28
27
|
view?: Obj.Unknown;
|
|
29
28
|
onSave: () => void;
|
|
@@ -69,7 +68,7 @@ export const FieldEditor = ({ readonly, projection, field, registry, view, onSav
|
|
|
69
68
|
{...props}
|
|
70
69
|
options={FormatEnums.filter((value) => value !== Format.TypeFormat.None).map((value) => ({
|
|
71
70
|
value,
|
|
72
|
-
label: t(`format
|
|
71
|
+
label: t(`format.${value}.label`),
|
|
73
72
|
}))}
|
|
74
73
|
/>
|
|
75
74
|
),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Form
|
|
2
|
+
|
|
3
|
+
## New features/polish
|
|
4
|
+
|
|
5
|
+
- [x] Unify readonly/inline modes.
|
|
6
|
+
- [x] Refs
|
|
7
|
+
- [x] Single-select (fix popover)
|
|
8
|
+
- [x] Multi-select (array)
|
|
9
|
+
- [ ] auto save doesn't work for combobox + select due to only firing on blur (workaround is to use onValuesChanged).
|
|
10
|
+
- [ ] Don't call save/autoSave if value hasn't changed.
|
|
11
|
+
- [ ] Fix onCancel (restore values).
|
|
12
|
+
- [ ] Fix useSchema Type.AnyObj cast.
|
|
13
|
+
- [ ] TableCellEditor (handleEnter/ModalController).
|
|
14
|
+
- [ ] Use FormFieldWrapper uniformly
|
|
15
|
+
- [ ] Inline tables.
|
|
16
|
+
- [ ] Defer query until popover.
|
|
17
|
+
- [ ] Omit id from sub properties (tune Omit)
|
|
@@ -6,13 +6,13 @@ 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, Tag, Type } from '@dxos/echo';
|
|
9
|
+
import { 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
|
-
import {
|
|
12
|
+
import { useSpaces } from '@dxos/react-client/echo';
|
|
13
13
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
14
14
|
import { Tooltip } from '@dxos/react-ui';
|
|
15
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
15
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
16
16
|
|
|
17
17
|
import { translations } from '../../translations';
|
|
18
18
|
import { TestLayout } from '../testing';
|
|
@@ -23,7 +23,7 @@ const Organization = Schema.Struct({
|
|
|
23
23
|
name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
|
|
24
24
|
}).pipe(
|
|
25
25
|
Type.object({
|
|
26
|
-
typename: 'example.
|
|
26
|
+
typename: 'com.example.type.organization',
|
|
27
27
|
version: '0.1.0',
|
|
28
28
|
}),
|
|
29
29
|
);
|
|
@@ -46,8 +46,8 @@ const Person = Schema.Struct({
|
|
|
46
46
|
zip: Schema.Number,
|
|
47
47
|
}).annotations({ title: 'Address' }),
|
|
48
48
|
),
|
|
49
|
-
employer: Schema.optional(
|
|
50
|
-
tags: Schema.optional(Schema.Array(
|
|
49
|
+
employer: Schema.optional(Ref.Ref(Organization).annotations({ title: 'Employer' })),
|
|
50
|
+
tags: Schema.optional(Schema.Array(Ref.Ref(Tag.Tag)).annotations({ title: 'Tags' })),
|
|
51
51
|
status: Schema.optional(Schema.Literal('active', 'inactive').annotations({ title: 'Status' })),
|
|
52
52
|
notes: Schema.optional(Format.Text.annotations({ title: 'Notes' })),
|
|
53
53
|
location: Schema.optional(Format.GeoPoint.annotations({ title: 'Location' })),
|
|
@@ -65,14 +65,14 @@ const Person = Schema.Struct({
|
|
|
65
65
|
),
|
|
66
66
|
}).pipe(
|
|
67
67
|
Type.object({
|
|
68
|
-
typename: 'dxos.
|
|
68
|
+
typename: 'org.dxos.type.person', // TODO(burdon): Change all types to /schema
|
|
69
69
|
version: '0.1.0',
|
|
70
70
|
}),
|
|
71
71
|
);
|
|
72
72
|
|
|
73
73
|
export interface Person extends Schema.Schema.Type<typeof Person> {}
|
|
74
74
|
|
|
75
|
-
type
|
|
75
|
+
type DefaultStoryProps<T extends AnyProperties> = {
|
|
76
76
|
debug?: boolean;
|
|
77
77
|
schema: Schema.Schema<T>;
|
|
78
78
|
} & FormRootProps<T>;
|
|
@@ -82,10 +82,10 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
|
|
|
82
82
|
schema,
|
|
83
83
|
values: valuesProp,
|
|
84
84
|
...props
|
|
85
|
-
}:
|
|
85
|
+
}: DefaultStoryProps<T>) => {
|
|
86
86
|
const [values, setValues] = useState<Partial<T>>(valuesProp ?? {});
|
|
87
|
-
const
|
|
88
|
-
const space =
|
|
87
|
+
const spaces = useSpaces();
|
|
88
|
+
const space = spaces[0];
|
|
89
89
|
|
|
90
90
|
const handleSave = useCallback<NonNullable<FormRootProps<T>['onSave']>>((values) => {
|
|
91
91
|
log.info('save', { values, meta });
|
|
@@ -97,13 +97,17 @@ const DefaultStory = <T extends AnyProperties = AnyProperties>({
|
|
|
97
97
|
setValues(valuesProp ?? {});
|
|
98
98
|
}, []);
|
|
99
99
|
|
|
100
|
+
if (!space) {
|
|
101
|
+
return <></>;
|
|
102
|
+
}
|
|
103
|
+
|
|
100
104
|
return (
|
|
101
105
|
<Tooltip.Provider>
|
|
102
106
|
<TestLayout json={{ values, schema: schema.ast }}>
|
|
103
107
|
<Form.Root
|
|
104
108
|
debug={debug}
|
|
105
109
|
schema={schema}
|
|
106
|
-
|
|
110
|
+
defaultValues={values}
|
|
107
111
|
db={space.db}
|
|
108
112
|
onSave={handleSave}
|
|
109
113
|
onCancel={handleCancel}
|
|
@@ -127,21 +131,16 @@ const meta = {
|
|
|
127
131
|
render: DefaultStory,
|
|
128
132
|
|
|
129
133
|
decorators: [
|
|
130
|
-
withTheme,
|
|
134
|
+
withTheme(),
|
|
135
|
+
withLayout({ layout: 'fullscreen' }),
|
|
131
136
|
withClientProvider({
|
|
132
137
|
createIdentity: true,
|
|
133
138
|
createSpace: true,
|
|
134
139
|
types: [Tag.Tag, Organization, Person],
|
|
135
|
-
|
|
136
|
-
const space = client.spaces.default;
|
|
140
|
+
onCreateSpace: ({ space }) => {
|
|
137
141
|
[
|
|
138
|
-
Obj.make(
|
|
139
|
-
Obj.make(Organization, { name:
|
|
140
|
-
Obj.make(Organization, { name: 'Backed' }),
|
|
141
|
-
Obj.make(Organization, { name: 'BCV' }),
|
|
142
|
-
Tag.make({ label: 'Tag 1' }),
|
|
143
|
-
Tag.make({ label: 'Tag 2' }),
|
|
144
|
-
Tag.make({ label: 'Tag 3' }),
|
|
142
|
+
...Array.from({ length: 3 }).map((_, i) => Obj.make(Tag.Tag, { label: `Tag ${i}` })),
|
|
143
|
+
...Array.from({ length: 50 }).map((_, i) => Obj.make(Organization, { name: `Organization ${i}` })),
|
|
145
144
|
].map((obj) => space.db.add(obj));
|
|
146
145
|
},
|
|
147
146
|
}),
|
|
@@ -150,11 +149,11 @@ const meta = {
|
|
|
150
149
|
layout: 'fullscreen',
|
|
151
150
|
translations,
|
|
152
151
|
},
|
|
153
|
-
} satisfies Meta<
|
|
152
|
+
} satisfies Meta<DefaultStoryProps<any>>;
|
|
154
153
|
|
|
155
154
|
export default meta;
|
|
156
155
|
|
|
157
|
-
type Story<T extends AnyProperties> = StoryObj<
|
|
156
|
+
type Story<T extends AnyProperties> = StoryObj<DefaultStoryProps<T>>;
|
|
158
157
|
|
|
159
158
|
const values: Partial<Person> = {
|
|
160
159
|
name: 'Alice',
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { createContext } from '@radix-ui/react-context';
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
|
-
import
|
|
7
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
8
8
|
import React, { type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
|
|
9
9
|
|
|
10
10
|
import { type AnyProperties } from '@dxos/echo/internal';
|
|
11
11
|
import { createJsonPath, getValue as getValue$ } from '@dxos/effect';
|
|
12
12
|
import { IconButton, type IconButtonProps, ScrollArea, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
13
|
-
import { mx } from '@dxos/ui-theme';
|
|
13
|
+
import { composable, composableProps, mx } from '@dxos/ui-theme';
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
type FormHandler,
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
useKeyHandler,
|
|
21
21
|
} from '../../hooks';
|
|
22
22
|
import { translationKey } from '../../translations';
|
|
23
|
-
import { setValueEchoAware } from '../../util';
|
|
24
23
|
|
|
25
24
|
import { FormFieldLabel, type FormFieldLabelProps, type FormFieldStateProps } from './FormFieldComponent';
|
|
26
25
|
import {
|
|
@@ -28,22 +27,6 @@ import {
|
|
|
28
27
|
type FormFieldSetProps as NaturalFormFieldSetProps,
|
|
29
28
|
} from './FormFieldSet';
|
|
30
29
|
|
|
31
|
-
// New features/polish
|
|
32
|
-
// [x] Unify readonly/inline modes
|
|
33
|
-
// [ ] Don't call save/autoSave if value hasn't changed
|
|
34
|
-
// [ ] Fix onCancel (restore values)
|
|
35
|
-
// [ ] Fix useSchema Type.Obj.Any cast
|
|
36
|
-
// [ ] Remove @dxos/echo-db deps
|
|
37
|
-
// [ ] TableCellEditor (handleEnter/ModalController).
|
|
38
|
-
// [ ] Use FormFieldWrapper uniformly
|
|
39
|
-
// [ ] Inline tables for object arrays
|
|
40
|
-
// [ ] Defer query until popover
|
|
41
|
-
// [ ] Omit id from sub properties.
|
|
42
|
-
// [x] Refs
|
|
43
|
-
// [x] Single-select (fix popover)
|
|
44
|
-
// [x] Multi-select (array)
|
|
45
|
-
// [ ] auto save doesn't work for combobox + select due to only firing on blur (workaround is to use onValuesChanged)
|
|
46
|
-
|
|
47
30
|
// TODO(burdon): Move to @dxos/schema (re-export here).
|
|
48
31
|
export type ExcludeId<S extends Schema.Schema.AnyNoContext> = Omit<Schema.Schema.Type<S>, 'id'>;
|
|
49
32
|
|
|
@@ -70,7 +53,10 @@ type FormContextValue<T extends AnyProperties = any> = {
|
|
|
70
53
|
* Testing.
|
|
71
54
|
*/
|
|
72
55
|
testId?: string;
|
|
73
|
-
} & Pick<
|
|
56
|
+
} & Pick<
|
|
57
|
+
NaturalFormFieldSetProps<T>,
|
|
58
|
+
'readonly' | 'layout' | 'fieldMap' | 'fieldProvider' | 'projection' | 'createTypename' | 'createFieldMap'
|
|
59
|
+
>;
|
|
74
60
|
|
|
75
61
|
const [FormContextProvider, useFormContext] = createContext<FormContextValue>('Form');
|
|
76
62
|
|
|
@@ -84,19 +70,25 @@ const useFormValues: {
|
|
|
84
70
|
path: (string | number)[] | undefined,
|
|
85
71
|
defaultValue: () => T,
|
|
86
72
|
): T | undefined;
|
|
87
|
-
} = (componentName: string, path
|
|
88
|
-
const
|
|
73
|
+
} = (componentName: string, path?: (string | number)[], defaultValue?: () => any) => {
|
|
74
|
+
const stablePath = useMemo(() => path ?? [], [path ? path.join('.') : undefined]);
|
|
75
|
+
const jsonPath = createJsonPath(stablePath);
|
|
89
76
|
const {
|
|
90
|
-
form: { values },
|
|
77
|
+
form: { values, onValueChange },
|
|
91
78
|
} = useFormContext(componentName);
|
|
92
79
|
|
|
93
80
|
const value = getValue$(values, jsonPath);
|
|
94
81
|
|
|
82
|
+
// Apply default value once when the field has no value. lastAppliedPathRef prevents
|
|
83
|
+
// re-applying on every render (e.g. when defaultValue() returns null) and ensures
|
|
84
|
+
// we apply per path when the hook is used for different fields.
|
|
85
|
+
const lastAppliedPathRef = useRef<string | null>(null);
|
|
95
86
|
useEffect(() => {
|
|
96
|
-
if (
|
|
97
|
-
|
|
87
|
+
if (value == null && defaultValue && lastAppliedPathRef.current !== jsonPath) {
|
|
88
|
+
lastAppliedPathRef.current = jsonPath;
|
|
89
|
+
onValueChange(stablePath, SchemaAST.stringKeyword, defaultValue());
|
|
98
90
|
}
|
|
99
|
-
}, [value, defaultValue]);
|
|
91
|
+
}, [value, defaultValue, onValueChange, stablePath, jsonPath]);
|
|
100
92
|
|
|
101
93
|
return value;
|
|
102
94
|
};
|
|
@@ -164,65 +156,64 @@ FormRoot.displayName = 'Form.Root';
|
|
|
164
156
|
// Viewport
|
|
165
157
|
//
|
|
166
158
|
|
|
167
|
-
|
|
159
|
+
const FORM_VIEWPORT_NAME = 'Form.Viewport';
|
|
160
|
+
|
|
161
|
+
type FormViewportProps = {};
|
|
168
162
|
|
|
169
|
-
const FormViewport = ({
|
|
163
|
+
const FormViewport = composable<HTMLDivElement>(({ children, ...props }, forwardedRef) => {
|
|
170
164
|
return (
|
|
171
|
-
<ScrollArea.Root>
|
|
172
|
-
<ScrollArea.Viewport
|
|
173
|
-
<ScrollArea.Scrollbar orientation='vertical'>
|
|
174
|
-
<ScrollArea.Thumb />
|
|
175
|
-
</ScrollArea.Scrollbar>
|
|
165
|
+
<ScrollArea.Root {...composableProps(props)} orientation='vertical' centered padding thin ref={forwardedRef}>
|
|
166
|
+
<ScrollArea.Viewport>{children}</ScrollArea.Viewport>
|
|
176
167
|
</ScrollArea.Root>
|
|
177
168
|
);
|
|
178
|
-
};
|
|
169
|
+
});
|
|
179
170
|
|
|
180
|
-
FormViewport.displayName =
|
|
171
|
+
FormViewport.displayName = FORM_VIEWPORT_NAME;
|
|
181
172
|
|
|
182
173
|
//
|
|
183
174
|
// Content
|
|
184
175
|
//
|
|
185
176
|
|
|
177
|
+
const FORM_CONTENT_NAME = 'Form.Content';
|
|
178
|
+
|
|
186
179
|
type FormContentProps = ThemedClassName<PropsWithChildren<{}>>;
|
|
187
180
|
|
|
188
|
-
// TOOD(burdon): Figure out nesting (indent and testId).
|
|
189
181
|
const FormContent = ({ classNames, children }: FormContentProps) => {
|
|
190
|
-
const { form, testId } = useFormContext(
|
|
182
|
+
const { form, testId } = useFormContext(FORM_CONTENT_NAME);
|
|
191
183
|
const ref = useRef<HTMLDivElement>(null);
|
|
192
184
|
useKeyHandler(ref.current, form);
|
|
193
185
|
|
|
194
186
|
return (
|
|
195
|
-
<div
|
|
196
|
-
ref={ref}
|
|
197
|
-
role='form'
|
|
198
|
-
className={mx('flex flex-col is-full pli-cardSpacingInline density-fine', classNames)}
|
|
199
|
-
data-testid={testId}
|
|
200
|
-
>
|
|
187
|
+
<div ref={ref} role='form' className={mx('flex flex-col w-full', classNames)} data-testid={testId}>
|
|
201
188
|
{children}
|
|
202
189
|
</div>
|
|
203
190
|
);
|
|
204
191
|
};
|
|
205
192
|
|
|
206
|
-
FormContent.displayName =
|
|
193
|
+
FormContent.displayName = FORM_CONTENT_NAME;
|
|
207
194
|
|
|
208
195
|
//
|
|
209
196
|
// FieldSet
|
|
210
197
|
//
|
|
211
198
|
|
|
212
|
-
|
|
199
|
+
const FORM_FIELDSET_NAME = 'Form.FieldSet';
|
|
200
|
+
|
|
201
|
+
type FormFieldSetProps = ThemedClassName<NaturalFormFieldSetProps<any>>;
|
|
213
202
|
|
|
214
|
-
const FormFieldSet = (
|
|
215
|
-
const { form, ...
|
|
203
|
+
const FormFieldSet = (props: FormFieldSetProps) => {
|
|
204
|
+
const { form, ...contextProps } = useFormContext(FORM_FIELDSET_NAME);
|
|
216
205
|
|
|
217
|
-
return <NaturalFormFieldSet
|
|
206
|
+
return <NaturalFormFieldSet schema={form.schema} {...contextProps} {...props} />;
|
|
218
207
|
};
|
|
219
208
|
|
|
220
|
-
FormFieldSet.displayName =
|
|
209
|
+
FormFieldSet.displayName = FORM_FIELDSET_NAME;
|
|
221
210
|
|
|
222
211
|
//
|
|
223
212
|
// Actions
|
|
224
213
|
//
|
|
225
214
|
|
|
215
|
+
const FORM_ACTIONS_NAME = 'Form.Actions';
|
|
216
|
+
|
|
226
217
|
type FormActionsProps = ThemedClassName<{}>;
|
|
227
218
|
|
|
228
219
|
const FormActions = ({ classNames }: FormActionsProps) => {
|
|
@@ -231,22 +222,21 @@ const FormActions = ({ classNames }: FormActionsProps) => {
|
|
|
231
222
|
form: { canSave, onSave, onCancel },
|
|
232
223
|
readonly,
|
|
233
224
|
layout,
|
|
234
|
-
} = useFormContext(
|
|
225
|
+
} = useFormContext(FORM_ACTIONS_NAME);
|
|
235
226
|
|
|
236
227
|
if (readonly || layout === 'static') {
|
|
237
228
|
return null;
|
|
238
229
|
}
|
|
239
|
-
|
|
240
230
|
// TODO(burdon): Currently onCancel is a no-op; implement "revert values".
|
|
241
231
|
// Deprecate FormSubmit ans use FormActions without Cancel button if no callback is supplied.
|
|
242
232
|
|
|
243
233
|
return (
|
|
244
|
-
<div role='none' className={mx('grid grid-flow-col auto-cols-fr
|
|
234
|
+
<div role='none' className={mx('grid grid-flow-col gap-form-gap auto-cols-fr py-form-padding', classNames)}>
|
|
245
235
|
{onCancel && (
|
|
246
236
|
<IconButton
|
|
247
237
|
icon='ph--x--regular'
|
|
248
238
|
iconEnd
|
|
249
|
-
label={t('cancel
|
|
239
|
+
label={t('cancel-button.label')}
|
|
250
240
|
onClick={onCancel}
|
|
251
241
|
data-testid='cancel-button'
|
|
252
242
|
/>
|
|
@@ -258,7 +248,7 @@ const FormActions = ({ classNames }: FormActionsProps) => {
|
|
|
258
248
|
disabled={!canSave}
|
|
259
249
|
icon='ph--check--regular'
|
|
260
250
|
iconEnd
|
|
261
|
-
label={t('save
|
|
251
|
+
label={t('save-button.label')}
|
|
262
252
|
onClick={onSave}
|
|
263
253
|
data-testid='save-button'
|
|
264
254
|
/>
|
|
@@ -267,35 +257,37 @@ const FormActions = ({ classNames }: FormActionsProps) => {
|
|
|
267
257
|
);
|
|
268
258
|
};
|
|
269
259
|
|
|
270
|
-
FormActions.displayName =
|
|
260
|
+
FormActions.displayName = FORM_ACTIONS_NAME;
|
|
271
261
|
|
|
272
262
|
//
|
|
273
263
|
// Submit
|
|
274
264
|
//
|
|
275
265
|
|
|
276
|
-
|
|
266
|
+
const FORM_SUBMIT_NAME = 'Form.Submit';
|
|
267
|
+
|
|
268
|
+
type FormSubmitProps = ThemedClassName<Partial<Pick<IconButtonProps, 'icon' | 'label' | 'disabled'>>>;
|
|
277
269
|
|
|
278
|
-
const FormSubmit = ({ classNames, label, icon }: FormSubmitProps) => {
|
|
270
|
+
const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
|
|
279
271
|
const { t } = useTranslation(translationKey);
|
|
280
272
|
const {
|
|
281
273
|
form: { canSave, onSave },
|
|
282
274
|
readonly,
|
|
283
275
|
layout,
|
|
284
|
-
} = useFormContext(
|
|
276
|
+
} = useFormContext(FORM_SUBMIT_NAME);
|
|
285
277
|
|
|
286
278
|
if (readonly || layout === 'static') {
|
|
287
279
|
return null;
|
|
288
280
|
}
|
|
289
281
|
|
|
290
282
|
return (
|
|
291
|
-
<div role='none' className={mx('flex
|
|
283
|
+
<div role='none' className={mx('flex w-full pt-form-padding', classNames)}>
|
|
292
284
|
<IconButton
|
|
293
|
-
classNames='
|
|
285
|
+
classNames='w-full'
|
|
294
286
|
type='submit'
|
|
295
287
|
variant='primary'
|
|
296
|
-
disabled={!canSave}
|
|
288
|
+
disabled={disabled ?? !canSave}
|
|
297
289
|
icon={icon ?? 'ph--check--regular'}
|
|
298
|
-
label={label ?? t('save
|
|
290
|
+
label={label ?? t('save-button.label')}
|
|
299
291
|
onClick={onSave}
|
|
300
292
|
data-testid='save-button'
|
|
301
293
|
/>
|
|
@@ -303,7 +295,7 @@ const FormSubmit = ({ classNames, label, icon }: FormSubmitProps) => {
|
|
|
303
295
|
);
|
|
304
296
|
};
|
|
305
297
|
|
|
306
|
-
FormSubmit.displayName =
|
|
298
|
+
FormSubmit.displayName = FORM_SUBMIT_NAME;
|
|
307
299
|
|
|
308
300
|
//
|
|
309
301
|
// Form
|
|
@@ -315,9 +307,9 @@ export const Form = {
|
|
|
315
307
|
Viewport: FormViewport,
|
|
316
308
|
Content: FormContent,
|
|
317
309
|
FieldSet: FormFieldSet,
|
|
310
|
+
Label: FormFieldLabel,
|
|
318
311
|
Actions: FormActions,
|
|
319
312
|
Submit: FormSubmit,
|
|
320
|
-
Label: FormFieldLabel,
|
|
321
313
|
};
|
|
322
314
|
|
|
323
315
|
export { useFormContext, useFormValues, useFormFieldState };
|
|
@@ -327,6 +319,7 @@ export type {
|
|
|
327
319
|
FormViewportProps,
|
|
328
320
|
FormContentProps,
|
|
329
321
|
FormFieldSetProps,
|
|
322
|
+
FormFieldLabelProps,
|
|
330
323
|
FormActionsProps,
|
|
331
|
-
|
|
324
|
+
FormSubmitProps,
|
|
332
325
|
};
|