@dxos/react-ui-form 0.8.4-main.c4373fc → 0.8.4-main.d05539e30a
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/README.md +0 -1
- package/dist/lib/browser/index.mjs +2747 -2324
- 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 +2747 -2324
- 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 +8 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +68 -60
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +109 -26
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +114 -137
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +43 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +79 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts +15 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +11 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
- 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 +4 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts +22 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +84 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/index.d.ts +12 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/index.d.ts +2 -4
- package/dist/types/src/components/Form/index.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 +37 -10
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +65 -59
- 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 +51 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts +84 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- 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 +4 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +15 -16
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +70 -64
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +3 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +4 -6
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -3
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts +7 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- 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 +65 -60
- 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 +3 -3
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +2 -2
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +2 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +13 -0
- package/dist/types/src/util/properties.d.ts.map +1 -0
- 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 +2 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +57 -51
- package/src/components/FieldEditor/FieldEditor.stories.tsx +44 -25
- package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
- package/src/components/FieldEditor/FieldEditor.tsx +76 -79
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +139 -251
- package/src/components/Form/Form.tsx +375 -106
- package/src/components/Form/FormField.tsx +306 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +174 -0
- package/src/components/Form/FormFieldSet.tsx +144 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +155 -0
- package/src/components/Form/fields/BooleanField.tsx +22 -0
- package/src/components/Form/fields/GeoPointField.tsx +121 -0
- package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
- package/src/components/Form/fields/MarkdownField.tsx +168 -0
- package/src/components/Form/fields/NumberField.tsx +39 -0
- package/src/components/Form/fields/RefField.tsx +229 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +23 -18
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +30 -24
- package/src/components/Form/fields/TextAreaField.tsx +38 -0
- package/src/components/Form/fields/TextField.tsx +38 -0
- package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
- package/src/components/Form/fields/index.ts +15 -0
- package/src/components/Form/index.ts +3 -4
- package/src/components/ObjectForm/ObjectForm.tsx +114 -0
- package/src/components/{ControlSection → ObjectForm}/index.ts +1 -1
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +50 -45
- package/src/components/ObjectPicker/ObjectPicker.tsx +127 -86
- 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 +104 -0
- package/src/components/Settings/Settings.tsx +80 -0
- 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 +7 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +73 -54
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -7
- package/src/components/ViewEditor/ViewEditor.tsx +304 -269
- package/src/components/index.ts +3 -1
- package/src/components/testing.tsx +24 -11
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +380 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/index.ts +0 -4
- package/src/translations.ts +70 -65
- package/src/util/field-type.ts +60 -0
- package/src/util/field.ts +25 -25
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +2 -1
- package/src/util/properties.test.ts +63 -0
- package/src/util/properties.ts +29 -0
- package/src/util/refs.ts +16 -18
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -84
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/index.d.ts +0 -2
- package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
- package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
- package/dist/types/src/components/Form/Defaults.d.ts +0 -14
- package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormActions.d.ts +0 -9
- package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContent.d.ts +0 -63
- package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContext.d.ts +0 -23
- package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
- package/dist/types/src/components/Form/Input.d.ts +0 -29
- package/dist/types/src/components/Form/Input.d.ts.map +0 -1
- package/dist/types/src/components/Form/RefField.d.ts +0 -21
- package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -79
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/index.d.ts +0 -4
- package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/factory.d.ts +0 -8
- package/dist/types/src/components/Form/factory.d.ts.map +0 -1
- package/dist/types/src/hooks/useForm.d.ts +0 -67
- package/dist/types/src/hooks/useForm.d.ts.map +0 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -28
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
- package/dist/types/src/util/schema.d.ts +0 -3
- package/dist/types/src/util/schema.d.ts.map +0 -1
- package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
- package/src/components/ControlSection/ControlSection.tsx +0 -153
- package/src/components/Form/ArrayField.tsx +0 -125
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -54
- package/src/components/Form/FormContent.tsx +0 -322
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -54
- package/src/components/Form/RefField.tsx +0 -269
- package/src/components/Form/custom/GeoPointInput.tsx +0 -80
- package/src/components/Form/custom/index.ts +0 -7
- package/src/components/Form/factory.tsx +0 -31
- package/src/hooks/useForm.ts +0 -295
- package/src/hooks/useQueryRefOptions.ts +0 -59
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -12
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import React, { useCallback, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { DXN, Obj, Ref, Tag, Type } from '@dxos/echo';
|
|
9
|
+
import { type JsonPath, splitJsonPath } from '@dxos/echo/internal';
|
|
10
|
+
import { invariant } from '@dxos/invariant';
|
|
11
|
+
import { HuePicker } from '@dxos/react-ui-pickers';
|
|
12
|
+
import { isNonNullable } from '@dxos/util';
|
|
13
|
+
|
|
14
|
+
import { translationKey } from '#translations';
|
|
15
|
+
|
|
16
|
+
import { Form, type FormFieldMap, omitId } from '../Form';
|
|
17
|
+
|
|
18
|
+
export type ObjectFormProps = {
|
|
19
|
+
schema: Schema.Schema.AnyNoContext;
|
|
20
|
+
object: Obj.Unknown;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const createFieldMap: FormFieldMap = {
|
|
24
|
+
hue: ({ type, label, layout, getValue, onValueChange }) => {
|
|
25
|
+
const handleChange = useCallback((nextHue: string) => onValueChange(type, nextHue), [onValueChange, type]);
|
|
26
|
+
const handleReset = useCallback(() => onValueChange(type, undefined), [onValueChange, type]);
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
{layout !== 'inline' && <Form.Label label={label} />}
|
|
30
|
+
<HuePicker value={getValue()} onChange={handleChange} onReset={handleReset} />
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const ObjectForm = ({ object, schema }: ObjectFormProps) => {
|
|
37
|
+
const db = Obj.getDatabase(object);
|
|
38
|
+
const meta = Obj.getMeta(object);
|
|
39
|
+
const tags = (meta.tags ?? []).map((tag) => db?.makeRef(DXN.parse(tag))).filter(isNonNullable);
|
|
40
|
+
const values = useMemo(() => ({ tags, ...object }), [object, tags]);
|
|
41
|
+
const formSchema = useMemo(
|
|
42
|
+
() =>
|
|
43
|
+
omitId(
|
|
44
|
+
Schema.Struct({
|
|
45
|
+
tags: Schema.Array(Ref.Ref(Tag.Tag)).pipe(Schema.optional),
|
|
46
|
+
}).pipe(Schema.extend(schema)),
|
|
47
|
+
),
|
|
48
|
+
[schema],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const handleCreate = useCallback((schema: Type.AnyEntity, values: any) => {
|
|
52
|
+
invariant(db);
|
|
53
|
+
invariant(Type.isObjectSchema(schema));
|
|
54
|
+
const newObject = db.add(Obj.make(schema, values));
|
|
55
|
+
if (Obj.instanceOf(Tag.Tag, newObject)) {
|
|
56
|
+
Obj.update(object, (object) => {
|
|
57
|
+
Obj.getMeta(object).tags = [...(Obj.getMeta(object).tags ?? []), Obj.getDXN(newObject).toString()];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
// TODO(wittjosiah): Use FormRootProps type.
|
|
63
|
+
const handleChange = useCallback(
|
|
64
|
+
({ tags, ...values }: any, { isValid, changed }: { isValid: boolean; changed: Record<JsonPath, boolean> }) => {
|
|
65
|
+
if (!isValid) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const changedPaths = Object.keys(changed).filter((path) => changed[path as JsonPath]) as JsonPath[];
|
|
70
|
+
|
|
71
|
+
// Handle tags separately using Obj.update.
|
|
72
|
+
const hasTagsChange = changedPaths.some((path) => splitJsonPath(path)[0] === 'tags');
|
|
73
|
+
if (hasTagsChange) {
|
|
74
|
+
Obj.update(object, (object) => {
|
|
75
|
+
Obj.getMeta(object).tags = tags?.map((tag: Ref.Ref<Tag.Tag>) => tag.dxn.toString()) ?? [];
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Handle other property changes.
|
|
80
|
+
const nonTagPaths = changedPaths.filter((path) => splitJsonPath(path)[0] !== 'tags');
|
|
81
|
+
if (nonTagPaths.length > 0) {
|
|
82
|
+
Obj.update(object, () => {
|
|
83
|
+
for (const path of nonTagPaths) {
|
|
84
|
+
const parts = splitJsonPath(path);
|
|
85
|
+
const value = Obj.getValue(values, parts);
|
|
86
|
+
Obj.setValue(object, parts, value);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
[object],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Form.Root
|
|
96
|
+
schema={formSchema}
|
|
97
|
+
defaultValues={values}
|
|
98
|
+
createTypename={Type.getTypename(Tag.Tag)}
|
|
99
|
+
createOptionIcon='ph--plus--regular'
|
|
100
|
+
createOptionLabel={['add-tag.label', { ns: translationKey }]}
|
|
101
|
+
createInitialValuePath='label'
|
|
102
|
+
createFieldMap={createFieldMap}
|
|
103
|
+
db={db}
|
|
104
|
+
onValuesChanged={handleChange}
|
|
105
|
+
onCreate={handleCreate}
|
|
106
|
+
>
|
|
107
|
+
<Form.Viewport>
|
|
108
|
+
<Form.Content>
|
|
109
|
+
<Form.FieldSet />
|
|
110
|
+
</Form.Content>
|
|
111
|
+
</Form.Viewport>
|
|
112
|
+
</Form.Root>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
@@ -7,36 +7,35 @@ import * as Schema from 'effect/Schema';
|
|
|
7
7
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
8
8
|
import { expect, fn, userEvent, within } from 'storybook/test';
|
|
9
9
|
|
|
10
|
-
import { Obj } from '@dxos/echo';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
10
|
+
import { Filter, Obj } from '@dxos/echo';
|
|
11
|
+
import { random } from '@dxos/random';
|
|
12
|
+
import { useQuery } from '@dxos/react-client/echo';
|
|
13
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
15
14
|
import { Button } from '@dxos/react-ui';
|
|
16
15
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
17
|
-
import {
|
|
16
|
+
import { Person } from '@dxos/types';
|
|
17
|
+
import { osTranslations } from '@dxos/ui-theme';
|
|
18
18
|
|
|
19
|
-
import { translations } from '
|
|
19
|
+
import { translations } from '#translations';
|
|
20
20
|
|
|
21
21
|
import { ObjectPicker } from './ObjectPicker';
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
random.seed(1);
|
|
24
24
|
|
|
25
25
|
const createPerson = () =>
|
|
26
|
-
Obj.make(
|
|
27
|
-
fullName:
|
|
26
|
+
Obj.make(Person.Person, {
|
|
27
|
+
fullName: random.person.fullName(),
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
const omitId = Schema.omit<any, any, ['id']>('id');
|
|
31
|
-
const personSchema = omitId(
|
|
31
|
+
const personSchema = omitId(Person.Person);
|
|
32
32
|
|
|
33
33
|
// Mock functions for testing
|
|
34
34
|
const mockHandleSelect = fn();
|
|
35
35
|
const mockHandleCreate = fn();
|
|
36
36
|
|
|
37
37
|
const DefaultStory = () => {
|
|
38
|
-
const
|
|
39
|
-
const { space } = useClientProvider();
|
|
38
|
+
const { space } = useClientStory();
|
|
40
39
|
const [isOpen, setIsOpen] = useState(false);
|
|
41
40
|
|
|
42
41
|
// Create sample Person objects
|
|
@@ -49,8 +48,8 @@ const DefaultStory = () => {
|
|
|
49
48
|
}
|
|
50
49
|
}, [space]);
|
|
51
50
|
|
|
52
|
-
// Get all objects in the space (similar to
|
|
53
|
-
const allObjects = useQuery(space, Filter.everything());
|
|
51
|
+
// Get all objects in the space (similar to BoardArticle)
|
|
52
|
+
const allObjects = useQuery(space?.db, Filter.everything());
|
|
54
53
|
|
|
55
54
|
// Map objects to options format expected by ObjectPicker
|
|
56
55
|
const options = useMemo(
|
|
@@ -66,8 +65,10 @@ const DefaultStory = () => {
|
|
|
66
65
|
const handleCreateCallback = useCallback(
|
|
67
66
|
(values: any) => {
|
|
68
67
|
console.log('[on create]', values);
|
|
69
|
-
if (!space)
|
|
70
|
-
|
|
68
|
+
if (!space) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const newPerson = space.db.add(Obj.make(Person.Person, values));
|
|
71
72
|
mockHandleCreate(values);
|
|
72
73
|
return newPerson;
|
|
73
74
|
},
|
|
@@ -75,22 +76,22 @@ const DefaultStory = () => {
|
|
|
75
76
|
);
|
|
76
77
|
|
|
77
78
|
return (
|
|
78
|
-
<div className='
|
|
79
|
+
<div className='w-96'>
|
|
79
80
|
<ObjectPicker.Root open={isOpen} onOpenChange={setIsOpen}>
|
|
80
81
|
<ObjectPicker.Trigger asChild>
|
|
81
|
-
<Button variant='primary' data-testid='trigger'>
|
|
82
|
+
<Button variant='primary' data-testid='trigger' classNames='w-full'>
|
|
82
83
|
Select Person
|
|
83
84
|
</Button>
|
|
84
85
|
</ObjectPicker.Trigger>
|
|
85
86
|
<ObjectPicker.Content
|
|
87
|
+
classNames='dx-card-popover-width'
|
|
86
88
|
options={options}
|
|
87
|
-
onSelect={mockHandleSelect}
|
|
88
89
|
createSchema={personSchema}
|
|
89
|
-
createOptionLabel={['create
|
|
90
|
+
createOptionLabel={['create-new-person.label', { ns: osTranslations }]}
|
|
90
91
|
createOptionIcon='ph--user-plus--regular'
|
|
91
92
|
createInitialValuePath='fullName'
|
|
92
93
|
onCreate={handleCreateCallback}
|
|
93
|
-
|
|
94
|
+
onSelect={mockHandleSelect}
|
|
94
95
|
/>
|
|
95
96
|
</ObjectPicker.Root>
|
|
96
97
|
</div>
|
|
@@ -101,9 +102,9 @@ const meta = {
|
|
|
101
102
|
title: 'ui/react-ui-form/ObjectPicker',
|
|
102
103
|
render: DefaultStory,
|
|
103
104
|
decorators: [
|
|
104
|
-
withTheme,
|
|
105
|
+
withTheme(),
|
|
105
106
|
withClientProvider({
|
|
106
|
-
types: [
|
|
107
|
+
types: [Person.Person],
|
|
107
108
|
createIdentity: true,
|
|
108
109
|
createSpace: true,
|
|
109
110
|
}),
|
|
@@ -119,37 +120,39 @@ export default meta;
|
|
|
119
120
|
|
|
120
121
|
type Story = StoryObj<typeof meta>;
|
|
121
122
|
|
|
122
|
-
export const Default: Story = {
|
|
123
|
+
export const Default: Story = {};
|
|
124
|
+
|
|
125
|
+
export const WithTest: Story = {
|
|
123
126
|
play: async ({ canvasElement }) => {
|
|
124
127
|
const canvas = within(canvasElement);
|
|
125
128
|
const body = within(document.body);
|
|
126
129
|
|
|
127
|
-
// Find and click the trigger button
|
|
130
|
+
// Find and click the trigger button.
|
|
128
131
|
const triggerButton = await canvas.findByTestId('trigger', undefined, { timeout: 5000 });
|
|
129
132
|
await expect(triggerButton).toBeVisible();
|
|
130
133
|
await userEvent.click(triggerButton);
|
|
131
134
|
|
|
132
|
-
// Check that the listbox renders when the trigger is clicked
|
|
135
|
+
// Check that the listbox renders when the trigger is clicked.
|
|
133
136
|
const listbox = await body.findByRole('listbox', undefined, { timeout: 5000 });
|
|
134
137
|
await expect(listbox).toBeVisible();
|
|
135
138
|
|
|
136
|
-
// Find the search input
|
|
139
|
+
// Find the search input.
|
|
137
140
|
const searchInput = await body.findByPlaceholderText('Search…');
|
|
138
141
|
await expect(searchInput).toBeVisible();
|
|
139
142
|
|
|
140
|
-
// Test searching filters the results - get initial options count
|
|
143
|
+
// Test searching filters the results - get initial options count.
|
|
141
144
|
const initialOptions = await body.findAllByRole('option');
|
|
142
145
|
const initialCount = initialOptions.length;
|
|
143
146
|
await expect(initialCount).toBeGreaterThan(0);
|
|
144
147
|
|
|
145
|
-
// Type a search term that should filter results
|
|
148
|
+
// Type a search term that should filter results.
|
|
146
149
|
await userEvent.type(searchInput, 'John');
|
|
147
150
|
|
|
148
|
-
// Wait a moment for filtering
|
|
151
|
+
// Wait a moment for filtering.
|
|
149
152
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
150
153
|
|
|
151
154
|
// Check that results are filtered (should be fewer options)
|
|
152
|
-
const
|
|
155
|
+
const _filteredOptions = await body.findAllByRole('option');
|
|
153
156
|
// Note: We expect either fewer results or the same if no John exists
|
|
154
157
|
|
|
155
158
|
// Clear search and test selecting a result
|
|
@@ -160,48 +163,50 @@ export const Default: Story = {
|
|
|
160
163
|
const firstOption = updatedOptions[0];
|
|
161
164
|
await expect(firstOption).toBeVisible();
|
|
162
165
|
|
|
163
|
-
const
|
|
166
|
+
const _firstOptionText = firstOption.textContent;
|
|
164
167
|
await userEvent.click(firstOption);
|
|
165
168
|
|
|
166
169
|
// Check that clicking a result fires onSelect with that id
|
|
167
170
|
// The onSelect should have been called with the selected option's id
|
|
168
171
|
await expect(mockHandleSelect).toHaveBeenCalled();
|
|
169
172
|
|
|
170
|
-
// Re-open the picker to test create functionality
|
|
173
|
+
// Re-open the picker to test create functionality.
|
|
171
174
|
await userEvent.click(triggerButton);
|
|
172
175
|
|
|
173
|
-
// Wait for listbox to appear again
|
|
176
|
+
// Wait for listbox to appear again.
|
|
174
177
|
const newListbox = await body.findByRole('listbox', undefined, { timeout: 5000 });
|
|
175
178
|
await expect(newListbox).toBeVisible();
|
|
176
179
|
|
|
177
180
|
const newSearchInput = await body.findByPlaceholderText('Search…');
|
|
178
181
|
|
|
179
|
-
// Test searching an unrelated term provides the __create__ option
|
|
182
|
+
// Test searching an unrelated term provides the __create__ option.
|
|
180
183
|
const unrelatedTerm = 'ZxyUnrelatedName123';
|
|
181
184
|
await userEvent.type(newSearchInput, unrelatedTerm);
|
|
182
185
|
|
|
183
|
-
// Wait for the create option to appear
|
|
186
|
+
// Wait for the create option to appear.
|
|
184
187
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
185
188
|
|
|
186
|
-
// Look for the create option
|
|
189
|
+
// Look for the create option.
|
|
187
190
|
const createOption = await body.findByRole('option', undefined, { timeout: 3000 });
|
|
188
191
|
await expect(createOption).toBeVisible();
|
|
189
192
|
|
|
190
|
-
// Click the create option - this should change state to the form field
|
|
193
|
+
// Click the create option - this should change state to the form field.
|
|
191
194
|
await userEvent.click(createOption);
|
|
192
195
|
|
|
193
|
-
// Check that clicking that option changes state to the form field
|
|
196
|
+
// Check that clicking that option changes state to the form field.
|
|
194
197
|
const form = await body.findByTestId('create-referenced-object-form', undefined, { timeout: 5000 });
|
|
195
198
|
await expect(form).toBeVisible();
|
|
196
199
|
|
|
197
|
-
// Find and click the save button
|
|
200
|
+
// Find and click the save button.
|
|
198
201
|
const saveButton = await within(form).findByTestId('save-button');
|
|
199
202
|
await expect(saveButton).toBeVisible();
|
|
200
203
|
await userEvent.click(saveButton);
|
|
201
204
|
|
|
202
|
-
// Check that clicking the save button calls onCreate with expected values
|
|
203
|
-
await expect(mockHandleCreate).toHaveBeenCalledWith(
|
|
204
|
-
|
|
205
|
-
|
|
205
|
+
// Check that clicking the save button calls onCreate with expected values.
|
|
206
|
+
await expect(mockHandleCreate).toHaveBeenCalledWith(
|
|
207
|
+
expect.objectContaining({
|
|
208
|
+
fullName: unrelatedTerm,
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
206
211
|
},
|
|
207
212
|
};
|
|
@@ -3,24 +3,40 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import type * as Schema from 'effect/Schema';
|
|
6
|
-
import React, { type KeyboardEvent,
|
|
6
|
+
import React, { type KeyboardEvent, forwardRef, useCallback, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { type Palette, Popover, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Combobox } from '@dxos/react-ui-list';
|
|
10
|
+
import { useSearchListResults } from '@dxos/react-ui-search';
|
|
11
|
+
|
|
12
|
+
import { translationKey } from '#translations';
|
|
10
13
|
|
|
11
|
-
import { type QueryTag } from '../../hooks';
|
|
12
|
-
import { translationKey } from '../../translations';
|
|
13
14
|
import { Form } from '../Form';
|
|
15
|
+
import { type FormFieldMap } from '../Form/FormFieldComponent';
|
|
16
|
+
|
|
17
|
+
export type RefOption = {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
hue?: Palette;
|
|
21
|
+
};
|
|
14
22
|
|
|
15
23
|
export type ObjectPickerContentProps = ThemedClassName<{
|
|
16
|
-
options:
|
|
24
|
+
options: RefOption[];
|
|
17
25
|
selectedIds?: string[];
|
|
18
|
-
createSchema?: Schema.Schema.AnyNoContext;
|
|
19
26
|
createOptionLabel?: [string, { ns: string }];
|
|
20
27
|
createOptionIcon?: string;
|
|
28
|
+
createSchema?: Schema.Schema.AnyNoContext;
|
|
21
29
|
createInitialValuePath?: string;
|
|
30
|
+
createFieldMap?: FormFieldMap;
|
|
31
|
+
/**
|
|
32
|
+
* Persist a newly-created object given the form values. May be async (e.g.
|
|
33
|
+
* to write to a database). The Promise is awaited before the inline create
|
|
34
|
+
* form is collapsed back to the picker list, so consumers can complete
|
|
35
|
+
* follow-up work (assign the new ref to a parent slot, close the popover)
|
|
36
|
+
* before the picker UI re-renders.
|
|
37
|
+
*/
|
|
38
|
+
onCreate?: (values: any) => unknown | Promise<unknown>;
|
|
22
39
|
onSelect: (id: string) => void;
|
|
23
|
-
onCreate?: (values: any) => void;
|
|
24
40
|
}>;
|
|
25
41
|
|
|
26
42
|
const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>(
|
|
@@ -32,46 +48,35 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
|
|
|
32
48
|
createOptionLabel,
|
|
33
49
|
createOptionIcon,
|
|
34
50
|
createInitialValuePath,
|
|
51
|
+
createFieldMap,
|
|
35
52
|
onSelect,
|
|
36
53
|
onCreate,
|
|
37
54
|
...props
|
|
38
55
|
},
|
|
39
|
-
|
|
56
|
+
forwardedRef,
|
|
40
57
|
) => {
|
|
41
58
|
const { t } = useTranslation(translationKey);
|
|
42
|
-
|
|
43
|
-
const [searchString, setSearchString] = useState('');
|
|
44
59
|
const [showForm, setShowForm] = useState(false);
|
|
60
|
+
const [formInitialValue, setFormInitialValue] = useState<string>('');
|
|
61
|
+
|
|
62
|
+
const { results, query, handleSearch } = useSearchListResults({
|
|
63
|
+
items: options,
|
|
64
|
+
});
|
|
45
65
|
|
|
46
66
|
const handleFormSave = useCallback(
|
|
47
|
-
(values: any) => {
|
|
48
|
-
onCreate?.(values);
|
|
67
|
+
async (values: any) => {
|
|
68
|
+
await onCreate?.(values);
|
|
49
69
|
setShowForm(false);
|
|
50
|
-
|
|
70
|
+
setFormInitialValue('');
|
|
51
71
|
},
|
|
52
72
|
[onCreate],
|
|
53
73
|
);
|
|
54
74
|
|
|
55
75
|
const handleFormCancel = useCallback(() => {
|
|
56
76
|
setShowForm(false);
|
|
57
|
-
|
|
77
|
+
setFormInitialValue('');
|
|
58
78
|
}, []);
|
|
59
79
|
|
|
60
|
-
// TODO(thure): The following click and keydown handlers are necessary because `onSelect` is called after the
|
|
61
|
-
// Popover is already closed. Augment/refactor CmdK, if possible, to facilitate stopping event default
|
|
62
|
-
// and propagation.
|
|
63
|
-
|
|
64
|
-
const handleClick = useCallback(
|
|
65
|
-
(event: MouseEvent) => {
|
|
66
|
-
if (createSchema && (event.target as HTMLElement).closest('[data-value="__create__"]')) {
|
|
67
|
-
event.stopPropagation();
|
|
68
|
-
event.preventDefault();
|
|
69
|
-
setShowForm(true);
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
[createSchema],
|
|
73
|
-
);
|
|
74
|
-
|
|
75
80
|
const handleKeyDown = useCallback(
|
|
76
81
|
(event: KeyboardEvent) => {
|
|
77
82
|
if (
|
|
@@ -83,79 +88,115 @@ const ObjectPickerContent = forwardRef<HTMLDivElement, ObjectPickerContentProps>
|
|
|
83
88
|
) {
|
|
84
89
|
event.stopPropagation();
|
|
85
90
|
event.preventDefault();
|
|
91
|
+
// Get the current query from the input element
|
|
92
|
+
const input = (event.currentTarget as HTMLElement).querySelector('input[type="text"]') as HTMLInputElement;
|
|
93
|
+
const currentQuery = input?.value || '';
|
|
94
|
+
setFormInitialValue(currentQuery);
|
|
86
95
|
setShowForm(true);
|
|
87
96
|
}
|
|
88
97
|
},
|
|
89
98
|
[createSchema],
|
|
90
99
|
);
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
(
|
|
94
|
-
|
|
95
|
-
acc[option.id.toLowerCase()] = option.label.toLowerCase();
|
|
96
|
-
return acc;
|
|
97
|
-
}, {}),
|
|
98
|
-
[options],
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<PopoverCombobox.Content
|
|
103
|
-
{...props}
|
|
104
|
-
ref={ref}
|
|
105
|
-
filter={(value, search) => (value === '__create__' || labelById[value]?.includes(search.toLowerCase()) ? 1 : 0)}
|
|
106
|
-
onClickCapture={handleClick}
|
|
107
|
-
onKeyDownCapture={handleKeyDown}
|
|
108
|
-
>
|
|
109
|
-
{showForm && createSchema ? (
|
|
101
|
+
if (showForm && createSchema) {
|
|
102
|
+
return (
|
|
103
|
+
<Combobox.Content {...props} onKeyDownCapture={handleKeyDown} ref={forwardedRef}>
|
|
110
104
|
<Popover.Viewport>
|
|
111
|
-
<Form
|
|
105
|
+
<Form.Root
|
|
106
|
+
testId='create-referenced-object-form'
|
|
112
107
|
schema={createSchema}
|
|
113
|
-
|
|
108
|
+
defaultValues={createInitialValuePath ? { [createInitialValuePath]: formInitialValue } : {}}
|
|
109
|
+
fieldMap={createFieldMap}
|
|
114
110
|
onSave={handleFormSave}
|
|
115
111
|
onCancel={handleFormCancel}
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
>
|
|
113
|
+
<Form.Viewport>
|
|
114
|
+
<Form.Content>
|
|
115
|
+
<Form.FieldSet />
|
|
116
|
+
<Form.Actions />
|
|
117
|
+
</Form.Content>
|
|
118
|
+
</Form.Viewport>
|
|
119
|
+
</Form.Root>
|
|
118
120
|
</Popover.Viewport>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
<Combobox.Arrow />
|
|
122
|
+
</Combobox.Content>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<Combobox.Content {...props} onKeyDownCapture={handleKeyDown} ref={forwardedRef}>
|
|
128
|
+
<Combobox.Input
|
|
129
|
+
placeholder={t('ref-field-combobox-input.placeholder')}
|
|
130
|
+
autoFocus
|
|
131
|
+
value={query}
|
|
132
|
+
onValueChange={handleSearch}
|
|
133
|
+
/>
|
|
134
|
+
<Combobox.List>
|
|
135
|
+
{results.map((option) => (
|
|
136
|
+
<Combobox.Item
|
|
137
|
+
key={option.id}
|
|
138
|
+
value={option.id}
|
|
139
|
+
label={option.label}
|
|
140
|
+
checked={selectedIds?.includes(option.id)}
|
|
141
|
+
onSelect={() => onSelect(option.id)}
|
|
142
|
+
classNames='flex items-center gap-2'
|
|
143
|
+
/>
|
|
144
|
+
))}
|
|
145
|
+
{createSchema && onCreate && (
|
|
146
|
+
<CreateItem
|
|
147
|
+
query={query}
|
|
148
|
+
createOptionLabel={createOptionLabel}
|
|
149
|
+
createOptionIcon={createOptionIcon ?? 'ph--plus--regular'}
|
|
150
|
+
onCreateItemSelect={(currentQuery: string) => {
|
|
151
|
+
setFormInitialValue(currentQuery);
|
|
152
|
+
setShowForm(true);
|
|
153
|
+
}}
|
|
126
154
|
/>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
value={option.id}
|
|
132
|
-
onSelect={() => onSelect(option.id)}
|
|
133
|
-
classNames='flex items-center gap-2'
|
|
134
|
-
>
|
|
135
|
-
<span className='grow'>{option.label}</span>
|
|
136
|
-
{selectedIds?.includes(option.id) && <Icon icon='ph--check--regular' />}
|
|
137
|
-
</PopoverCombobox.Item>
|
|
138
|
-
))}
|
|
139
|
-
{searchString.length > 0 && createOptionLabel && createOptionIcon && createSchema && onCreate && (
|
|
140
|
-
<PopoverCombobox.Item value='__create__' classNames='flex items-center gap-2'>
|
|
141
|
-
<Icon icon={createOptionIcon} />
|
|
142
|
-
{t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: searchString })}
|
|
143
|
-
</PopoverCombobox.Item>
|
|
144
|
-
)}
|
|
145
|
-
</PopoverCombobox.List>
|
|
146
|
-
</>
|
|
147
|
-
)}
|
|
148
|
-
<PopoverCombobox.Arrow />
|
|
149
|
-
</PopoverCombobox.Content>
|
|
155
|
+
)}
|
|
156
|
+
</Combobox.List>
|
|
157
|
+
<Combobox.Arrow />
|
|
158
|
+
</Combobox.Content>
|
|
150
159
|
);
|
|
151
160
|
},
|
|
152
161
|
);
|
|
153
162
|
|
|
154
|
-
|
|
163
|
+
const CreateItem = ({
|
|
164
|
+
query,
|
|
165
|
+
createOptionLabel,
|
|
166
|
+
createOptionIcon,
|
|
167
|
+
onCreateItemSelect,
|
|
168
|
+
}: {
|
|
169
|
+
query: string;
|
|
170
|
+
createOptionLabel?: [string, { ns: string }];
|
|
171
|
+
createOptionIcon: string;
|
|
172
|
+
onCreateItemSelect: (query: string) => void;
|
|
173
|
+
}) => {
|
|
174
|
+
const { t } = useTranslation(translationKey);
|
|
175
|
+
|
|
176
|
+
const label = createOptionLabel
|
|
177
|
+
? t(createOptionLabel[0], { ns: createOptionLabel[1].ns, text: query })
|
|
178
|
+
: t('create-option.label');
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<Combobox.Item
|
|
182
|
+
value='__create__'
|
|
183
|
+
label={label}
|
|
184
|
+
icon={createOptionIcon}
|
|
185
|
+
classNames='flex items-center gap-2'
|
|
186
|
+
closeOnSelect={false}
|
|
187
|
+
onSelect={() => {
|
|
188
|
+
onCreateItemSelect(query);
|
|
189
|
+
}}
|
|
190
|
+
/>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
ObjectPickerContent.displayName = 'ObjectPicker.Content';
|
|
155
195
|
|
|
156
196
|
export const ObjectPicker = {
|
|
157
|
-
Root:
|
|
158
|
-
|
|
159
|
-
|
|
197
|
+
Root: Combobox.Root,
|
|
198
|
+
Portal: Combobox.Portal,
|
|
199
|
+
Trigger: Combobox.Trigger,
|
|
200
|
+
VirtualTrigger: Combobox.VirtualTrigger,
|
|
160
201
|
Content: ObjectPickerContent,
|
|
161
202
|
};
|