@dxos/react-ui-form 0.8.4-main.d05673bc65 → 0.8.4-main.dfabb4ec29
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 +1004 -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 +1004 -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 -7
- 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 -9
- 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 +2 -2
- 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.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 +42 -40
- 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 +5 -3
- package/src/components/Form/FormFieldSet.tsx +98 -77
- 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 +10 -10
- 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 +9 -7
- 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 +141 -0
- package/src/components/ObjectProperties/index.ts +5 -0
- package/src/components/Settings/AUDIT.md +52 -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 +10 -10
- package/src/components/ViewEditor/ViewEditor.test.tsx +0 -1
- package/src/components/ViewEditor/ViewEditor.tsx +15 -15
- 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/refs.ts +7 -3
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Select } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type SettingsFieldProps } from './SettingsFieldSet';
|
|
10
|
+
|
|
11
|
+
export type SelectFieldOption = {
|
|
12
|
+
value: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type CreateSelectFieldOptions = {
|
|
17
|
+
/** Options to display. Strings are used as both value and label. */
|
|
18
|
+
options: ReadonlyArray<string | SelectFieldOption>;
|
|
19
|
+
/** Label for the sentinel option that maps to `undefined`. Defaults to `'Default'`. Pass `null` to omit. */
|
|
20
|
+
defaultLabel?: string | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Factory for a `SettingsFieldSet` `fieldMap` entry that renders a Select control
|
|
25
|
+
* over a fixed list of options, with an optional sentinel for `undefined`.
|
|
26
|
+
*/
|
|
27
|
+
export const createSelectField = ({
|
|
28
|
+
options,
|
|
29
|
+
defaultLabel = 'Default',
|
|
30
|
+
}: CreateSelectFieldOptions): React.FC<SettingsFieldProps<string | undefined>> => {
|
|
31
|
+
const normalized = options.map((option) => (typeof option === 'string' ? { value: option, label: option } : option));
|
|
32
|
+
const hasDefault = defaultLabel !== null;
|
|
33
|
+
// Per-instance sentinel avoids collisions with real option values; empty string when the
|
|
34
|
+
// sentinel is omitted so `undefined` maps to a value Select.Root treats as "no selection".
|
|
35
|
+
const sentinel = hasDefault ? `__default__${Math.random().toString(36).slice(2)}` : '';
|
|
36
|
+
|
|
37
|
+
return ({ value, onChange, readonly }) => (
|
|
38
|
+
<Select.Root
|
|
39
|
+
disabled={readonly}
|
|
40
|
+
value={value ?? sentinel}
|
|
41
|
+
onValueChange={(next) => onChange(hasDefault && next === sentinel ? undefined : next)}
|
|
42
|
+
>
|
|
43
|
+
<Select.TriggerButton disabled={readonly} />
|
|
44
|
+
<Select.Portal>
|
|
45
|
+
<Select.Content>
|
|
46
|
+
<Select.Viewport>
|
|
47
|
+
{hasDefault && <Select.Option value={sentinel}>{defaultLabel}</Select.Option>}
|
|
48
|
+
{normalized.map((option) => (
|
|
49
|
+
<Select.Option key={option.value} value={option.value}>
|
|
50
|
+
{option.label ?? option.value}
|
|
51
|
+
</Select.Option>
|
|
52
|
+
))}
|
|
53
|
+
</Select.Viewport>
|
|
54
|
+
<Select.Arrow />
|
|
55
|
+
</Select.Content>
|
|
56
|
+
</Select.Portal>
|
|
57
|
+
</Select.Root>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -16,9 +16,9 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
|
16
16
|
import { type ProjectionModel, ViewModel, getTypenameFromQuery } from '@dxos/schema';
|
|
17
17
|
import { Employer, Organization, Person, Pipeline } from '@dxos/types';
|
|
18
18
|
|
|
19
|
-
import { translations } from '
|
|
20
|
-
import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
19
|
+
import { translations } from '#translations';
|
|
21
20
|
|
|
21
|
+
import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
22
22
|
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
23
23
|
|
|
24
24
|
const types = [
|
|
@@ -36,9 +36,9 @@ export type ViewEditorDebugObjects = {
|
|
|
36
36
|
projection: ProjectionModel;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
type
|
|
39
|
+
type DefaultStoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
|
|
40
40
|
|
|
41
|
-
const DefaultStory = (props:
|
|
41
|
+
const DefaultStory = (props: DefaultStoryProps) => {
|
|
42
42
|
const { space } = useClientStory();
|
|
43
43
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
44
44
|
const [view, setView] = useState<View.View>();
|
|
@@ -91,8 +91,8 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
91
91
|
if (props.mode === 'tag') {
|
|
92
92
|
const queue = target && DXN.tryParse(target) ? target : undefined;
|
|
93
93
|
const query = queue ? Query.fromAst(newQuery).from({ queues: [queue] }) : Query.fromAst(newQuery);
|
|
94
|
-
Obj.
|
|
95
|
-
|
|
94
|
+
Obj.update(view, (view) => {
|
|
95
|
+
view.query.ast = query.ast as Mutable<typeof query.ast>;
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
const typename = getTypenameFromQuery(query.ast);
|
|
@@ -105,13 +105,13 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
105
105
|
query,
|
|
106
106
|
jsonSchema: newSchema.jsonSchema,
|
|
107
107
|
});
|
|
108
|
-
Obj.
|
|
109
|
-
|
|
108
|
+
Obj.update(view, (view) => {
|
|
109
|
+
view.projection = Obj.getSnapshot(newView).projection as Mutable<typeof view.projection>;
|
|
110
110
|
});
|
|
111
111
|
setSchema(() => newSchema);
|
|
112
112
|
} else {
|
|
113
|
-
Obj.
|
|
114
|
-
|
|
113
|
+
Obj.update(view, (view) => {
|
|
114
|
+
view.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
115
115
|
});
|
|
116
116
|
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
117
117
|
}
|
|
@@ -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
|
|
|
@@ -26,7 +26,8 @@ import {
|
|
|
26
26
|
} from '@dxos/schema';
|
|
27
27
|
import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
|
|
28
28
|
|
|
29
|
-
import { translationKey } from '
|
|
29
|
+
import { translationKey } from '#translations';
|
|
30
|
+
|
|
30
31
|
import { FieldEditor } from '../FieldEditor';
|
|
31
32
|
import {
|
|
32
33
|
Form,
|
|
@@ -189,11 +190,11 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
189
190
|
);
|
|
190
191
|
|
|
191
192
|
return (
|
|
192
|
-
<div
|
|
193
|
+
<div className={mx(classNames)}>
|
|
193
194
|
{/* If readonly is set, then the callout is not needed. */}
|
|
194
195
|
{schemaReadonly && !readonly && (
|
|
195
196
|
<Message.Root valence='info' classNames='my-form-padding'>
|
|
196
|
-
<Message.Title>{t('system
|
|
197
|
+
<Message.Title>{t('system-schema.description')}</Message.Title>
|
|
197
198
|
</Message.Root>
|
|
198
199
|
)}
|
|
199
200
|
|
|
@@ -201,7 +202,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
201
202
|
<Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} db={db} onValuesChanged={handleUpdate}>
|
|
202
203
|
<Form.FieldSet />
|
|
203
204
|
|
|
204
|
-
<FormFieldLabel label={t('fields
|
|
205
|
+
<FormFieldLabel label={t('fields.label')} asChild />
|
|
205
206
|
<FieldList
|
|
206
207
|
schema={schema}
|
|
207
208
|
view={view}
|
|
@@ -274,12 +275,12 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
274
275
|
const handleMove = useCallback(
|
|
275
276
|
(fromIndex: number, toIndex: number) => {
|
|
276
277
|
invariant(!readonly);
|
|
277
|
-
Obj.
|
|
278
|
+
Obj.update(view, (view) => {
|
|
278
279
|
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
279
|
-
const fields = [...
|
|
280
|
+
const fields = [...view.projection.fields];
|
|
280
281
|
const [moved] = fields.splice(fromIndex, 1);
|
|
281
282
|
fields.splice(toIndex, 0, moved);
|
|
282
|
-
|
|
283
|
+
view.projection.fields = fields;
|
|
283
284
|
});
|
|
284
285
|
},
|
|
285
286
|
[view, readonly],
|
|
@@ -317,7 +318,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
317
318
|
>
|
|
318
319
|
{({ items: fields }) => (
|
|
319
320
|
<>
|
|
320
|
-
{showHeading && <h3 className='text-sm'>{t('field
|
|
321
|
+
{showHeading && <h3 className='text-sm'>{t('field-path.label')}</h3>}
|
|
321
322
|
<div role='list' className='grid grid-cols-[min-content_1fr_min-content_min-content_min-content]'>
|
|
322
323
|
{fields?.map((field) => {
|
|
323
324
|
const hidden = field.visible === false;
|
|
@@ -329,7 +330,6 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
329
330
|
aria-expanded={expandedField === field.id}
|
|
330
331
|
>
|
|
331
332
|
<div
|
|
332
|
-
role='none'
|
|
333
333
|
className={mx(
|
|
334
334
|
subtleHover,
|
|
335
335
|
'grid grid-cols-subgrid col-span-5',
|
|
@@ -341,7 +341,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
341
341
|
{field.path}
|
|
342
342
|
</List.ItemTitle>
|
|
343
343
|
<List.ItemIconButton
|
|
344
|
-
label={t(hidden ? 'show
|
|
344
|
+
label={t(hidden ? 'show-field.label' : 'hide-field.label')}
|
|
345
345
|
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
346
346
|
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
347
347
|
autoHide={false}
|
|
@@ -351,7 +351,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
351
351
|
{!readonly && (
|
|
352
352
|
<>
|
|
353
353
|
<List.ItemDeleteButton
|
|
354
|
-
label={t('delete
|
|
354
|
+
label={t('delete-field.label')}
|
|
355
355
|
autoHide={false}
|
|
356
356
|
disabled={readonly || schemaReadonly || viewSnapshot.projection.fields.length <= 1}
|
|
357
357
|
onClick={() => handleDelete(field.id)}
|
|
@@ -360,7 +360,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
360
360
|
<List.ItemIconButton
|
|
361
361
|
iconOnly
|
|
362
362
|
variant='ghost'
|
|
363
|
-
label={t('toggle
|
|
363
|
+
label={t('toggle-expand.label', { ns: osTranslations })}
|
|
364
364
|
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
365
365
|
onClick={() => handleToggleField(field)}
|
|
366
366
|
data-testid='field.toggle'
|
|
@@ -369,7 +369,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
369
369
|
)}
|
|
370
370
|
</div>
|
|
371
371
|
{expandedField === field.id && !readonly && (
|
|
372
|
-
<div
|
|
372
|
+
<div className='col-span-full mt-1 mb-1 border border-separator rounded-md'>
|
|
373
373
|
<FieldEditor
|
|
374
374
|
readonly={readonly || schemaReadonly}
|
|
375
375
|
registry={registry}
|
|
@@ -384,10 +384,10 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
384
384
|
})}
|
|
385
385
|
</div>
|
|
386
386
|
{!readonly && !expandedField && (
|
|
387
|
-
<div
|
|
387
|
+
<div className='my-form-padding'>
|
|
388
388
|
<IconButton
|
|
389
389
|
icon='ph--plus--regular'
|
|
390
|
-
label={t('add
|
|
390
|
+
label={t('add-property-button.label')}
|
|
391
391
|
onClick={handleAdd}
|
|
392
392
|
disabled={viewSnapshot.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
393
393
|
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
package/src/util/refs.ts
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
import type * as SchemaAST from 'effect/SchemaAST';
|
|
6
6
|
|
|
7
7
|
import { Ref } from '@dxos/echo';
|
|
8
|
-
import {
|
|
8
|
+
import { type ReferenceAnnotationValue, ReferenceAnnotationId } from '@dxos/echo/internal';
|
|
9
|
+
import { findAnnotation, getArrayElementType, isArrayType } from '@dxos/effect';
|
|
9
10
|
|
|
10
11
|
type RefProps = {
|
|
11
12
|
ast: SchemaAST.AST;
|
|
12
13
|
isArray: boolean;
|
|
14
|
+
typename?: string;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export const getRefProps = (ast: SchemaAST.AST): RefProps | undefined => {
|
|
@@ -18,14 +20,16 @@ export const getRefProps = (ast: SchemaAST.AST): RefProps | undefined => {
|
|
|
18
20
|
const elementType = getArrayElementType(ast);
|
|
19
21
|
if (elementType) {
|
|
20
22
|
if (Ref.isRefType(elementType)) {
|
|
21
|
-
|
|
23
|
+
const typename = findAnnotation<ReferenceAnnotationValue>(elementType, ReferenceAnnotationId)?.typename;
|
|
24
|
+
return { ast: elementType, isArray: true, typename };
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
// Direct reference.
|
|
27
30
|
if (Ref.isRefType(ast)) {
|
|
28
|
-
|
|
31
|
+
const typename = findAnnotation<ReferenceAnnotationValue>(ast, ReferenceAnnotationId)?.typename;
|
|
32
|
+
return { ast, isArray: false, typename };
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
return undefined;
|