@dxos/react-ui-form 0.8.2-main.f11618f → 0.8.2-staging.42af850
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 +1732 -1374
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +1715 -1365
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +1732 -1374
- 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 +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/FieldEditor/FieldEditor.test.d.ts +2 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts +4 -4
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +4 -4
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +34 -34
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormActions.d.ts +1 -1
- package/dist/types/src/components/Form/FormActions.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +9 -8
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- 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 +7 -18
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
- 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 +5 -3
- 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/translations.d.ts +5 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- 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/src/vitest-setup.d.ts +2 -0
- package/dist/types/src/vitest-setup.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +28 -25
- package/src/components/ControlSection/ControlSection.stories.tsx +1 -1
- package/src/components/ControlSection/ControlSection.tsx +2 -2
- package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.test.tsx +49 -0
- package/src/components/FieldEditor/FieldEditor.tsx +3 -2
- package/src/components/Form/ArrayField.tsx +60 -49
- package/src/components/Form/Defaults.tsx +77 -48
- package/src/components/Form/Form.stories.tsx +104 -122
- package/src/components/Form/Form.tsx +5 -6
- package/src/components/Form/FormActions.tsx +15 -18
- package/src/components/Form/FormContent.tsx +80 -54
- package/src/components/Form/FormContext.tsx +14 -8
- package/src/components/Form/Input.tsx +4 -10
- package/src/components/Form/RefField.tsx +167 -101
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +6 -5
- package/src/components/Form/factory.tsx +1 -1
- package/src/components/Form/index.ts +0 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +19 -9
- package/src/components/ViewEditor/ViewEditor.tsx +65 -35
- package/src/components/deprecated/Form.tsx +6 -6
- package/src/components/testing.tsx +5 -9
- 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 +39 -0
- package/src/translations.ts +9 -2
- package/src/util/index.ts +3 -1
- package/src/util/refs.ts +38 -0
- package/src/util/schema.ts +14 -0
- package/src/vitest-setup.ts +11 -0
|
@@ -10,6 +10,7 @@ import React, {
|
|
|
10
10
|
useMemo,
|
|
11
11
|
type FocusEvent,
|
|
12
12
|
type PropsWithChildren,
|
|
13
|
+
useCallback,
|
|
13
14
|
} from 'react';
|
|
14
15
|
|
|
15
16
|
import { raise } from '@dxos/debug';
|
|
@@ -67,16 +68,14 @@ export const FormProvider = ({
|
|
|
67
68
|
>) => {
|
|
68
69
|
const form = useForm(formOptions);
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
71
|
+
const handleKeyDown = useCallback(
|
|
72
|
+
(event: KeyboardEvent) => {
|
|
76
73
|
const keyIsEnter = event.key === 'Enter';
|
|
77
74
|
const modifierUsed = event.ctrlKey || event.altKey || event.metaKey || event.shiftKey;
|
|
78
75
|
const inputIsTextarea = (event.target as HTMLElement).tagName.toLowerCase() === 'textarea';
|
|
79
|
-
const inputOptOut =
|
|
76
|
+
const inputOptOut =
|
|
77
|
+
(event.target as HTMLElement).hasAttribute('data-no-submit') ||
|
|
78
|
+
(event.target as HTMLElement).closest('[data-no-submit]') !== null;
|
|
80
79
|
|
|
81
80
|
// Regular inputs: Submit on Enter (no modifiers).
|
|
82
81
|
const shouldSubmitRegularInput = !inputIsTextarea && keyIsEnter && !modifierUsed;
|
|
@@ -92,7 +91,14 @@ export const FormProvider = ({
|
|
|
92
91
|
(event.target as HTMLElement).blur();
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
|
-
}
|
|
94
|
+
},
|
|
95
|
+
[form, autoSave],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!formRef?.current) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
96
102
|
|
|
97
103
|
const formElement = formRef.current;
|
|
98
104
|
|
|
@@ -34,18 +34,12 @@ export type InputHeaderProps = PropsWithChildren<{
|
|
|
34
34
|
|
|
35
35
|
export const InputHeader = ({ children, error }: InputHeaderProps) => {
|
|
36
36
|
return (
|
|
37
|
-
<div role='none' className='flex justify-between items-center
|
|
37
|
+
<div role='none' className='flex justify-between items-center mbs-2 mbe-1 first:mbs-0'>
|
|
38
38
|
{children}
|
|
39
39
|
{error && (
|
|
40
|
-
<Tooltip.
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
</Tooltip.Trigger>
|
|
44
|
-
<Tooltip.Content side='bottom'>
|
|
45
|
-
<Tooltip.Arrow />
|
|
46
|
-
{error}
|
|
47
|
-
</Tooltip.Content>
|
|
48
|
-
</Tooltip.Root>
|
|
40
|
+
<Tooltip.Trigger asChild content={error} side='bottom'>
|
|
41
|
+
<Icon icon='ph--warning--regular' size={4} classNames={errorText} />
|
|
42
|
+
</Tooltip.Trigger>
|
|
49
43
|
)}
|
|
50
44
|
</div>
|
|
51
45
|
);
|
|
@@ -2,149 +2,215 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { type SchemaAST } from 'effect';
|
|
6
|
+
import React, { useCallback, useMemo, type FocusEvent } from 'react';
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
|
-
type AST,
|
|
9
9
|
Expando,
|
|
10
10
|
getTypeAnnotation,
|
|
11
|
+
Ref,
|
|
11
12
|
ReferenceAnnotationId,
|
|
12
13
|
type ReferenceAnnotationValue,
|
|
13
|
-
type TypeAnnotation,
|
|
14
14
|
} from '@dxos/echo-schema';
|
|
15
|
-
import { findAnnotation
|
|
15
|
+
import { findAnnotation } from '@dxos/effect';
|
|
16
16
|
import { DXN } from '@dxos/keys';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { type
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
placeholder?: string;
|
|
32
|
-
inline?: boolean;
|
|
17
|
+
import { DxRefTag } from '@dxos/lit-ui/react';
|
|
18
|
+
import { Input, useTranslation } from '@dxos/react-ui';
|
|
19
|
+
import { TagPicker, type TagPickerMode, type TagPickerItemData } from '@dxos/react-ui-tag-picker';
|
|
20
|
+
import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
21
|
+
import { isNonNullable } from '@dxos/util';
|
|
22
|
+
|
|
23
|
+
import { TextInput } from './Defaults';
|
|
24
|
+
import { InputHeader, type InputProps } from './Input';
|
|
25
|
+
import { type QueryRefOptions, useQueryRefOptions } from '../../hooks';
|
|
26
|
+
import { translationKey } from '../../translations';
|
|
27
|
+
|
|
28
|
+
type RefFieldProps = InputProps & {
|
|
29
|
+
ast?: SchemaAST.AST;
|
|
30
|
+
array?: boolean;
|
|
33
31
|
onQueryRefOptions?: QueryRefOptions;
|
|
34
|
-
inputProps: FormInputStateProps;
|
|
35
32
|
};
|
|
36
33
|
|
|
37
34
|
export const RefField = ({
|
|
38
|
-
ast,
|
|
39
35
|
type,
|
|
40
36
|
label,
|
|
41
|
-
|
|
37
|
+
disabled,
|
|
42
38
|
placeholder,
|
|
43
|
-
|
|
39
|
+
inputOnly,
|
|
40
|
+
array,
|
|
41
|
+
ast,
|
|
42
|
+
getValue,
|
|
43
|
+
onBlur,
|
|
44
44
|
onQueryRefOptions,
|
|
45
|
-
|
|
45
|
+
onValueChange,
|
|
46
|
+
...restInputProps
|
|
46
47
|
}: RefFieldProps) => {
|
|
47
|
-
const {
|
|
48
|
-
const refTypeInfo =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
const { t } = useTranslation(translationKey);
|
|
49
|
+
const refTypeInfo = useMemo(
|
|
50
|
+
() => (ast ? findAnnotation<ReferenceAnnotationValue>(ast, ReferenceAnnotationId) : undefined),
|
|
51
|
+
[ast],
|
|
52
|
+
);
|
|
53
|
+
const { options: availableOptions, loading: _loading } = useQueryRefOptions({ refTypeInfo, onQueryRefOptions });
|
|
54
|
+
|
|
55
|
+
if ((refTypeInfo && refTypeInfo?.typename === getTypeAnnotation(Expando)?.typename) || !onQueryRefOptions) {
|
|
56
|
+
// If ref type is expando, fall back to taking a DXN in string format.
|
|
57
|
+
return (
|
|
58
|
+
<RefFieldFallback
|
|
59
|
+
{...{ type, label, placeholder, disabled, inputOnly, getValue, onBlur, onValueChange, ...restInputProps }}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
} catch (error) {
|
|
68
|
-
log.error('Failed to fetch ref options:', error);
|
|
69
|
-
setRefOptions([]);
|
|
70
|
-
} finally {
|
|
71
|
-
setLoading(false);
|
|
64
|
+
const handleGetValue = useCallback((): TagPickerItemData[] => {
|
|
65
|
+
const formValue = getValue();
|
|
66
|
+
|
|
67
|
+
const unknownToRefOption = (val: unknown) => {
|
|
68
|
+
if (Ref.isRef(val)) {
|
|
69
|
+
const dxnString = val.dxn.toString();
|
|
70
|
+
const matchingOption = availableOptions.find((option) => option.id === dxnString);
|
|
71
|
+
if (matchingOption) {
|
|
72
|
+
return matchingOption;
|
|
73
|
+
}
|
|
72
74
|
}
|
|
75
|
+
return undefined;
|
|
73
76
|
};
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
if (array && Array.isArray(formValue)) {
|
|
79
|
+
return formValue.map(unknownToRefOption).filter(isNonNullable) ?? [];
|
|
80
|
+
}
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
const option = unknownToRefOption(formValue);
|
|
83
|
+
if (option) {
|
|
84
|
+
return [option];
|
|
85
|
+
}
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
};
|
|
87
|
+
return [];
|
|
88
|
+
}, [getValue, availableOptions, array]);
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
const handleSearch = useCallback(
|
|
91
|
+
(text: string, ids: string[]): TagPickerItemData[] =>
|
|
92
|
+
availableOptions
|
|
93
|
+
.filter((option) => !ids.includes(option.id))
|
|
94
|
+
.filter((option) => option.label.toLowerCase().includes(text.toLowerCase()))
|
|
95
|
+
.map((option) => ({ id: option.id, label: option.label, hue: option.hue as any })),
|
|
96
|
+
[availableOptions],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const handleUpdate = useCallback(
|
|
100
|
+
(ids: string[]) => {
|
|
101
|
+
if (ids.length === 0) {
|
|
102
|
+
onValueChange('object', undefined);
|
|
103
|
+
return;
|
|
99
104
|
}
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
|
|
106
|
+
const refs = ids
|
|
107
|
+
.map((id) => {
|
|
108
|
+
const item = availableOptions.find((option) => option.id === id);
|
|
109
|
+
if (item) {
|
|
110
|
+
const dxn = DXN.parse(item.id);
|
|
111
|
+
return Ref.fromDXN(dxn);
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
})
|
|
115
|
+
.filter(isNonNullable);
|
|
116
|
+
|
|
117
|
+
if (array) {
|
|
118
|
+
onValueChange('object', refs);
|
|
119
|
+
} else {
|
|
120
|
+
onValueChange('object', refs[0]);
|
|
102
121
|
}
|
|
122
|
+
},
|
|
123
|
+
[availableOptions, array, onValueChange],
|
|
124
|
+
);
|
|
103
125
|
|
|
104
|
-
|
|
105
|
-
};
|
|
126
|
+
const tagPickerMode: TagPickerMode = useMemo(() => (array ? 'multi-select' : 'single-select'), [array]);
|
|
106
127
|
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
);
|
|
128
|
+
if (!refTypeInfo) {
|
|
129
|
+
return null;
|
|
119
130
|
}
|
|
120
131
|
|
|
132
|
+
const { status, error } = restInputProps.getStatus();
|
|
133
|
+
|
|
134
|
+
const items = handleGetValue();
|
|
135
|
+
|
|
136
|
+
// NOTE(thure): I left both predicates in-place in case we decide to add variants which do render readonly but empty values.
|
|
137
|
+
return disabled && items.length < 1 ? null : (
|
|
138
|
+
<Input.Root validationValence={status}>
|
|
139
|
+
{!inputOnly && (
|
|
140
|
+
<InputHeader error={error}>
|
|
141
|
+
<Input.Label>{label}</Input.Label>
|
|
142
|
+
</InputHeader>
|
|
143
|
+
)}
|
|
144
|
+
<div data-no-submit>
|
|
145
|
+
{disabled ? (
|
|
146
|
+
items.length < 1 ? (
|
|
147
|
+
<p className={mx(descriptionText, 'mbe-2')}>{t('empty readonly ref field label')}</p>
|
|
148
|
+
) : (
|
|
149
|
+
items.map((item) => (
|
|
150
|
+
<DxRefTag key={item.id} refid={item.id} rootclassname='mie-1' style={{ marginInlineEnd: '.25rem' }}>
|
|
151
|
+
{item.label}
|
|
152
|
+
</DxRefTag>
|
|
153
|
+
))
|
|
154
|
+
)
|
|
155
|
+
) : (
|
|
156
|
+
<TagPicker
|
|
157
|
+
readonly={disabled}
|
|
158
|
+
items={items}
|
|
159
|
+
mode={tagPickerMode}
|
|
160
|
+
onBlur={(event) => onBlur(event as unknown as FocusEvent<HTMLElement>)}
|
|
161
|
+
onUpdate={handleUpdate}
|
|
162
|
+
onSearch={handleSearch}
|
|
163
|
+
classNames='rounded-sm bg-inputSurface p-1.5'
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
{inputOnly && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
168
|
+
</Input.Root>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const RefFieldFallback = ({
|
|
173
|
+
type,
|
|
174
|
+
label,
|
|
175
|
+
disabled,
|
|
176
|
+
placeholder,
|
|
177
|
+
inputOnly,
|
|
178
|
+
getValue,
|
|
179
|
+
onValueChange,
|
|
180
|
+
...restInputProps
|
|
181
|
+
}: InputProps) => {
|
|
182
|
+
const handleOnValueChange = (_type: any, dxnString: string) => {
|
|
183
|
+
const dxn = DXN.tryParse(dxnString);
|
|
184
|
+
if (dxn) {
|
|
185
|
+
onValueChange?.('object', Ref.fromDXN(dxn));
|
|
186
|
+
} else if (dxnString === '') {
|
|
187
|
+
onValueChange?.('object', undefined);
|
|
188
|
+
} else {
|
|
189
|
+
onValueChange?.('string', dxnString);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
121
193
|
const handleGetValue = () => {
|
|
122
194
|
const formValue = getValue();
|
|
123
|
-
|
|
124
|
-
|
|
195
|
+
if (typeof formValue === 'string') {
|
|
196
|
+
return formValue;
|
|
197
|
+
}
|
|
198
|
+
if (Ref.isRef(formValue)) {
|
|
125
199
|
return formValue.dxn.toString();
|
|
126
200
|
}
|
|
127
201
|
|
|
128
202
|
return undefined;
|
|
129
203
|
};
|
|
130
204
|
|
|
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
205
|
return (
|
|
139
|
-
<
|
|
206
|
+
<TextInput
|
|
140
207
|
type={type}
|
|
141
208
|
label={label}
|
|
142
|
-
disabled={
|
|
143
|
-
placeholder={
|
|
144
|
-
inputOnly={
|
|
209
|
+
disabled={disabled}
|
|
210
|
+
placeholder={placeholder}
|
|
211
|
+
inputOnly={inputOnly}
|
|
145
212
|
getValue={handleGetValue as <V>() => V | undefined}
|
|
146
|
-
onValueChange={
|
|
147
|
-
options={refOptions}
|
|
213
|
+
onValueChange={handleOnValueChange}
|
|
148
214
|
{...restInputProps}
|
|
149
215
|
/>
|
|
150
216
|
);
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
+
import { Schema } from 'effect';
|
|
8
9
|
import React, { useState } from 'react';
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
+
import { SelectOptionSchema, type SelectOption } from '@dxos/echo-schema';
|
|
11
12
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
12
13
|
|
|
13
14
|
import { SelectOptionInput } from './SelectOptionsInput';
|
|
@@ -15,9 +16,9 @@ import translations from '../../../translations';
|
|
|
15
16
|
import { TestLayout, TestPanel } from '../../testing';
|
|
16
17
|
import { Form } from '../Form';
|
|
17
18
|
|
|
18
|
-
const schema =
|
|
19
|
-
options:
|
|
20
|
-
}).pipe(
|
|
19
|
+
const schema = Schema.Struct({
|
|
20
|
+
options: Schema.Array(SelectOptionSchema).pipe(Schema.mutable),
|
|
21
|
+
}).pipe(Schema.mutable);
|
|
21
22
|
|
|
22
23
|
const DefaultStory = () => {
|
|
23
24
|
const [values, setValues] = useState<{ options: SelectOption[] }>({
|
|
@@ -46,7 +47,7 @@ const DefaultStory = () => {
|
|
|
46
47
|
const meta: Meta = {
|
|
47
48
|
title: 'ui/react-ui-form/SelectOptionsInput',
|
|
48
49
|
component: SelectOptionInput,
|
|
49
|
-
decorators: [withTheme, withLayout(
|
|
50
|
+
decorators: [withTheme, withLayout()],
|
|
50
51
|
parameters: { translations },
|
|
51
52
|
};
|
|
52
53
|
|
|
@@ -12,7 +12,7 @@ import { GeoPointInput } from './custom';
|
|
|
12
12
|
/**
|
|
13
13
|
* Get property input component.
|
|
14
14
|
*/
|
|
15
|
-
export const getInputComponent = (type: SimpleType, format?: FormatEnum): InputComponent | undefined => {
|
|
15
|
+
export const getInputComponent = (type: SimpleType, format?: FormatEnum | string): InputComponent | undefined => {
|
|
16
16
|
switch (format) {
|
|
17
17
|
case FormatEnum.GeoPoint:
|
|
18
18
|
return GeoPointInput;
|
|
@@ -5,20 +5,23 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
+
import { Schema } from 'effect';
|
|
8
9
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
9
10
|
|
|
10
|
-
import { Format, type EchoSchema,
|
|
11
|
+
import { Format, type EchoSchema, toJsonSchema, TypedObject } from '@dxos/echo-schema';
|
|
11
12
|
import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
12
13
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
13
14
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
14
15
|
import { ViewProjection, ViewType, createView } from '@dxos/schema';
|
|
15
16
|
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
16
17
|
|
|
17
|
-
import { ViewEditor } from './ViewEditor';
|
|
18
|
+
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
18
19
|
import translations from '../../translations';
|
|
19
20
|
import { TestLayout, TestPanel } from '../testing';
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
type StoryProps = Pick<ViewEditorProps, 'readonly'>;
|
|
23
|
+
|
|
24
|
+
const DefaultStory = (props: StoryProps) => {
|
|
22
25
|
const space = useSpace();
|
|
23
26
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
24
27
|
const [view, setView] = useState<ViewType>();
|
|
@@ -26,7 +29,7 @@ const DefaultStory = () => {
|
|
|
26
29
|
useAsyncEffect(async () => {
|
|
27
30
|
if (space) {
|
|
28
31
|
class TestSchema extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
29
|
-
name:
|
|
32
|
+
name: Schema.String,
|
|
30
33
|
email: Format.Email,
|
|
31
34
|
salary: Format.Currency(),
|
|
32
35
|
}) {}
|
|
@@ -41,7 +44,7 @@ const DefaultStory = () => {
|
|
|
41
44
|
}
|
|
42
45
|
}, [space]);
|
|
43
46
|
|
|
44
|
-
const views = useQuery(space, Filter.
|
|
47
|
+
const views = useQuery(space, Filter.type(ViewType));
|
|
45
48
|
const currentTypename = useMemo(() => view?.query?.typename, [view]);
|
|
46
49
|
const updateViewTypename = useCallback(
|
|
47
50
|
(newTypename: string) => {
|
|
@@ -56,6 +59,7 @@ const DefaultStory = () => {
|
|
|
56
59
|
},
|
|
57
60
|
[views, schema],
|
|
58
61
|
);
|
|
62
|
+
|
|
59
63
|
const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
|
|
60
64
|
|
|
61
65
|
if (!schema || !view || !projection) {
|
|
@@ -69,6 +73,7 @@ const DefaultStory = () => {
|
|
|
69
73
|
schema={schema}
|
|
70
74
|
view={view}
|
|
71
75
|
registry={space?.db.schemaRegistry}
|
|
76
|
+
readonly={props.readonly}
|
|
72
77
|
onTypenameChanged={updateViewTypename}
|
|
73
78
|
onDelete={handleDelete}
|
|
74
79
|
/>
|
|
@@ -77,11 +82,10 @@ const DefaultStory = () => {
|
|
|
77
82
|
);
|
|
78
83
|
};
|
|
79
84
|
|
|
80
|
-
const meta: Meta<
|
|
85
|
+
const meta: Meta<StoryProps> = {
|
|
81
86
|
title: 'ui/react-ui-form/ViewEditor',
|
|
82
|
-
component: ViewEditor,
|
|
83
87
|
render: DefaultStory,
|
|
84
|
-
decorators: [withClientProvider({ createSpace: true }), withLayout({ fullscreen: true
|
|
88
|
+
decorators: [withClientProvider({ createSpace: true }), withLayout({ fullscreen: true }), withTheme],
|
|
85
89
|
parameters: {
|
|
86
90
|
translations,
|
|
87
91
|
},
|
|
@@ -89,6 +93,12 @@ const meta: Meta<typeof ViewEditor> = {
|
|
|
89
93
|
|
|
90
94
|
export default meta;
|
|
91
95
|
|
|
92
|
-
type Story = StoryObj
|
|
96
|
+
type Story = StoryObj<StoryProps>;
|
|
93
97
|
|
|
94
98
|
export const Default: Story = {};
|
|
99
|
+
|
|
100
|
+
export const Readonly: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
readonly: true,
|
|
103
|
+
},
|
|
104
|
+
};
|