@dxos/react-ui-form 0.8.4-main.d05539e30a → 0.8.4-main.d9fc60f731
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +1335 -561
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +2 -1
- package/dist/lib/browser/translations.mjs.map +2 -2
- package/dist/lib/node-esm/index.mjs +1335 -561
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +2 -1
- package/dist/lib/node-esm/translations.mjs.map +2 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +2 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +2 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +35 -9
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +76 -16
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +14 -4
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +22 -10
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +8 -2
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormTooltipsContext.d.ts +19 -0
- package/dist/types/src/components/Form/FormTooltipsContext.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.d.ts +50 -0
- package/dist/types/src/components/Form/Layout/FormLayout.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts +119 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/annotation.d.ts +30 -0
- package/dist/types/src/components/Form/Layout/annotation.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/index.d.ts +4 -0
- package/dist/types/src/components/Form/Layout/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/parser.d.ts +31 -0
- package/dist/types/src/components/Form/Layout/parser.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/parser.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/parser.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/DateField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/DateField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/InlineRefField.d.ts +13 -0
- package/dist/types/src/components/Form/fields/InlineRefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +2 -1
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectField.d.ts +4 -1
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +2 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/index.d.ts +2 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/index.d.ts +3 -0
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/meta-tags.d.ts +30 -0
- package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -0
- package/dist/types/src/components/Form/meta-tags.test.d.ts +2 -0
- package/dist/types/src/components/Form/meta-tags.test.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +3 -4
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +2 -2
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +2 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +2 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectTree/ObjectTree.d.ts +9 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.d.ts.map +1 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts +87 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectTree/index.d.ts +2 -0
- package/dist/types/src/components/ObjectTree/index.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts +2 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +7 -8
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +4 -4
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +9 -0
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +11 -0
- package/dist/types/src/util/properties.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +37 -32
- package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -2
- package/src/components/FieldEditor/FieldEditor.test.tsx +4 -1
- package/src/components/FieldEditor/FieldEditor.tsx +19 -14
- package/src/components/Form/Form.stories.tsx +21 -27
- package/src/components/Form/Form.tsx +106 -19
- package/src/components/Form/FormField.tsx +85 -28
- package/src/components/Form/FormFieldComponent.tsx +75 -20
- package/src/components/Form/FormFieldSet.tsx +32 -3
- package/src/components/Form/FormTooltipsContext.ts +25 -0
- package/src/components/Form/Layout/FormLayout.stories.test.tsx +51 -0
- package/src/components/Form/Layout/FormLayout.stories.tsx +422 -0
- package/src/components/Form/Layout/FormLayout.test.ts +59 -0
- package/src/components/Form/Layout/FormLayout.tsx +251 -0
- package/src/components/Form/Layout/annotation.ts +38 -0
- package/src/components/Form/Layout/index.ts +7 -0
- package/src/components/Form/Layout/parser.test.ts +120 -0
- package/src/components/Form/Layout/parser.ts +167 -0
- package/src/components/Form/fields/ArrayField.tsx +61 -38
- package/src/components/Form/fields/DateField.tsx +101 -0
- package/src/components/Form/fields/GeoPointField.tsx +4 -1
- package/src/components/Form/fields/InlineRefField.tsx +146 -0
- package/src/components/Form/fields/MarkdownField.tsx +3 -3
- package/src/components/Form/fields/RefField.tsx +29 -23
- package/src/components/Form/fields/SelectField.tsx +17 -5
- package/src/components/Form/fields/SelectOptionField.tsx +4 -3
- package/src/components/Form/fields/TextAreaField.tsx +1 -1
- package/src/components/Form/fields/TupleField.tsx +2 -1
- package/src/components/Form/fields/index.ts +2 -0
- package/src/components/Form/index.ts +3 -0
- package/src/components/Form/meta-tags.test.ts +47 -0
- package/src/components/Form/meta-tags.ts +33 -0
- package/src/components/ObjectForm/ObjectForm.tsx +21 -26
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +2 -2
- package/src/components/ObjectPicker/ObjectPicker.tsx +2 -2
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +14 -26
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +20 -10
- package/src/components/ObjectProperties/ObjectProperties.tsx +26 -30
- package/src/components/ObjectTree/ObjectTree.stories.tsx +134 -0
- package/src/components/ObjectTree/ObjectTree.tsx +190 -0
- package/src/components/ObjectTree/index.ts +5 -0
- package/src/components/Settings/Settings.tsx +2 -1
- package/src/components/Settings/SettingsItem.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +35 -40
- package/src/components/ViewEditor/ViewEditor.test.tsx +11 -8
- package/src/components/ViewEditor/ViewEditor.tsx +29 -32
- package/src/components/index.ts +1 -0
- package/src/components/testing.tsx +4 -4
- package/src/translations.ts +2 -1
- package/src/util/properties.test.ts +67 -9
- package/src/util/properties.ts +36 -4
- package/src/vite-env.d.ts +10 -0
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
-
import * as Array from 'effect/Array';
|
|
7
6
|
import * as Match from 'effect/Match';
|
|
8
7
|
import * as Option from 'effect/Option';
|
|
9
8
|
import * as Schema from 'effect/Schema';
|
|
10
9
|
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
|
|
11
10
|
|
|
12
|
-
import { Entity, Feed, Filter, Format, Obj, Query, QueryAST, Ref, type
|
|
13
|
-
import {
|
|
11
|
+
import { EID, Entity, Feed, Filter, Format, Obj, Query, QueryAST, Ref, type Registry, Type, View } from '@dxos/echo';
|
|
12
|
+
import { type JsonProp } from '@dxos/echo/internal';
|
|
14
13
|
import { invariant } from '@dxos/invariant';
|
|
15
14
|
import { useObject, useQuery } from '@dxos/react-client/echo';
|
|
16
15
|
import { IconButton, Input, Message, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
@@ -39,12 +38,12 @@ import {
|
|
|
39
38
|
|
|
40
39
|
export type ViewEditorProps = ThemedClassName<
|
|
41
40
|
{
|
|
42
|
-
|
|
41
|
+
type: Type.AnyEntity;
|
|
43
42
|
view: View.View;
|
|
44
43
|
mode?: 'schema' | 'tag';
|
|
45
|
-
registry?:
|
|
44
|
+
registry?: Registry.Registry;
|
|
46
45
|
showHeading?: boolean;
|
|
47
|
-
onQueryChanged?: (query: QueryAST.Query, target?:
|
|
46
|
+
onQueryChanged?: (query: QueryAST.Query, target?: EID.EID) => void;
|
|
48
47
|
onDelete?: (fieldId: string) => void;
|
|
49
48
|
} & Pick<QueryFormProps, 'types' | 'tags'> &
|
|
50
49
|
Pick<FormRootProps<any>, 'readonly' | 'db'>
|
|
@@ -57,7 +56,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
57
56
|
(
|
|
58
57
|
{
|
|
59
58
|
classNames,
|
|
60
|
-
|
|
59
|
+
type,
|
|
61
60
|
view,
|
|
62
61
|
mode = 'schema',
|
|
63
62
|
registry,
|
|
@@ -72,16 +71,15 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
72
71
|
forwardedRef,
|
|
73
72
|
) => {
|
|
74
73
|
const atomRegistry = useContext(RegistryContext);
|
|
75
|
-
const schemaReadonly =
|
|
74
|
+
const schemaReadonly = Type.getDatabase(type) == null;
|
|
76
75
|
const { t } = useTranslation(translationKey);
|
|
77
76
|
|
|
78
77
|
const projectionModel = useMemo(() => {
|
|
79
|
-
|
|
80
|
-
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
78
|
+
const jsonSchema = type.jsonSchema;
|
|
81
79
|
|
|
82
80
|
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
83
|
-
// Pass
|
|
84
|
-
const change = createEchoChangeCallback(view,
|
|
81
|
+
// Pass type only when mutable to allow schema mutations.
|
|
82
|
+
const change = createEchoChangeCallback(view, Type.getDatabase(type) != null ? type : undefined);
|
|
85
83
|
|
|
86
84
|
const model = new ProjectionModel({
|
|
87
85
|
registry: atomRegistry,
|
|
@@ -91,7 +89,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
91
89
|
});
|
|
92
90
|
|
|
93
91
|
return model;
|
|
94
|
-
}, [atomRegistry,
|
|
92
|
+
}, [atomRegistry, type, view]);
|
|
95
93
|
|
|
96
94
|
useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
|
|
97
95
|
|
|
@@ -100,9 +98,9 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
100
98
|
if (from._tag !== 'scope') {
|
|
101
99
|
return undefined;
|
|
102
100
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Option.map(
|
|
101
|
+
const feedScope = from.scopes.find((s) => s._tag === 'feed');
|
|
102
|
+
return Option.fromNullable(feedScope).pipe(
|
|
103
|
+
Option.map((s) => s.feedUri),
|
|
106
104
|
Option.getOrUndefined,
|
|
107
105
|
);
|
|
108
106
|
}),
|
|
@@ -115,8 +113,8 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
115
113
|
if (!queueTarget) {
|
|
116
114
|
return undefined;
|
|
117
115
|
}
|
|
118
|
-
const feed = feeds.find((feed) => Feed.
|
|
119
|
-
return feed ? Ref.
|
|
116
|
+
const feed = feeds.find((feed) => Feed.getQueueUri(feed)?.toString() === queueTarget);
|
|
117
|
+
return feed ? Ref.fromURI(Entity.getURI(feed)) : undefined;
|
|
120
118
|
}, [queueTarget, feeds]);
|
|
121
119
|
|
|
122
120
|
const viewSchema = useMemo(() => {
|
|
@@ -160,13 +158,13 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
160
158
|
const handleUpdate = useCallback(
|
|
161
159
|
(values: any) => {
|
|
162
160
|
const targetValue = values.target;
|
|
163
|
-
let
|
|
161
|
+
let queueDxn: EID.EID | undefined;
|
|
164
162
|
|
|
165
163
|
if (Ref.isRef(targetValue)) {
|
|
166
|
-
const
|
|
167
|
-
const feed = feeds.find((feed) => Obj.
|
|
164
|
+
const feedUri = targetValue.uri;
|
|
165
|
+
const feed = feeds.find((feed) => Obj.getURI(feed) === feedUri);
|
|
168
166
|
if (feed) {
|
|
169
|
-
|
|
167
|
+
queueDxn = Feed.getQueueUri(feed);
|
|
170
168
|
}
|
|
171
169
|
}
|
|
172
170
|
|
|
@@ -175,7 +173,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
175
173
|
mode === 'schema'
|
|
176
174
|
? Query.select(Filter.typename(values.query)).ast
|
|
177
175
|
: JSON.parse(JSON.stringify(values.query));
|
|
178
|
-
onQueryChanged?.(query,
|
|
176
|
+
onQueryChanged?.(query, queueDxn);
|
|
179
177
|
},
|
|
180
178
|
[onQueryChanged, mode, feeds],
|
|
181
179
|
);
|
|
@@ -203,7 +201,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
203
201
|
|
|
204
202
|
<FormFieldLabel label={t('fields.label')} asChild />
|
|
205
203
|
<FieldList
|
|
206
|
-
|
|
204
|
+
type={type}
|
|
207
205
|
view={view}
|
|
208
206
|
registry={registry}
|
|
209
207
|
readonly={readonly}
|
|
@@ -216,23 +214,22 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
216
214
|
},
|
|
217
215
|
);
|
|
218
216
|
|
|
219
|
-
type FieldListProps = Pick<ViewEditorProps, '
|
|
217
|
+
type FieldListProps = Pick<ViewEditorProps, 'type' | 'view' | 'registry' | 'readonly' | 'showHeading' | 'onDelete'>;
|
|
220
218
|
|
|
221
|
-
const FieldList = ({
|
|
219
|
+
const FieldList = ({ type, view, registry, readonly, showHeading = false, onDelete }: FieldListProps) => {
|
|
222
220
|
const atomRegistry = useContext(RegistryContext);
|
|
223
|
-
const schemaReadonly =
|
|
221
|
+
const schemaReadonly = Type.getDatabase(type) == null;
|
|
224
222
|
const { t } = useTranslation(translationKey);
|
|
225
223
|
|
|
226
224
|
// Subscribe to view changes for reactivity.
|
|
227
225
|
const [viewSnapshot] = useObject(view);
|
|
228
226
|
|
|
229
227
|
const projectionModel = useMemo(() => {
|
|
230
|
-
|
|
231
|
-
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
228
|
+
const jsonSchema = type.jsonSchema;
|
|
232
229
|
|
|
233
230
|
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
234
|
-
// Pass
|
|
235
|
-
const change = createEchoChangeCallback(view,
|
|
231
|
+
// Pass type only when mutable to allow schema mutations.
|
|
232
|
+
const change = createEchoChangeCallback(view, Type.getDatabase(type) != null ? type : undefined);
|
|
236
233
|
|
|
237
234
|
const model = new ProjectionModel({
|
|
238
235
|
registry: atomRegistry,
|
|
@@ -242,7 +239,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
242
239
|
});
|
|
243
240
|
|
|
244
241
|
return model;
|
|
245
|
-
}, [atomRegistry,
|
|
242
|
+
}, [atomRegistry, type, view]);
|
|
246
243
|
|
|
247
244
|
const [expandedField, setExpandedField] = useState<View.FieldType['id']>();
|
|
248
245
|
|
package/src/components/index.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import React, { type PropsWithChildren } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
|
+
import { composableProps, slottable } from '@dxos/react-ui';
|
|
8
9
|
import { Syntax } from '@dxos/react-ui-syntax-highlighter';
|
|
9
|
-
import { composableProps, slottable } from '@dxos/ui-theme';
|
|
10
10
|
|
|
11
11
|
type TestLayoutProps = PropsWithChildren<{ json?: any }>;
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ export const TestLayout = ({ children, json }: TestLayoutProps) => {
|
|
|
19
19
|
<Syntax.Content>
|
|
20
20
|
<Syntax.Filter />
|
|
21
21
|
<Syntax.Viewport>
|
|
22
|
-
<Syntax.Code testId='debug' classNames='text-
|
|
22
|
+
<Syntax.Code testId='debug' classNames='text-sm' />
|
|
23
23
|
</Syntax.Viewport>
|
|
24
24
|
</Syntax.Content>
|
|
25
25
|
</Syntax.Root>
|
|
@@ -30,9 +30,9 @@ export const TestLayout = ({ children, json }: TestLayoutProps) => {
|
|
|
30
30
|
|
|
31
31
|
type TestPanelProps = ThemedClassName<PropsWithChildren>;
|
|
32
32
|
|
|
33
|
-
const TestPanel = slottable<HTMLDivElement, TestPanelProps>(({ children }, forwardedRef) => {
|
|
33
|
+
export const TestPanel = slottable<HTMLDivElement, TestPanelProps>(({ children }, forwardedRef) => {
|
|
34
34
|
return (
|
|
35
|
-
<div {...composableProps({ classNames: 'dx-container bg-
|
|
35
|
+
<div {...composableProps({ classNames: 'dx-container bg-card-surface rounded-md' })} ref={forwardedRef}>
|
|
36
36
|
{children}
|
|
37
37
|
</div>
|
|
38
38
|
);
|
package/src/translations.ts
CHANGED
|
@@ -19,6 +19,7 @@ export const translations = [
|
|
|
19
19
|
'field-format.label': 'Type',
|
|
20
20
|
'field-path.label': 'Field path',
|
|
21
21
|
|
|
22
|
+
'remove-item.button': 'Delete item',
|
|
22
23
|
'add-item.button': 'Add item',
|
|
23
24
|
'add-property-button.label': 'Add property',
|
|
24
25
|
'boolean-input-true.value': 'Yes',
|
|
@@ -85,7 +86,7 @@ export const translations = [
|
|
|
85
86
|
// System schema message.
|
|
86
87
|
'system-schema.title': 'System type',
|
|
87
88
|
'system-schema.description': 'System type: cannot add or remove fields.',
|
|
88
|
-
'remove.button': '
|
|
89
|
+
'remove.button': 'Delete',
|
|
89
90
|
},
|
|
90
91
|
},
|
|
91
92
|
},
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import * as Schema from 'effect/Schema';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
6
7
|
import { describe, test } from 'vitest';
|
|
7
8
|
|
|
8
|
-
import { Annotation, JsonSchema, Type } from '@dxos/echo';
|
|
9
|
+
import { DXN, Annotation, JsonSchema, Type } from '@dxos/echo';
|
|
10
|
+
import { Format } from '@dxos/echo/internal';
|
|
11
|
+
import { findNode, getArrayElementType } from '@dxos/effect';
|
|
9
12
|
|
|
10
13
|
import { getFormProperties } from './properties';
|
|
11
14
|
|
|
@@ -14,9 +17,9 @@ describe('getFormProperties', () => {
|
|
|
14
17
|
const TestSchema = Schema.Struct({
|
|
15
18
|
name: Schema.optional(Schema.String),
|
|
16
19
|
hidden: Schema.String.pipe(Annotation.FormInputAnnotation.set(false)),
|
|
17
|
-
}).pipe(Type.
|
|
20
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.keywordHidden', '0.1.0')));
|
|
18
21
|
|
|
19
|
-
const names = getFormProperties(TestSchema.ast).map((prop) => prop.name);
|
|
22
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
20
23
|
expect(names).toContain('name');
|
|
21
24
|
expect(names).not.toContain('hidden');
|
|
22
25
|
});
|
|
@@ -28,9 +31,9 @@ describe('getFormProperties', () => {
|
|
|
28
31
|
const TestSchema = Schema.Struct({
|
|
29
32
|
name: Schema.optional(Schema.String),
|
|
30
33
|
schema: JsonSchema.JsonSchema.pipe(Annotation.FormInputAnnotation.set(false)),
|
|
31
|
-
}).pipe(Type.
|
|
34
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.structHidden', '0.1.0')));
|
|
32
35
|
|
|
33
|
-
const names = getFormProperties(TestSchema.ast).map((prop) => prop.name);
|
|
36
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
34
37
|
expect(names).toContain('name');
|
|
35
38
|
expect(names).not.toContain('schema');
|
|
36
39
|
});
|
|
@@ -39,13 +42,68 @@ describe('getFormProperties', () => {
|
|
|
39
42
|
const TestSchema = Schema.Struct({
|
|
40
43
|
name: Schema.optional(Schema.String),
|
|
41
44
|
context: Schema.Array(Schema.Any).pipe(Annotation.FormInputAnnotation.set(false)),
|
|
42
|
-
}).pipe(Type.
|
|
45
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.arrayHidden', '0.1.0')));
|
|
43
46
|
|
|
44
|
-
const names = getFormProperties(TestSchema.ast).map((prop) => prop.name);
|
|
47
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
45
48
|
expect(names).toContain('name');
|
|
46
49
|
expect(names).not.toContain('context');
|
|
47
50
|
});
|
|
48
51
|
|
|
52
|
+
test('filters out optional fields annotated FormInputAnnotation.set(false)', ({ expect }) => {
|
|
53
|
+
// Regression: optional fields' `prop.type` is the union `T | undefined`; the
|
|
54
|
+
// annotation lives on the inner `T`. Without unwrapping, the filter would
|
|
55
|
+
// miss the annotation and the hidden field would render. Covers the
|
|
56
|
+
// conventional pattern `Schema.X.pipe(FormInputAnnotation.set(false), Schema.optional)`.
|
|
57
|
+
const TestSchema = Schema.Struct({
|
|
58
|
+
name: Schema.optional(Schema.String),
|
|
59
|
+
userId: Schema.String.pipe(Annotation.FormInputAnnotation.set(false), Schema.optional),
|
|
60
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.optionalHidden', '0.1.0')));
|
|
61
|
+
|
|
62
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
63
|
+
expect(names).toContain('name');
|
|
64
|
+
expect(names).not.toContain('userId');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('preserves a deeply-nested FormInputAnnotation through unions and arrays', ({ expect }) => {
|
|
68
|
+
// Regression: a hidden field nested below a discriminated union and arrays of structs (e.g.
|
|
69
|
+
// Segment.details(road).routes[].legs[].geometry). `encodedBoundAST` strips annotations from
|
|
70
|
+
// non-keyword inner types, so without keeping container types raw the annotation is lost by
|
|
71
|
+
// the time the form recurses to the leaf field. The transformation element (GeoPoint) is the
|
|
72
|
+
// trigger that forces the encode rebuild.
|
|
73
|
+
const Leg = Schema.Struct({
|
|
74
|
+
distance: Schema.Number,
|
|
75
|
+
geometry: Schema.Array(Format.GeoPoint).pipe(Annotation.FormInputAnnotation.set(false)),
|
|
76
|
+
});
|
|
77
|
+
const Route = Schema.Struct({ legs: Schema.Array(Leg) });
|
|
78
|
+
const TestSchema = Schema.Struct({
|
|
79
|
+
details: Schema.Union(
|
|
80
|
+
Schema.TaggedStruct('road', { routes: Schema.optional(Schema.Array(Route)) }),
|
|
81
|
+
Schema.TaggedStruct('other', { note: Schema.optional(Schema.String) }),
|
|
82
|
+
),
|
|
83
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.nestedHidden', '0.1.0')));
|
|
84
|
+
|
|
85
|
+
// Drill as the form does: getFormProperties at each level, descending via the property type
|
|
86
|
+
// (array element / type literal) returned by the previous level.
|
|
87
|
+
const findTypeLiteralWith = (ast: SchemaAST.AST, prop: string) =>
|
|
88
|
+
findNode(
|
|
89
|
+
ast,
|
|
90
|
+
(node) => SchemaAST.isTypeLiteral(node) && SchemaAST.getPropertySignatures(node).some((p) => p.name === prop),
|
|
91
|
+
)!;
|
|
92
|
+
const propType = (ast: SchemaAST.AST, name: string) => getFormProperties(ast).find((p) => p.name === name)!.type;
|
|
93
|
+
|
|
94
|
+
const detailsType = propType(Type.getSchema(TestSchema).ast, 'details');
|
|
95
|
+
const roadTypeLiteral = findTypeLiteralWith(detailsType, 'routes');
|
|
96
|
+
const routeTypeLiteral = findNode(
|
|
97
|
+
getArrayElementType(propType(roadTypeLiteral, 'routes'))!,
|
|
98
|
+
SchemaAST.isTypeLiteral,
|
|
99
|
+
)!;
|
|
100
|
+
const legTypeLiteral = findNode(getArrayElementType(propType(routeTypeLiteral, 'legs'))!, SchemaAST.isTypeLiteral)!;
|
|
101
|
+
|
|
102
|
+
const names = getFormProperties(legTypeLiteral).map((prop) => prop.name);
|
|
103
|
+
expect(names).toContain('distance');
|
|
104
|
+
expect(names).not.toContain('geometry');
|
|
105
|
+
});
|
|
106
|
+
|
|
49
107
|
test('preserves annotation when chained with .annotations()', ({ expect }) => {
|
|
50
108
|
// Regression: `.pipe(FormInputAnnotation.set(false)).annotations({...})` must
|
|
51
109
|
// not lose the form-input annotation.
|
|
@@ -54,9 +112,9 @@ describe('getFormProperties', () => {
|
|
|
54
112
|
hidden: JsonSchema.JsonSchema.pipe(Annotation.FormInputAnnotation.set(false)).annotations({
|
|
55
113
|
description: 'Hidden field',
|
|
56
114
|
}),
|
|
57
|
-
}).pipe(Type.
|
|
115
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.chainedHidden', '0.1.0')));
|
|
58
116
|
|
|
59
|
-
const names = getFormProperties(TestSchema.ast).map((prop) => prop.name);
|
|
117
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
60
118
|
expect(names).toContain('name');
|
|
61
119
|
expect(names).not.toContain('hidden');
|
|
62
120
|
});
|
package/src/util/properties.ts
CHANGED
|
@@ -6,7 +6,20 @@ import * as Option from 'effect/Option';
|
|
|
6
6
|
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
7
|
|
|
8
8
|
import { Annotation } from '@dxos/echo';
|
|
9
|
-
import { type SchemaProperty, getProperties } from '@dxos/effect';
|
|
9
|
+
import { type SchemaProperty, getProperties, isArrayType, isNestedType } from '@dxos/effect';
|
|
10
|
+
|
|
11
|
+
/** The property's type with an optional `T | undefined` union unwrapped to its inner `T`. */
|
|
12
|
+
const unwrapOptional = (prop: SchemaAST.PropertySignature): SchemaAST.AST => {
|
|
13
|
+
if (!prop.isOptional || !SchemaAST.isUnion(prop.type)) {
|
|
14
|
+
return prop.type;
|
|
15
|
+
}
|
|
16
|
+
// Drop the `undefined` member, preserving the remaining union (don't collapse `A | B | undefined` to `A`).
|
|
17
|
+
const defined = prop.type.types.filter((type) => type._tag !== 'UndefinedKeyword');
|
|
18
|
+
if (defined.length === 0) {
|
|
19
|
+
return prop.type;
|
|
20
|
+
}
|
|
21
|
+
return defined.length === 1 ? defined[0] : SchemaAST.Union.make(defined, prop.type.annotations);
|
|
22
|
+
};
|
|
10
23
|
|
|
11
24
|
/**
|
|
12
25
|
* Get the property types of an AST and filter out properties that are not form inputs.
|
|
@@ -16,14 +29,33 @@ import { type SchemaProperty, getProperties } from '@dxos/effect';
|
|
|
16
29
|
* annotations from non-keyword inner types (e.g., `Schema.Struct`, `Schema.Array`).
|
|
17
30
|
* Without this, fields like `Routine.input` (a JsonSchema struct) annotated
|
|
18
31
|
* `FormInputAnnotation.set(false)` would still render in the form.
|
|
32
|
+
*
|
|
33
|
+
* For optional fields, `prop.type` is the union `T | undefined` — the annotation
|
|
34
|
+
* lives on the inner `T`. Unwrap before reading so that the conventional pattern
|
|
35
|
+
* `Schema.String.pipe(FormInputAnnotation.set(false), Schema.optional)` works.
|
|
36
|
+
*
|
|
37
|
+
* Container properties (nested structs, unions, arrays) keep their *raw* AST rather than
|
|
38
|
+
* the `encodedBoundAST` produced by `getProperties`. `encodedBoundAST` strips annotations
|
|
39
|
+
* from non-keyword inner types, so a `FormInputAnnotation` nested beneath an encoded container
|
|
40
|
+
* (e.g. `RouteLeg.geometry`, reached via `Segment → details → routes → Route → legs`) would
|
|
41
|
+
* otherwise be lost by the time recursion reaches it. Leaf/transformation fields stay encoded
|
|
42
|
+
* (e.g. a `DateTime` still renders as its encoded string input).
|
|
19
43
|
*/
|
|
20
44
|
export const getFormProperties = (ast: SchemaAST.AST): SchemaProperty[] => {
|
|
45
|
+
const signatures = SchemaAST.getPropertySignatures(ast);
|
|
46
|
+
const rawByName = new Map<PropertyKey, SchemaAST.AST>(signatures.map((prop) => [prop.name, unwrapOptional(prop)]));
|
|
21
47
|
const hidden = new Set(
|
|
22
|
-
|
|
48
|
+
signatures
|
|
23
49
|
.filter(
|
|
24
|
-
(prop) =>
|
|
50
|
+
(prop) =>
|
|
51
|
+
Annotation.FormInputAnnotation.getFromAst(unwrapOptional(prop)).pipe(Option.getOrElse(() => true)) === false,
|
|
25
52
|
)
|
|
26
53
|
.map((prop) => prop.name),
|
|
27
54
|
);
|
|
28
|
-
return getProperties(ast)
|
|
55
|
+
return getProperties(ast)
|
|
56
|
+
.filter((prop) => !hidden.has(prop.name))
|
|
57
|
+
.map((prop) => {
|
|
58
|
+
const raw = rawByName.get(prop.name);
|
|
59
|
+
return raw && (isNestedType(raw) || isArrayType(raw)) ? { ...prop, type: raw } : prop;
|
|
60
|
+
});
|
|
29
61
|
};
|