@dxos/react-ui-form 0.8.2-main.f081794 → 0.8.2-main.fbd8ed0
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 +827 -537
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +675 -395
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +827 -537
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +6 -4
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +37 -34
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +14 -11
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContext.d.ts +2 -2
- package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/RefField.d.ts +12 -0
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/factory.d.ts +1 -1
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/Test.stories.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.map +1 -1
- package/dist/types/src/components/deprecated/Form.d.ts +2 -2
- package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
- package/dist/types/src/components/deprecated/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useForm.d.ts +2 -2
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +23 -0
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -0
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +8 -0
- package/dist/types/src/hooks/useRefQueryLookupHandler.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 +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/refs.d.ts +9 -0
- package/dist/types/src/util/refs.d.ts.map +1 -0
- package/dist/types/src/util/schema.d.ts +3 -0
- package/dist/types/src/util/schema.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -25
- package/src/components/ControlSection/ControlSection.stories.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
- package/src/components/Form/ArrayField.tsx +13 -17
- package/src/components/Form/Defaults.tsx +4 -1
- package/src/components/Form/Form.stories.tsx +92 -43
- package/src/components/Form/Form.tsx +7 -3
- package/src/components/Form/FormContent.tsx +90 -19
- package/src/components/Form/FormContext.tsx +13 -3
- package/src/components/Form/Input.tsx +3 -9
- package/src/components/Form/RefField.tsx +197 -0
- package/src/components/Form/custom/GeoPointInput.tsx +2 -2
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +6 -5
- package/src/components/Form/custom/SelectOptionsInput.tsx +2 -2
- package/src/components/Form/factory.tsx +1 -1
- package/src/components/Form/index.ts +0 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +5 -4
- package/src/components/ViewEditor/ViewEditor.tsx +23 -20
- package/src/components/deprecated/Form.tsx +6 -6
- package/src/hooks/index.ts +3 -1
- package/src/hooks/useForm.ts +4 -3
- package/src/hooks/useQueryRefOptions.ts +53 -0
- package/src/hooks/useRefQueryLookupHandler.ts +41 -0
- package/src/util/formatting.ts +15 -3
- package/src/util/index.ts +3 -1
- package/src/util/refs.ts +38 -0
- package/src/util/schema.ts +14 -0
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Schema, SchemaAST } from 'effect';
|
|
5
6
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
6
7
|
|
|
7
8
|
import { type SchemaRegistry } from '@dxos/echo-db';
|
|
8
|
-
import {
|
|
9
|
+
import { Format, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo-schema';
|
|
9
10
|
import { invariant } from '@dxos/invariant';
|
|
10
11
|
import { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
11
12
|
import { List } from '@dxos/react-ui-list';
|
|
@@ -18,19 +19,19 @@ import { Form } from '../Form';
|
|
|
18
19
|
|
|
19
20
|
const grid = 'grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]';
|
|
20
21
|
|
|
21
|
-
const ViewMetaSchema =
|
|
22
|
-
name:
|
|
23
|
-
[
|
|
22
|
+
const ViewMetaSchema = Schema.Struct({
|
|
23
|
+
name: Schema.String.annotations({
|
|
24
|
+
[SchemaAST.TitleAnnotationId]: 'View',
|
|
24
25
|
}),
|
|
25
26
|
typename: Format.URL.annotations({
|
|
26
|
-
[
|
|
27
|
+
[SchemaAST.TitleAnnotationId]: 'Typename',
|
|
27
28
|
}),
|
|
28
|
-
}).pipe(
|
|
29
|
+
}).pipe(Schema.mutable);
|
|
29
30
|
|
|
30
|
-
type ViewMetaType =
|
|
31
|
+
type ViewMetaType = Schema.Schema.Type<typeof ViewMetaSchema>;
|
|
31
32
|
|
|
32
33
|
export type ViewEditorProps = ThemedClassName<{
|
|
33
|
-
schema:
|
|
34
|
+
schema: Schema.Schema.AnyNoContext;
|
|
34
35
|
view: ViewType;
|
|
35
36
|
registry?: SchemaRegistry;
|
|
36
37
|
readonly?: boolean;
|
|
@@ -53,8 +54,9 @@ export const ViewEditor = ({
|
|
|
53
54
|
onDelete,
|
|
54
55
|
}: ViewEditorProps) => {
|
|
55
56
|
const { t } = useTranslation(translationKey);
|
|
56
|
-
const projection = useMemo(() => new ViewProjection(schema
|
|
57
|
+
const projection = useMemo(() => new ViewProjection(toJsonSchema(schema), view), [schema, view]);
|
|
57
58
|
const [field, setField] = useState<FieldType>();
|
|
59
|
+
const immutable = readonly || !isMutable(schema);
|
|
58
60
|
|
|
59
61
|
// TODO(burdon): Should be reactive.
|
|
60
62
|
const viewValues = useMemo(() => {
|
|
@@ -73,14 +75,14 @@ export const ViewEditor = ({
|
|
|
73
75
|
// TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
|
|
74
76
|
|
|
75
77
|
const handleAdd = useCallback(() => {
|
|
76
|
-
invariant(!
|
|
78
|
+
invariant(!immutable);
|
|
77
79
|
const field = projection.createFieldProjection();
|
|
78
80
|
setField(field);
|
|
79
81
|
}, [schema, view]);
|
|
80
82
|
|
|
81
83
|
const handleUpdate = useCallback(
|
|
82
84
|
({ name, typename }: ViewMetaType) => {
|
|
83
|
-
invariant(!
|
|
85
|
+
invariant(!immutable);
|
|
84
86
|
requestAnimationFrame(() => {
|
|
85
87
|
if (view.name !== name) {
|
|
86
88
|
view.name = name;
|
|
@@ -96,7 +98,7 @@ export const ViewEditor = ({
|
|
|
96
98
|
|
|
97
99
|
const handleDelete = useCallback(
|
|
98
100
|
(fieldId: string) => {
|
|
99
|
-
invariant(!
|
|
101
|
+
invariant(!immutable);
|
|
100
102
|
if (fieldId === field?.id) {
|
|
101
103
|
setField(undefined);
|
|
102
104
|
}
|
|
@@ -108,7 +110,7 @@ export const ViewEditor = ({
|
|
|
108
110
|
|
|
109
111
|
const handleMove = useCallback(
|
|
110
112
|
(fromIndex: number, toIndex: number) => {
|
|
111
|
-
invariant(!
|
|
113
|
+
invariant(!immutable);
|
|
112
114
|
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
113
115
|
const fields = [...view.fields];
|
|
114
116
|
const [moved] = fields.splice(fromIndex, 1);
|
|
@@ -139,15 +141,16 @@ export const ViewEditor = ({
|
|
|
139
141
|
);
|
|
140
142
|
|
|
141
143
|
return (
|
|
142
|
-
<div role='none' className={mx('
|
|
144
|
+
<div role='none' className={mx('grid grid-rows-[1fr_1fr] min-bs-0', classNames)}>
|
|
143
145
|
<Form<ViewMetaType>
|
|
144
146
|
autoSave
|
|
145
147
|
schema={ViewMetaSchema}
|
|
146
148
|
values={viewValues}
|
|
147
|
-
onSave={
|
|
149
|
+
onSave={immutable ? undefined : handleUpdate}
|
|
150
|
+
classNames='min-bs-0 overflow-y-auto'
|
|
148
151
|
/>
|
|
149
152
|
|
|
150
|
-
<div>
|
|
153
|
+
<div role='none' className='border-bs border-separator min-bs-0 overflow-y-auto'>
|
|
151
154
|
{/* TODO(burdon): Clean up common form ux. */}
|
|
152
155
|
<div role='none' className='p-2'>
|
|
153
156
|
<label className={mx(inputTextLabel)}>{t('fields label')}</label>
|
|
@@ -155,9 +158,9 @@ export const ViewEditor = ({
|
|
|
155
158
|
|
|
156
159
|
<List.Root<FieldType>
|
|
157
160
|
items={view.fields}
|
|
158
|
-
isItem={
|
|
161
|
+
isItem={Schema.is(FieldSchema)}
|
|
159
162
|
getId={(field) => field.id}
|
|
160
|
-
onMove={
|
|
163
|
+
onMove={immutable ? undefined : handleMove}
|
|
161
164
|
>
|
|
162
165
|
{({ items: fields }) => (
|
|
163
166
|
<>
|
|
@@ -179,7 +182,7 @@ export const ViewEditor = ({
|
|
|
179
182
|
disabled={view.fields.length <= 1}
|
|
180
183
|
onClick={() => handleHide(field.id)}
|
|
181
184
|
/>
|
|
182
|
-
{!
|
|
185
|
+
{!immutable && (
|
|
183
186
|
<List.ItemDeleteButton
|
|
184
187
|
icon='ph--trash--regular'
|
|
185
188
|
disabled={view.fields.length <= 1}
|
|
@@ -241,7 +244,7 @@ export const ViewEditor = ({
|
|
|
241
244
|
<IconButton
|
|
242
245
|
icon='ph--plus--regular'
|
|
243
246
|
label={t('button add property')}
|
|
244
|
-
onClick={
|
|
247
|
+
onClick={immutable ? undefined : handleAdd}
|
|
245
248
|
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
246
249
|
disabled={view.fields.length >= VIEW_FIELD_LIMIT}
|
|
247
250
|
/>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { SchemaAST
|
|
5
|
+
import { SchemaAST, type Schema, Option, pipe } from 'effect';
|
|
6
6
|
import React, { useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { findProperty } from '@dxos/effect';
|
|
@@ -13,7 +13,7 @@ import { type ViewType, getFieldValue, setFieldValue } from '@dxos/schema';
|
|
|
13
13
|
export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
|
|
14
14
|
view: ViewType;
|
|
15
15
|
object: T;
|
|
16
|
-
schema?:
|
|
16
|
+
schema?: Schema.Schema<T>;
|
|
17
17
|
readonly?: boolean;
|
|
18
18
|
}>;
|
|
19
19
|
|
|
@@ -31,16 +31,16 @@ export const DeprecatedForm = <T extends {} = {}>({
|
|
|
31
31
|
<div role='none' className={mx('flex flex-col w-full gap-2 p-2', classNames)}>
|
|
32
32
|
{view.fields.map((field) => {
|
|
33
33
|
const prop = schema && findProperty(schema, field.path);
|
|
34
|
-
const title = (prop && pipe(
|
|
35
|
-
const description = (prop && pipe(
|
|
34
|
+
const title = (prop && pipe(SchemaAST.getTitleAnnotation(prop), Option.getOrUndefined)) ?? '';
|
|
35
|
+
const description = (prop && pipe(SchemaAST.getDescriptionAnnotation(prop), Option.getOrUndefined)) ?? title;
|
|
36
36
|
// const format =
|
|
37
|
-
// (prop && (getPatternAnnotation(prop) ?? (
|
|
37
|
+
// (prop && (getPatternAnnotation(prop) ?? (SchemaAST.isNumberKeyword(prop) && RealNumberFormat))) || undefined;
|
|
38
38
|
|
|
39
39
|
//
|
|
40
40
|
// Boolean
|
|
41
41
|
//
|
|
42
42
|
|
|
43
|
-
if (prop &&
|
|
43
|
+
if (prop && SchemaAST.isBooleanKeyword(prop)) {
|
|
44
44
|
const value = object && getFieldValue(object, field);
|
|
45
45
|
const onChange = (checked: boolean) => {
|
|
46
46
|
setFieldValue(object, field, checked);
|
package/src/hooks/index.ts
CHANGED
package/src/hooks/useForm.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { SchemaAST
|
|
5
|
+
import { SchemaAST, type Schema } from 'effect';
|
|
6
6
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type BaseObject, getValue, setValue } from '@dxos/echo-schema';
|
|
@@ -45,7 +45,7 @@ export interface FormOptions<T extends BaseObject> {
|
|
|
45
45
|
/**
|
|
46
46
|
* Effect schema (Type literal).
|
|
47
47
|
*/
|
|
48
|
-
schema:
|
|
48
|
+
schema: Schema.Schema<T, any>;
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Initial values (which may not pass validation).
|
|
@@ -90,9 +90,10 @@ export const useForm = <T extends BaseObject>({
|
|
|
90
90
|
onValid,
|
|
91
91
|
onSave,
|
|
92
92
|
}: FormOptions<T>): FormHandler<T> => {
|
|
93
|
-
invariant(
|
|
93
|
+
invariant(SchemaAST.isTypeLiteral(schema.ast));
|
|
94
94
|
|
|
95
95
|
const [values, setValues] = useState<Partial<T>>(initialValues);
|
|
96
|
+
|
|
96
97
|
useEffect(() => {
|
|
97
98
|
setValues(initialValues);
|
|
98
99
|
}, [initialValues]);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type TypeAnnotation } from '@dxos/echo-schema';
|
|
8
|
+
import { type DXN } from '@dxos/keys';
|
|
9
|
+
import { log } from '@dxos/log';
|
|
10
|
+
import { type TagPickerItemData } from '@dxos/react-ui-tag-picker';
|
|
11
|
+
import { type MaybePromise } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
export type RefOption = { dxn: DXN; label?: string };
|
|
14
|
+
export type QueryRefOptions = (type: TypeAnnotation) => MaybePromise<RefOption[]>;
|
|
15
|
+
|
|
16
|
+
type UseQueryRefOptionsProps = { refTypeInfo: TypeAnnotation | undefined; onQueryRefOptions?: QueryRefOptions };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hook to query reference options based on type information.
|
|
20
|
+
* Used internally within forms to fetch and format reference options for reference fields.
|
|
21
|
+
*/
|
|
22
|
+
export const useQueryRefOptions = ({ refTypeInfo, onQueryRefOptions }: UseQueryRefOptionsProps) => {
|
|
23
|
+
const [options, setOptions] = useState<TagPickerItemData[]>([]);
|
|
24
|
+
const [loading, setLoading] = useState(false);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!refTypeInfo || !onQueryRefOptions) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fetchOptions = async () => {
|
|
32
|
+
setLoading(true);
|
|
33
|
+
try {
|
|
34
|
+
const fetchedOptions = await onQueryRefOptions(refTypeInfo);
|
|
35
|
+
setOptions(
|
|
36
|
+
fetchedOptions.map((option) => {
|
|
37
|
+
const dxn = option.dxn.toString() as string;
|
|
38
|
+
return { id: dxn, label: option.label ?? dxn, hue: 'neutral' as any };
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
log.error('Failed to fetch ref options:', error);
|
|
43
|
+
setOptions([]);
|
|
44
|
+
} finally {
|
|
45
|
+
setLoading(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
void fetchOptions();
|
|
50
|
+
}, [refTypeInfo, onQueryRefOptions]);
|
|
51
|
+
|
|
52
|
+
return { options, loading };
|
|
53
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { getDXN, type TypeAnnotation } from '@dxos/echo-schema';
|
|
8
|
+
import { Filter, type Space } from '@dxos/react-client/echo';
|
|
9
|
+
import { isNonNullable } from '@dxos/util';
|
|
10
|
+
|
|
11
|
+
import { type QueryRefOptions } from './useQueryRefOptions';
|
|
12
|
+
|
|
13
|
+
type UseRefQueryLookupProps = { space?: Space };
|
|
14
|
+
|
|
15
|
+
export const useRefQueryLookupHandler = ({ space }: UseRefQueryLookupProps): QueryRefOptions => {
|
|
16
|
+
const handleRefQueryLookup = useCallback(
|
|
17
|
+
async (typeInfo: TypeAnnotation) => {
|
|
18
|
+
if (!space) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const query = space.db.query(Filter.typename(typeInfo.typename));
|
|
22
|
+
const { objects } = await query.run();
|
|
23
|
+
|
|
24
|
+
return objects
|
|
25
|
+
.map((object) => {
|
|
26
|
+
const dxn = getDXN(object);
|
|
27
|
+
if (!dxn) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// TODO(Zaymon): Better fallback object names?
|
|
32
|
+
const item = { dxn, label: object?.name ?? object?.id ?? '' };
|
|
33
|
+
return item;
|
|
34
|
+
})
|
|
35
|
+
.filter(isNonNullable);
|
|
36
|
+
},
|
|
37
|
+
[space],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return handleRefQueryLookup;
|
|
41
|
+
};
|
package/src/util/formatting.ts
CHANGED
|
@@ -64,13 +64,25 @@ export const formatForDisplay = ({ type, format, value, locale = undefined }: Va
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
case FormatEnum.Date: {
|
|
67
|
-
|
|
67
|
+
try {
|
|
68
|
+
return formatDate(new Date(value as number), 'yyyy-MM-dd');
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return 'Invalid Date';
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
73
|
case FormatEnum.Time: {
|
|
70
|
-
|
|
74
|
+
try {
|
|
75
|
+
return formatDate(new Date(value as number), 'HH:mm:ss');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return 'Invalid Time';
|
|
78
|
+
}
|
|
71
79
|
}
|
|
72
80
|
case FormatEnum.DateTime: {
|
|
73
|
-
|
|
81
|
+
try {
|
|
82
|
+
return formatDate(new Date(value as number), 'yyyy-MM-dd HH:mm:ss');
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return 'Invalid DateTime';
|
|
85
|
+
}
|
|
74
86
|
}
|
|
75
87
|
case FormatEnum.GeoPoint: {
|
|
76
88
|
if (value === null || value === undefined) {
|
package/src/util/index.ts
CHANGED
package/src/util/refs.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type SchemaAST } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { FormatEnum, ReferenceAnnotationId } from '@dxos/echo-schema';
|
|
8
|
+
import { findAnnotation } from '@dxos/effect';
|
|
9
|
+
import { type SchemaProperty } from '@dxos/schema';
|
|
10
|
+
|
|
11
|
+
import { findArrayElementType } from './schema';
|
|
12
|
+
|
|
13
|
+
type RefProps = {
|
|
14
|
+
ast: SchemaAST.AST;
|
|
15
|
+
isArray: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getRefProps = (property: SchemaProperty<any>): RefProps | undefined => {
|
|
19
|
+
const { ast, format, array } = property;
|
|
20
|
+
|
|
21
|
+
// Direct reference.
|
|
22
|
+
if (format === FormatEnum.Ref) {
|
|
23
|
+
return { ast, isArray: false };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Array of references.
|
|
27
|
+
if (array) {
|
|
28
|
+
const elementType = findArrayElementType(ast);
|
|
29
|
+
if (elementType) {
|
|
30
|
+
const annotation = findAnnotation(elementType, ReferenceAnnotationId);
|
|
31
|
+
if (annotation) {
|
|
32
|
+
return { ast: elementType, isArray: true };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return undefined;
|
|
38
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { SchemaAST } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { findNode } from '@dxos/effect';
|
|
8
|
+
|
|
9
|
+
export const findArrayElementType = (ast: SchemaAST.AST) => {
|
|
10
|
+
const tupleType = findNode(ast, SchemaAST.isTupleType);
|
|
11
|
+
const elementType = (tupleType as SchemaAST.TupleType | undefined)?.rest[0]?.type;
|
|
12
|
+
|
|
13
|
+
return elementType;
|
|
14
|
+
};
|