@dxos/react-ui-form 0.8.4-main.8360d9e660 → 0.8.4-main.8baae0fced
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/LICENSE +102 -5
- package/dist/lib/browser/index.mjs +1009 -612
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +82 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1009 -612
- 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 +84 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +63 -61
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +40 -12
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +76 -74
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +11 -5
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +8 -6
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +5 -8
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +4 -4
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +8 -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 +84 -0
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +12 -6
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectField.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 +63 -61
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +9 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
- package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +26 -4
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +63 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +127 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.d.ts +28 -33
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts +64 -61
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
- package/dist/types/src/components/Settings/SettingsSelectField.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 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +3 -4
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +65 -63
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +4 -4
- 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/hooks/useKeyHandler.d.ts +2 -1
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +63 -61
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field-type.d.ts +19 -0
- package/dist/types/src/util/field-type.d.ts.map +1 -0
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +7 -1
- package/dist/types/src/util/properties.d.ts.map +1 -1
- package/dist/types/src/util/properties.test.d.ts +2 -0
- package/dist/types/src/util/properties.test.d.ts.map +1 -0
- 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 +43 -41
- package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -2
- package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
- package/src/components/FieldEditor/FieldEditor.tsx +4 -3
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +23 -36
- package/src/components/Form/Form.tsx +77 -48
- package/src/components/Form/FormField.tsx +27 -15
- package/src/components/Form/FormFieldComponent.tsx +6 -3
- package/src/components/Form/FormFieldSet.tsx +49 -32
- package/src/components/Form/fields/ArrayField.tsx +14 -8
- package/src/components/Form/fields/GeoPointField.tsx +8 -7
- package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
- package/src/components/Form/fields/MarkdownField.tsx +135 -45
- package/src/components/Form/fields/RefField.tsx +70 -35
- package/src/components/Form/fields/SelectOptionField.stories.tsx +8 -6
- package/src/components/Form/fields/SelectOptionField.tsx +12 -12
- package/src/components/Form/fields/TextField.tsx +2 -2
- package/src/components/Form/fields/TupleField.tsx +1 -1
- package/src/components/ObjectForm/ObjectForm.tsx +114 -0
- package/src/components/ObjectForm/index.ts +5 -0
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +12 -10
- package/src/components/ObjectPicker/ObjectPicker.tsx +92 -65
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +362 -0
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
- package/src/components/ObjectProperties/ObjectProperties.tsx +148 -0
- package/src/components/ObjectProperties/index.ts +5 -0
- package/src/components/Settings/AUDIT.md +51 -0
- package/src/components/Settings/Settings.stories.tsx +69 -21
- package/src/components/Settings/Settings.tsx +33 -183
- package/src/components/Settings/SettingsFieldSet.tsx +257 -0
- package/src/components/Settings/SettingsItem.tsx +58 -0
- package/src/components/Settings/SettingsSelectField.tsx +59 -0
- package/src/components/Settings/index.ts +2 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +12 -13
- package/src/components/ViewEditor/ViewEditor.test.tsx +0 -1
- package/src/components/ViewEditor/ViewEditor.tsx +25 -32
- package/src/components/index.ts +2 -0
- package/src/components/testing.tsx +19 -11
- package/src/hooks/useKeyHandler.tsx +7 -7
- package/src/index.ts +0 -4
- package/src/translations.ts +66 -63
- package/src/util/field-type.ts +60 -0
- package/src/util/index.ts +1 -0
- package/src/util/properties.test.ts +63 -0
- package/src/util/properties.ts +14 -3
- package/src/util/refs.ts +7 -3
|
@@ -6,8 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import { DXN, Filter, JsonSchema, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
|
|
10
|
-
import { type View } from '@dxos/echo';
|
|
9
|
+
import { DXN, Filter, JsonSchema, Obj, Query, type QueryAST, Tag, Type, type View } from '@dxos/echo';
|
|
11
10
|
import { type EchoSchema, Format, type Mutable } from '@dxos/echo/internal';
|
|
12
11
|
import { useQuery } from '@dxos/react-client/echo';
|
|
13
12
|
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
@@ -16,9 +15,9 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
|
16
15
|
import { type ProjectionModel, ViewModel, getTypenameFromQuery } from '@dxos/schema';
|
|
17
16
|
import { Employer, Organization, Person, Pipeline } from '@dxos/types';
|
|
18
17
|
|
|
19
|
-
import { translations } from '
|
|
20
|
-
import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
18
|
+
import { translations } from '#translations';
|
|
21
19
|
|
|
20
|
+
import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
22
21
|
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
23
22
|
|
|
24
23
|
const types = [
|
|
@@ -36,9 +35,9 @@ export type ViewEditorDebugObjects = {
|
|
|
36
35
|
projection: ProjectionModel;
|
|
37
36
|
};
|
|
38
37
|
|
|
39
|
-
type
|
|
38
|
+
type DefaultStoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
|
|
40
39
|
|
|
41
|
-
const DefaultStory = (props:
|
|
40
|
+
const DefaultStory = (props: DefaultStoryProps) => {
|
|
42
41
|
const { space } = useClientStory();
|
|
43
42
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
44
43
|
const [view, setView] = useState<View.View>();
|
|
@@ -90,9 +89,9 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
90
89
|
|
|
91
90
|
if (props.mode === 'tag') {
|
|
92
91
|
const queue = target && DXN.tryParse(target) ? target : undefined;
|
|
93
|
-
const query = queue ? Query.fromAst(newQuery).from({
|
|
94
|
-
Obj.
|
|
95
|
-
|
|
92
|
+
const query = queue ? Query.fromAst(newQuery).from({ feeds: [queue] }) : Query.fromAst(newQuery);
|
|
93
|
+
Obj.update(view, (view) => {
|
|
94
|
+
view.query.ast = query.ast as Mutable<typeof query.ast>;
|
|
96
95
|
});
|
|
97
96
|
|
|
98
97
|
const typename = getTypenameFromQuery(query.ast);
|
|
@@ -105,13 +104,13 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
105
104
|
query,
|
|
106
105
|
jsonSchema: newSchema.jsonSchema,
|
|
107
106
|
});
|
|
108
|
-
Obj.
|
|
109
|
-
|
|
107
|
+
Obj.update(view, (view) => {
|
|
108
|
+
view.projection = Obj.getSnapshot(newView).projection as Mutable<typeof view.projection>;
|
|
110
109
|
});
|
|
111
110
|
setSchema(() => newSchema);
|
|
112
111
|
} else {
|
|
113
|
-
Obj.
|
|
114
|
-
|
|
112
|
+
Obj.update(view, (view) => {
|
|
113
|
+
view.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
115
114
|
});
|
|
116
115
|
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
117
116
|
}
|
|
@@ -11,7 +11,6 @@ import { EchoSchema, isInstanceOf } from '@dxos/echo/internal';
|
|
|
11
11
|
import { ProjectionModel } from '@dxos/schema';
|
|
12
12
|
|
|
13
13
|
import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
14
|
-
|
|
15
14
|
import * as stories from './ViewEditor.stories';
|
|
16
15
|
import { type ViewEditorDebugObjects } from './ViewEditor.stories';
|
|
17
16
|
|
|
@@ -9,8 +9,7 @@ import * as Option from 'effect/Option';
|
|
|
9
9
|
import * as Schema from 'effect/Schema';
|
|
10
10
|
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
|
|
11
11
|
|
|
12
|
-
import { Entity, Feed, Filter, Format, Obj, Query, QueryAST, Ref, type SchemaRegistry } from '@dxos/echo';
|
|
13
|
-
import { View } from '@dxos/echo';
|
|
12
|
+
import { Entity, Feed, Filter, Format, Obj, Query, QueryAST, Ref, type SchemaRegistry, View } from '@dxos/echo';
|
|
14
13
|
import { EchoSchema, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
|
|
15
14
|
import { invariant } from '@dxos/invariant';
|
|
16
15
|
import { useObject, useQuery } from '@dxos/react-client/echo';
|
|
@@ -24,9 +23,10 @@ import {
|
|
|
24
23
|
createEchoChangeCallback,
|
|
25
24
|
getTypenameFromQuery,
|
|
26
25
|
} from '@dxos/schema';
|
|
27
|
-
import { mx, osTranslations
|
|
26
|
+
import { mx, osTranslations } from '@dxos/ui-theme';
|
|
27
|
+
|
|
28
|
+
import { translationKey } from '#translations';
|
|
28
29
|
|
|
29
|
-
import { translationKey } from '../../translations';
|
|
30
30
|
import { FieldEditor } from '../FieldEditor';
|
|
31
31
|
import {
|
|
32
32
|
Form,
|
|
@@ -100,8 +100,8 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
100
100
|
if (from._tag !== 'scope') {
|
|
101
101
|
return undefined;
|
|
102
102
|
}
|
|
103
|
-
return Option.fromNullable(from.scope.
|
|
104
|
-
Option.flatMap((
|
|
103
|
+
return Option.fromNullable(from.scope.feeds).pipe(
|
|
104
|
+
Option.flatMap((feeds) => Array.head(feeds)),
|
|
105
105
|
Option.map(String),
|
|
106
106
|
Option.getOrUndefined,
|
|
107
107
|
);
|
|
@@ -160,13 +160,13 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
160
160
|
const handleUpdate = useCallback(
|
|
161
161
|
(values: any) => {
|
|
162
162
|
const targetValue = values.target;
|
|
163
|
-
let
|
|
163
|
+
let feedDXN: string | undefined;
|
|
164
164
|
|
|
165
165
|
if (Ref.isRef(targetValue)) {
|
|
166
|
-
const
|
|
167
|
-
const feed = feeds.find((feed) => Obj.getDXN(feed).toString() ===
|
|
166
|
+
const targetDXN = targetValue.dxn.toString();
|
|
167
|
+
const feed = feeds.find((feed) => Obj.getDXN(feed).toString() === targetDXN);
|
|
168
168
|
if (feed) {
|
|
169
|
-
|
|
169
|
+
feedDXN = Feed.getQueueDxn(feed)?.toString();
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -175,7 +175,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
175
175
|
mode === 'schema'
|
|
176
176
|
? Query.select(Filter.typename(values.query)).ast
|
|
177
177
|
: JSON.parse(JSON.stringify(values.query));
|
|
178
|
-
onQueryChanged?.(query,
|
|
178
|
+
onQueryChanged?.(query, feedDXN);
|
|
179
179
|
},
|
|
180
180
|
[onQueryChanged, mode, feeds],
|
|
181
181
|
);
|
|
@@ -189,11 +189,11 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
189
189
|
);
|
|
190
190
|
|
|
191
191
|
return (
|
|
192
|
-
<div
|
|
192
|
+
<div className={mx(classNames)}>
|
|
193
193
|
{/* If readonly is set, then the callout is not needed. */}
|
|
194
194
|
{schemaReadonly && !readonly && (
|
|
195
195
|
<Message.Root valence='info' classNames='my-form-padding'>
|
|
196
|
-
<Message.Title>{t('system
|
|
196
|
+
<Message.Title>{t('system-schema.description')}</Message.Title>
|
|
197
197
|
</Message.Root>
|
|
198
198
|
)}
|
|
199
199
|
|
|
@@ -201,7 +201,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
201
201
|
<Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} db={db} onValuesChanged={handleUpdate}>
|
|
202
202
|
<Form.FieldSet />
|
|
203
203
|
|
|
204
|
-
<FormFieldLabel label={t('fields
|
|
204
|
+
<FormFieldLabel label={t('fields.label')} asChild />
|
|
205
205
|
<FieldList
|
|
206
206
|
schema={schema}
|
|
207
207
|
view={view}
|
|
@@ -274,12 +274,12 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
274
274
|
const handleMove = useCallback(
|
|
275
275
|
(fromIndex: number, toIndex: number) => {
|
|
276
276
|
invariant(!readonly);
|
|
277
|
-
Obj.
|
|
277
|
+
Obj.update(view, (view) => {
|
|
278
278
|
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
279
|
-
const fields = [...
|
|
279
|
+
const fields = [...view.projection.fields];
|
|
280
280
|
const [moved] = fields.splice(fromIndex, 1);
|
|
281
281
|
fields.splice(toIndex, 0, moved);
|
|
282
|
-
|
|
282
|
+
view.projection.fields = fields;
|
|
283
283
|
});
|
|
284
284
|
},
|
|
285
285
|
[view, readonly],
|
|
@@ -317,7 +317,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
317
317
|
>
|
|
318
318
|
{({ items: fields }) => (
|
|
319
319
|
<>
|
|
320
|
-
{showHeading && <h3 className='text-sm'>{t('field
|
|
320
|
+
{showHeading && <h3 className='text-sm'>{t('field-path.label')}</h3>}
|
|
321
321
|
<div role='list' className='grid grid-cols-[min-content_1fr_min-content_min-content_min-content]'>
|
|
322
322
|
{fields?.map((field) => {
|
|
323
323
|
const hidden = field.visible === false;
|
|
@@ -328,20 +328,13 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
328
328
|
classNames={'grid grid-cols-subgrid col-span-5'}
|
|
329
329
|
aria-expanded={expandedField === field.id}
|
|
330
330
|
>
|
|
331
|
-
<div
|
|
332
|
-
role='none'
|
|
333
|
-
className={mx(
|
|
334
|
-
subtleHover,
|
|
335
|
-
'grid grid-cols-subgrid col-span-5',
|
|
336
|
-
'rounded-xs cursor-pointer min-h-10',
|
|
337
|
-
)}
|
|
338
|
-
>
|
|
331
|
+
<div className='grid grid-cols-subgrid col-span-5 rounded-xs cursor-pointer min-h-10 dx-hover'>
|
|
339
332
|
<List.ItemDragHandle disabled={readonly || schemaReadonly} />
|
|
340
333
|
<List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
|
|
341
334
|
{field.path}
|
|
342
335
|
</List.ItemTitle>
|
|
343
336
|
<List.ItemIconButton
|
|
344
|
-
label={t(hidden ? 'show
|
|
337
|
+
label={t(hidden ? 'show-field.label' : 'hide-field.label')}
|
|
345
338
|
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
346
339
|
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
347
340
|
autoHide={false}
|
|
@@ -351,7 +344,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
351
344
|
{!readonly && (
|
|
352
345
|
<>
|
|
353
346
|
<List.ItemDeleteButton
|
|
354
|
-
label={t('delete
|
|
347
|
+
label={t('delete-field.label')}
|
|
355
348
|
autoHide={false}
|
|
356
349
|
disabled={readonly || schemaReadonly || viewSnapshot.projection.fields.length <= 1}
|
|
357
350
|
onClick={() => handleDelete(field.id)}
|
|
@@ -360,7 +353,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
360
353
|
<List.ItemIconButton
|
|
361
354
|
iconOnly
|
|
362
355
|
variant='ghost'
|
|
363
|
-
label={t('toggle
|
|
356
|
+
label={t('toggle-expand.label', { ns: osTranslations })}
|
|
364
357
|
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
365
358
|
onClick={() => handleToggleField(field)}
|
|
366
359
|
data-testid='field.toggle'
|
|
@@ -369,7 +362,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
369
362
|
)}
|
|
370
363
|
</div>
|
|
371
364
|
{expandedField === field.id && !readonly && (
|
|
372
|
-
<div
|
|
365
|
+
<div className='col-span-full mt-1 mb-1 border border-separator rounded-md'>
|
|
373
366
|
<FieldEditor
|
|
374
367
|
readonly={readonly || schemaReadonly}
|
|
375
368
|
registry={registry}
|
|
@@ -384,10 +377,10 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
384
377
|
})}
|
|
385
378
|
</div>
|
|
386
379
|
{!readonly && !expandedField && (
|
|
387
|
-
<div
|
|
380
|
+
<div className='my-form-padding'>
|
|
388
381
|
<IconButton
|
|
389
382
|
icon='ph--plus--regular'
|
|
390
|
-
label={t('add
|
|
383
|
+
label={t('add-property-button.label')}
|
|
391
384
|
onClick={handleAdd}
|
|
392
385
|
disabled={viewSnapshot.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
393
386
|
classNames='w-full'
|
package/src/components/index.ts
CHANGED
|
@@ -5,17 +5,24 @@
|
|
|
5
5
|
import React, { type PropsWithChildren } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Syntax } from '@dxos/react-ui-syntax-highlighter';
|
|
9
|
+
import { composableProps, slottable } from '@dxos/ui-theme';
|
|
10
10
|
|
|
11
|
-
type TestLayoutProps =
|
|
11
|
+
type TestLayoutProps = PropsWithChildren<{ json?: any }>;
|
|
12
12
|
|
|
13
|
-
export const TestLayout = ({
|
|
13
|
+
export const TestLayout = ({ children, json }: TestLayoutProps) => {
|
|
14
14
|
return (
|
|
15
|
-
<div className='
|
|
16
|
-
<TestPanel
|
|
17
|
-
<TestPanel
|
|
18
|
-
<
|
|
15
|
+
<div className='dx-container grid grid-cols-[1fr_1fr] p-4 gap-4'>
|
|
16
|
+
<TestPanel>{children}</TestPanel>
|
|
17
|
+
<TestPanel>
|
|
18
|
+
<Syntax.Root data={json}>
|
|
19
|
+
<Syntax.Content>
|
|
20
|
+
<Syntax.Filter />
|
|
21
|
+
<Syntax.Viewport>
|
|
22
|
+
<Syntax.Code testId='debug' classNames='text-xs' />
|
|
23
|
+
</Syntax.Viewport>
|
|
24
|
+
</Syntax.Content>
|
|
25
|
+
</Syntax.Root>
|
|
19
26
|
</TestPanel>
|
|
20
27
|
</div>
|
|
21
28
|
);
|
|
@@ -23,14 +30,15 @@ export const TestLayout = ({ classNames, children, json }: TestLayoutProps) => {
|
|
|
23
30
|
|
|
24
31
|
type TestPanelProps = ThemedClassName<PropsWithChildren>;
|
|
25
32
|
|
|
26
|
-
const TestPanel = ({
|
|
33
|
+
const TestPanel = slottable<HTMLDivElement, TestPanelProps>(({ children }, forwardedRef) => {
|
|
27
34
|
return (
|
|
28
|
-
<div
|
|
35
|
+
<div {...composableProps({ classNames: 'dx-container bg-modal-surface rounded-md' })} ref={forwardedRef}>
|
|
29
36
|
{children}
|
|
30
37
|
</div>
|
|
31
38
|
);
|
|
32
|
-
};
|
|
39
|
+
});
|
|
33
40
|
|
|
34
41
|
// Symbol for accessing debug objects in tests.
|
|
35
42
|
export const VIEW_EDITOR_DEBUG_SYMBOL = Symbol('viewEditorDebug');
|
|
36
43
|
export const FIELD_EDITOR_DEBUG_SYMBOL = Symbol('fieldEditorDebug');
|
|
44
|
+
export const OBJECT_PROPERTIES_DEBUG_SYMBOL = Symbol('objectPropertiesDebug');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { useCallback,
|
|
5
|
+
import { type RefObject, useCallback, useLayoutEffect } from 'react';
|
|
6
6
|
|
|
7
7
|
import { addEventListener } from '@dxos/async';
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ import { type FormHandler } from './useFormHandler';
|
|
|
11
11
|
/**
|
|
12
12
|
* Key handler.
|
|
13
13
|
*/
|
|
14
|
-
export const useKeyHandler = (
|
|
14
|
+
export const useKeyHandler = (elRef: RefObject<HTMLDivElement | null>, form: FormHandler<any>) => {
|
|
15
15
|
const handleKeyDown = useCallback(
|
|
16
16
|
(event: KeyboardEvent) => {
|
|
17
17
|
switch (event.key) {
|
|
@@ -26,12 +26,12 @@ export const useKeyHandler = (formElement: HTMLDivElement | null, form: FormHand
|
|
|
26
26
|
[form.isValid, form.canSave, form.onSave, form.autoSave],
|
|
27
27
|
);
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
useLayoutEffect(() => {
|
|
30
|
+
const el = elRef.current;
|
|
31
|
+
if (!el) {
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}, [formElement, handleKeyDown]);
|
|
35
|
+
return addEventListener(el, 'keydown', handleKeyDown);
|
|
36
|
+
}, [elRef, handleKeyDown]);
|
|
37
37
|
};
|
package/src/index.ts
CHANGED
package/src/translations.ts
CHANGED
|
@@ -10,79 +10,82 @@ export const translations = [
|
|
|
10
10
|
{
|
|
11
11
|
'en-US': {
|
|
12
12
|
[translationKey]: {
|
|
13
|
-
'fields
|
|
14
|
-
'hidden
|
|
15
|
-
'empty
|
|
13
|
+
'fields.label': 'Fields',
|
|
14
|
+
'hidden-fields.label': 'Hidden Fields',
|
|
15
|
+
'empty-readonly-ref-field.label': '(none)',
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
'
|
|
19
|
-
'field
|
|
20
|
-
'field
|
|
21
|
-
'field format label': 'Type',
|
|
22
|
-
'field path label': 'Field path',
|
|
23
|
-
'add field': 'Add field',
|
|
24
|
-
'delete field': 'Delete field',
|
|
25
|
-
'field limit reached': 'Maximum number of fields reached',
|
|
26
|
-
'boolean input true value': 'Yes',
|
|
27
|
-
'boolean input false value': 'No',
|
|
28
|
-
'show field label': 'Show field',
|
|
29
|
-
'hide field label': 'Hide field',
|
|
30
|
-
'delete field label': 'Delete field',
|
|
31
|
-
'ref field combobox input placeholder': 'Search…',
|
|
32
|
-
'ref field placeholder_one': 'Select…',
|
|
33
|
-
'ref field placeholder_other': 'Select items…',
|
|
17
|
+
'field-property.label': 'Property',
|
|
18
|
+
'field-property.placeholder': 'Property name',
|
|
19
|
+
'field-format.label': 'Type',
|
|
20
|
+
'field-path.label': 'Field path',
|
|
34
21
|
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
22
|
+
'add-item.button': 'Add item',
|
|
23
|
+
'add-property-button.label': 'Add property',
|
|
24
|
+
'boolean-input-true.value': 'Yes',
|
|
25
|
+
'boolean-input-false.value': 'No',
|
|
26
|
+
'show-field.label': 'Show field',
|
|
27
|
+
'hide-field.label': 'Hide field',
|
|
28
|
+
'delete-field.label': 'Delete field',
|
|
29
|
+
'create-option.label': 'Create',
|
|
30
|
+
'add-tag.label': 'Add tag',
|
|
38
31
|
|
|
39
|
-
'
|
|
40
|
-
'
|
|
32
|
+
'ref-field-combobox-input.placeholder': 'Search…',
|
|
33
|
+
'ref-field.placeholder': 'Select…',
|
|
34
|
+
'ref-field.placeholder_one': 'Select…',
|
|
35
|
+
'ref-field.placeholder_other': 'Select items…',
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'format
|
|
52
|
-
'format
|
|
53
|
-
'format
|
|
54
|
-
'format
|
|
55
|
-
'format
|
|
56
|
-
'format
|
|
57
|
-
'format
|
|
58
|
-
'format
|
|
59
|
-
'format
|
|
60
|
-
'format
|
|
61
|
-
'format
|
|
62
|
-
'format
|
|
63
|
-
'format
|
|
64
|
-
'format
|
|
65
|
-
'format
|
|
66
|
-
'format
|
|
67
|
-
'format
|
|
68
|
-
'format
|
|
69
|
-
'format
|
|
70
|
-
'format
|
|
37
|
+
'example.placeholder': 'Example',
|
|
38
|
+
'latitude.placeholder': 'Latitude (e.g., 40.7128)',
|
|
39
|
+
'longitude.placeholder': 'Longitude (e.g., -74.0060)',
|
|
40
|
+
|
|
41
|
+
'cancel-button.label': 'Cancel',
|
|
42
|
+
'save-button.label': 'Save',
|
|
43
|
+
'create-text.label': 'Create text',
|
|
44
|
+
|
|
45
|
+
// TypeFormat
|
|
46
|
+
'format.boolean.label': 'Boolean',
|
|
47
|
+
'format.currency.label': 'Currency',
|
|
48
|
+
'format.date.label': 'Date',
|
|
49
|
+
'format.date-time.label': 'DateTime',
|
|
50
|
+
'format.did.label': 'DID',
|
|
51
|
+
'format.duration.label': 'Duration',
|
|
52
|
+
'format.dxn.label': 'DXN',
|
|
53
|
+
'format.email.label': 'Email',
|
|
54
|
+
'format.formula.label': 'Formula',
|
|
55
|
+
'format.hostname.label': 'Hostname',
|
|
56
|
+
'format.integer.label': 'Integer',
|
|
57
|
+
'format.json.label': 'JSON',
|
|
58
|
+
'format.lnglat.label': 'Geopoint',
|
|
59
|
+
'format.markdown.label': 'Markdown',
|
|
60
|
+
'format.percent.label': 'Percent',
|
|
61
|
+
'format.ref.label': 'Reference',
|
|
62
|
+
'format.regex.label': 'RegExp',
|
|
63
|
+
'format.string.label': 'String',
|
|
64
|
+
'format.single-select.label': 'Select',
|
|
65
|
+
'format.multi-select.label': 'Multi-select',
|
|
66
|
+
'format.text.label': 'Long text',
|
|
67
|
+
'format.time.label': 'Time',
|
|
68
|
+
'format.timestamp.label': 'Timestamp',
|
|
69
|
+
'format.user.label': 'User',
|
|
70
|
+
'format.number.label': 'Number',
|
|
71
|
+
'format.uri.label': 'URI',
|
|
72
|
+
'format.url.label': 'URL',
|
|
73
|
+
'format.uuid.label': 'UUID',
|
|
71
74
|
|
|
72
75
|
// GeoPointField.
|
|
73
|
-
'latitude
|
|
74
|
-
'longitude
|
|
76
|
+
'latitude.label': 'Latitude',
|
|
77
|
+
'longitude.label': 'Longitude',
|
|
75
78
|
|
|
76
79
|
// SelectOptionsField.
|
|
77
|
-
'select
|
|
78
|
-
'select
|
|
79
|
-
'select
|
|
80
|
-
'select
|
|
81
|
-
'select option add': 'Add option',
|
|
80
|
+
'select-option.label': 'Label',
|
|
81
|
+
'select-option-label.placeholder': 'Option label',
|
|
82
|
+
'select-option-delete.button': 'Delete',
|
|
83
|
+
'select-option-add.button': 'Add option',
|
|
82
84
|
|
|
83
85
|
// System schema message.
|
|
84
|
-
'system
|
|
85
|
-
'system
|
|
86
|
+
'system-schema.title': 'System type',
|
|
87
|
+
'system-schema.description': 'System type: cannot add or remove fields.',
|
|
88
|
+
'remove.button': 'Remove',
|
|
86
89
|
},
|
|
87
90
|
},
|
|
88
91
|
},
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
6
|
+
|
|
7
|
+
import { getAnnotation, isLiteralUnion } from '@dxos/effect';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detected field type for a schema property.
|
|
11
|
+
*/
|
|
12
|
+
export type SettingsFieldType = 'boolean' | 'string' | 'number' | 'select' | 'struct';
|
|
13
|
+
|
|
14
|
+
export type SelectOption = {
|
|
15
|
+
value: string | number;
|
|
16
|
+
label?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect the field type from an Effect Schema AST node.
|
|
21
|
+
*/
|
|
22
|
+
export const detectFieldType = (ast: SchemaAST.AST): SettingsFieldType | undefined => {
|
|
23
|
+
if (isLiteralUnion(ast)) {
|
|
24
|
+
return 'select';
|
|
25
|
+
}
|
|
26
|
+
if (SchemaAST.isTypeLiteral(ast)) {
|
|
27
|
+
return 'struct';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch (ast._tag) {
|
|
31
|
+
case 'BooleanKeyword':
|
|
32
|
+
return 'boolean';
|
|
33
|
+
case 'StringKeyword':
|
|
34
|
+
return 'string';
|
|
35
|
+
case 'NumberKeyword':
|
|
36
|
+
return 'number';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract select options from a literal union AST node.
|
|
44
|
+
* Reads title annotations from individual literals for labels.
|
|
45
|
+
*/
|
|
46
|
+
export const getSelectOptionsFromAst = (ast: SchemaAST.AST): SelectOption[] | undefined => {
|
|
47
|
+
if (!isLiteralUnion(ast)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (ast as SchemaAST.Union<SchemaAST.Literal>).types.flatMap((literalNode) => {
|
|
52
|
+
const value = literalNode.literal;
|
|
53
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const title = getAnnotation<string>(SchemaAST.TitleAnnotationId, false)(literalNode);
|
|
57
|
+
const label = title ?? (typeof value === 'string' ? value.charAt(0).toUpperCase() + value.slice(1) : String(value));
|
|
58
|
+
return [{ value, label }];
|
|
59
|
+
});
|
|
60
|
+
};
|
package/src/util/index.ts
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import { describe, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { Annotation, JsonSchema, Type } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { getFormProperties } from './properties';
|
|
11
|
+
|
|
12
|
+
describe('getFormProperties', () => {
|
|
13
|
+
test('filters out keyword fields annotated FormInputAnnotation.set(false)', ({ expect }) => {
|
|
14
|
+
const TestSchema = Schema.Struct({
|
|
15
|
+
name: Schema.optional(Schema.String),
|
|
16
|
+
hidden: Schema.String.pipe(Annotation.FormInputAnnotation.set(false)),
|
|
17
|
+
}).pipe(Type.object({ typename: 'org.dxos.test.keyword-hidden', version: '0.1.0' }));
|
|
18
|
+
|
|
19
|
+
const names = getFormProperties(TestSchema.ast).map((prop) => prop.name);
|
|
20
|
+
expect(names).toContain('name');
|
|
21
|
+
expect(names).not.toContain('hidden');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('filters out struct fields annotated FormInputAnnotation.set(false)', ({ expect }) => {
|
|
25
|
+
// Regression: JsonSchema fields are a Schema.Struct; encodedBoundAST strips
|
|
26
|
+
// annotations from non-keyword inner types, which previously bypassed the
|
|
27
|
+
// form-input filter (e.g., Routine.input).
|
|
28
|
+
const TestSchema = Schema.Struct({
|
|
29
|
+
name: Schema.optional(Schema.String),
|
|
30
|
+
schema: JsonSchema.JsonSchema.pipe(Annotation.FormInputAnnotation.set(false)),
|
|
31
|
+
}).pipe(Type.object({ typename: 'org.dxos.test.struct-hidden', version: '0.1.0' }));
|
|
32
|
+
|
|
33
|
+
const names = getFormProperties(TestSchema.ast).map((prop) => prop.name);
|
|
34
|
+
expect(names).toContain('name');
|
|
35
|
+
expect(names).not.toContain('schema');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('filters out array fields annotated FormInputAnnotation.set(false)', ({ expect }) => {
|
|
39
|
+
const TestSchema = Schema.Struct({
|
|
40
|
+
name: Schema.optional(Schema.String),
|
|
41
|
+
context: Schema.Array(Schema.Any).pipe(Annotation.FormInputAnnotation.set(false)),
|
|
42
|
+
}).pipe(Type.object({ typename: 'org.dxos.test.array-hidden', version: '0.1.0' }));
|
|
43
|
+
|
|
44
|
+
const names = getFormProperties(TestSchema.ast).map((prop) => prop.name);
|
|
45
|
+
expect(names).toContain('name');
|
|
46
|
+
expect(names).not.toContain('context');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('preserves annotation when chained with .annotations()', ({ expect }) => {
|
|
50
|
+
// Regression: `.pipe(FormInputAnnotation.set(false)).annotations({...})` must
|
|
51
|
+
// not lose the form-input annotation.
|
|
52
|
+
const TestSchema = Schema.Struct({
|
|
53
|
+
name: Schema.optional(Schema.String),
|
|
54
|
+
hidden: JsonSchema.JsonSchema.pipe(Annotation.FormInputAnnotation.set(false)).annotations({
|
|
55
|
+
description: 'Hidden field',
|
|
56
|
+
}),
|
|
57
|
+
}).pipe(Type.object({ typename: 'org.dxos.test.chained-hidden', version: '0.1.0' }));
|
|
58
|
+
|
|
59
|
+
const names = getFormProperties(TestSchema.ast).map((prop) => prop.name);
|
|
60
|
+
expect(names).toContain('name');
|
|
61
|
+
expect(names).not.toContain('hidden');
|
|
62
|
+
});
|
|
63
|
+
});
|