@dxos/react-ui-form 0.8.2-main.f081794 → 0.8.2-main.f11618f
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 +205 -73
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +259 -129
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +205 -73
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +3 -1
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +4 -2
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +13 -10
- 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/RefField.d.ts +23 -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/util/formatting.d.ts.map +1 -1
- package/package.json +24 -24
- package/src/components/Form/ArrayField.tsx +3 -1
- package/src/components/Form/Form.stories.tsx +48 -5
- package/src/components/Form/Form.tsx +4 -0
- package/src/components/Form/FormContent.tsx +59 -15
- package/src/components/Form/FormContext.tsx +10 -2
- package/src/components/Form/RefField.tsx +151 -0
- package/src/components/Form/custom/GeoPointInput.tsx +2 -2
- package/src/components/Form/custom/SelectOptionsInput.tsx +2 -2
- package/src/util/formatting.ts +15 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { pipe } from 'effect';
|
|
@@ -17,29 +17,29 @@ import { SelectInput } from './Defaults';
|
|
|
17
17
|
import { type ComponentLookup } from './Form';
|
|
18
18
|
import { useInputProps, useFormValues } from './FormContext';
|
|
19
19
|
import { type InputComponent } from './Input';
|
|
20
|
+
import { RefField, type QueryRefOptions } from './RefField';
|
|
20
21
|
import { getInputComponent } from './factory';
|
|
21
22
|
|
|
22
|
-
export type FormContentProps = {
|
|
23
|
-
schema: S.Schema.All;
|
|
24
|
-
path?: (string | number)[];
|
|
25
|
-
filter?: (props: SchemaProperty<any>[]) => SchemaProperty<any>[];
|
|
26
|
-
sort?: string[];
|
|
27
|
-
readonly?: boolean;
|
|
28
|
-
lookupComponent?: ComponentLookup;
|
|
29
|
-
Custom?: Partial<Record<string, InputComponent>>;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
23
|
export type FormFieldProps = {
|
|
33
24
|
property: SchemaProperty<any>;
|
|
34
25
|
path?: (string | number)[];
|
|
35
26
|
readonly?: boolean;
|
|
36
27
|
/** Used to indicate if input should be presented inline (e.g. for array items). */
|
|
37
28
|
inline?: boolean;
|
|
29
|
+
onQueryRefOptions?: QueryRefOptions;
|
|
38
30
|
lookupComponent?: ComponentLookup;
|
|
39
31
|
Custom?: Partial<Record<string, InputComponent>>;
|
|
40
32
|
};
|
|
41
33
|
|
|
42
|
-
export const FormField = ({
|
|
34
|
+
export const FormField = ({
|
|
35
|
+
property,
|
|
36
|
+
path,
|
|
37
|
+
readonly,
|
|
38
|
+
inline,
|
|
39
|
+
onQueryRefOptions,
|
|
40
|
+
lookupComponent,
|
|
41
|
+
Custom,
|
|
42
|
+
}: FormFieldProps) => {
|
|
43
43
|
const inputProps = useInputProps(path);
|
|
44
44
|
const { ast, name, type, format, title, description, options, examples, array } = property;
|
|
45
45
|
|
|
@@ -120,6 +120,22 @@ export const FormField = ({ property, path, readonly, inline, lookupComponent, C
|
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// TODO(ZaymonFC): Extract this to it's own component.
|
|
124
|
+
if (format === 'ref') {
|
|
125
|
+
return (
|
|
126
|
+
<RefField
|
|
127
|
+
ast={ast}
|
|
128
|
+
type={type}
|
|
129
|
+
label={label}
|
|
130
|
+
readonly={readonly}
|
|
131
|
+
placeholder={placeholder}
|
|
132
|
+
inline={inline}
|
|
133
|
+
onQueryRefOptions={onQueryRefOptions}
|
|
134
|
+
inputProps={inputProps}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
123
139
|
if (type === 'object') {
|
|
124
140
|
const baseNode = findNode(ast, isDiscriminatedUnion);
|
|
125
141
|
const typeLiteral = baseNode
|
|
@@ -129,8 +145,15 @@ export const FormField = ({ property, path, readonly, inline, lookupComponent, C
|
|
|
129
145
|
if (typeLiteral) {
|
|
130
146
|
return (
|
|
131
147
|
<div role='none'>
|
|
132
|
-
{!inline && <
|
|
133
|
-
<FormFields
|
|
148
|
+
{!inline && <h3 className='text-lg mbs-2 mbe-1'>{label}</h3>}
|
|
149
|
+
<FormFields
|
|
150
|
+
schema={S.make(typeLiteral)}
|
|
151
|
+
path={path}
|
|
152
|
+
readonly={readonly}
|
|
153
|
+
onQueryRefOptions={onQueryRefOptions}
|
|
154
|
+
Custom={Custom}
|
|
155
|
+
lookupComponent={lookupComponent}
|
|
156
|
+
/>
|
|
134
157
|
</div>
|
|
135
158
|
);
|
|
136
159
|
}
|
|
@@ -139,7 +162,27 @@ export const FormField = ({ property, path, readonly, inline, lookupComponent, C
|
|
|
139
162
|
return null;
|
|
140
163
|
};
|
|
141
164
|
|
|
142
|
-
export
|
|
165
|
+
export type FormContentProps = {
|
|
166
|
+
schema: S.Schema.All;
|
|
167
|
+
path?: (string | number)[];
|
|
168
|
+
filter?: (props: SchemaProperty<any>[]) => SchemaProperty<any>[];
|
|
169
|
+
sort?: string[];
|
|
170
|
+
readonly?: boolean;
|
|
171
|
+
onQueryRefOptions?: QueryRefOptions;
|
|
172
|
+
lookupComponent?: ComponentLookup;
|
|
173
|
+
Custom?: Partial<Record<string, InputComponent>>;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const FormFields = ({
|
|
177
|
+
schema,
|
|
178
|
+
path,
|
|
179
|
+
filter,
|
|
180
|
+
sort,
|
|
181
|
+
readonly,
|
|
182
|
+
onQueryRefOptions,
|
|
183
|
+
lookupComponent,
|
|
184
|
+
Custom,
|
|
185
|
+
}: FormContentProps) => {
|
|
143
186
|
const values = useFormValues(path);
|
|
144
187
|
|
|
145
188
|
const properties = useMemo(() => {
|
|
@@ -159,6 +202,7 @@ export const FormFields = ({ schema, path, filter, sort, readonly, lookupCompone
|
|
|
159
202
|
property={property}
|
|
160
203
|
path={[...(path ?? []), property.name]}
|
|
161
204
|
readonly={readonly}
|
|
205
|
+
onQueryRefOptions={onQueryRefOptions}
|
|
162
206
|
lookupComponent={lookupComponent}
|
|
163
207
|
Custom={Custom}
|
|
164
208
|
/>
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, {
|
|
6
|
+
createContext,
|
|
7
|
+
type RefObject,
|
|
8
|
+
useContext,
|
|
9
|
+
useEffect,
|
|
10
|
+
useMemo,
|
|
11
|
+
type FocusEvent,
|
|
12
|
+
type PropsWithChildren,
|
|
13
|
+
} from 'react';
|
|
6
14
|
|
|
7
15
|
import { raise } from '@dxos/debug';
|
|
8
16
|
import { type BaseObject, getValue } from '@dxos/echo-schema';
|
|
@@ -53,7 +61,7 @@ export const FormProvider = ({
|
|
|
53
61
|
...formOptions
|
|
54
62
|
}: PropsWithChildren<
|
|
55
63
|
FormOptions<any> & {
|
|
56
|
-
formRef?:
|
|
64
|
+
formRef?: RefObject<HTMLDivElement>;
|
|
57
65
|
autoSave?: boolean;
|
|
58
66
|
}
|
|
59
67
|
>) => {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
type AST,
|
|
9
|
+
Expando,
|
|
10
|
+
getTypeAnnotation,
|
|
11
|
+
ReferenceAnnotationId,
|
|
12
|
+
type ReferenceAnnotationValue,
|
|
13
|
+
type TypeAnnotation,
|
|
14
|
+
} from '@dxos/echo-schema';
|
|
15
|
+
import { findAnnotation, type SimpleType } from '@dxos/effect';
|
|
16
|
+
import { DXN } from '@dxos/keys';
|
|
17
|
+
import { refFromDXN, RefImpl } from '@dxos/live-object';
|
|
18
|
+
import { log } from '@dxos/log';
|
|
19
|
+
import { type MaybePromise } from '@dxos/util';
|
|
20
|
+
|
|
21
|
+
import { SelectInput, TextInput } from './Defaults';
|
|
22
|
+
import { type FormInputStateProps } from './FormContext';
|
|
23
|
+
|
|
24
|
+
export type QueryRefOptions = (type: TypeAnnotation) => MaybePromise<{ dxn: DXN; label?: string }[]>;
|
|
25
|
+
|
|
26
|
+
type RefFieldProps = {
|
|
27
|
+
ast: AST.AST;
|
|
28
|
+
type: SimpleType;
|
|
29
|
+
label: string;
|
|
30
|
+
readonly?: boolean;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
inline?: boolean;
|
|
33
|
+
onQueryRefOptions?: QueryRefOptions;
|
|
34
|
+
inputProps: FormInputStateProps;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const RefField = ({
|
|
38
|
+
ast,
|
|
39
|
+
type,
|
|
40
|
+
label,
|
|
41
|
+
readonly,
|
|
42
|
+
placeholder,
|
|
43
|
+
inline,
|
|
44
|
+
onQueryRefOptions,
|
|
45
|
+
inputProps,
|
|
46
|
+
}: RefFieldProps) => {
|
|
47
|
+
const { getValue, onValueChange, ...restInputProps } = inputProps;
|
|
48
|
+
const refTypeInfo = findAnnotation<ReferenceAnnotationValue>(ast, ReferenceAnnotationId);
|
|
49
|
+
const [refOptions, setRefOptions] = useState<Array<{ value: string; label?: string }>>([]);
|
|
50
|
+
const [loading, setLoading] = useState(false);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!refTypeInfo || !onQueryRefOptions) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const fetchOptions = async () => {
|
|
58
|
+
setLoading(true);
|
|
59
|
+
try {
|
|
60
|
+
const options = await onQueryRefOptions(refTypeInfo);
|
|
61
|
+
setRefOptions(
|
|
62
|
+
options.map((option) => ({
|
|
63
|
+
label: option.label,
|
|
64
|
+
value: option.dxn.toString(),
|
|
65
|
+
})),
|
|
66
|
+
);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
log.error('Failed to fetch ref options:', error);
|
|
69
|
+
setRefOptions([]);
|
|
70
|
+
} finally {
|
|
71
|
+
setLoading(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
void fetchOptions();
|
|
76
|
+
}, [refTypeInfo, onQueryRefOptions]);
|
|
77
|
+
|
|
78
|
+
if (!refTypeInfo) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// If ref type is expando, fall back to taking a DXN in string format.
|
|
83
|
+
if (refTypeInfo.typename === getTypeAnnotation(Expando)?.typename || !onQueryRefOptions) {
|
|
84
|
+
const handleOnValueChange = (_type: any, dxnString: string) => {
|
|
85
|
+
const dxn = DXN.tryParse(dxnString);
|
|
86
|
+
if (dxn) {
|
|
87
|
+
onValueChange?.('object', refFromDXN(dxn));
|
|
88
|
+
} else if (dxnString === '') {
|
|
89
|
+
onValueChange?.('object', undefined);
|
|
90
|
+
} else {
|
|
91
|
+
onValueChange?.('string', dxnString);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleGetValue = () => {
|
|
96
|
+
const formValue = getValue();
|
|
97
|
+
if (typeof formValue === 'string') {
|
|
98
|
+
return formValue;
|
|
99
|
+
}
|
|
100
|
+
if (formValue instanceof RefImpl) {
|
|
101
|
+
return formValue.dxn.toString();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return undefined;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<TextInput
|
|
109
|
+
type={type}
|
|
110
|
+
label={label}
|
|
111
|
+
disabled={readonly}
|
|
112
|
+
placeholder={placeholder}
|
|
113
|
+
inputOnly={inline}
|
|
114
|
+
getValue={handleGetValue as <V>() => V | undefined}
|
|
115
|
+
onValueChange={handleOnValueChange}
|
|
116
|
+
{...restInputProps}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const handleGetValue = () => {
|
|
122
|
+
const formValue = getValue();
|
|
123
|
+
|
|
124
|
+
if (formValue instanceof RefImpl) {
|
|
125
|
+
return formValue.dxn.toString();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return undefined;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const handleValueChanged = (_type: any, dxnString: string) => {
|
|
132
|
+
const dxn = DXN.parse(dxnString);
|
|
133
|
+
const ref = refFromDXN(dxn);
|
|
134
|
+
|
|
135
|
+
onValueChange('object', ref);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<SelectInput
|
|
140
|
+
type={type}
|
|
141
|
+
label={label}
|
|
142
|
+
disabled={readonly || loading}
|
|
143
|
+
placeholder={loading ? 'Loading options...' : placeholder}
|
|
144
|
+
inputOnly={inline}
|
|
145
|
+
getValue={handleGetValue as <V>() => V | undefined}
|
|
146
|
+
onValueChange={handleValueChanged}
|
|
147
|
+
options={refOptions}
|
|
148
|
+
{...restInputProps}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
|
5
|
+
import React, { useCallback, useState, useEffect, useMemo, type ChangeEvent } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type GeoPoint, GeoLocation } from '@dxos/echo-schema';
|
|
8
8
|
import { Input, useTranslation } from '@dxos/react-ui';
|
|
@@ -28,7 +28,7 @@ export const GeoPointInput = ({ type, label, disabled, getStatus, getValue, onVa
|
|
|
28
28
|
|
|
29
29
|
const handleCoordinateChange = useCallback(
|
|
30
30
|
(coordinateType: keyof Pick<GeoLocation, 'longitude' | 'latitude'>, setText: (text: string) => void) =>
|
|
31
|
-
(event:
|
|
31
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
32
32
|
const inputText = event.target.value;
|
|
33
33
|
setText(inputText);
|
|
34
34
|
if (inputText !== '' && inputText !== '-') {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { useCallback, useEffect, useState,
|
|
5
|
+
import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect, useState, useRef } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type SelectOption } from '@dxos/echo-schema';
|
|
8
8
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -89,7 +89,7 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
89
89
|
[options, type, onValueChange],
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
const handleKeyDown = useCallback((e:
|
|
92
|
+
const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
|
|
93
93
|
if (e.key === 'Enter') {
|
|
94
94
|
setSelectedId(null);
|
|
95
95
|
}
|
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) {
|