@dxos/react-ui-form 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8
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 +4054 -2217
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +87 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +4054 -2217
- 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 +89 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -8
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +89 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +135 -25
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +207 -56
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +52 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +98 -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 +26 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormTooltipsContext.d.ts +19 -0
- package/dist/types/src/components/Form/FormTooltipsContext.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.d.ts +50 -0
- package/dist/types/src/components/Form/Layout/FormLayout.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts +123 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/annotation.d.ts +30 -0
- package/dist/types/src/components/Form/Layout/annotation.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/index.d.ts +4 -0
- package/dist/types/src/components/Form/Layout/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/parser.d.ts +31 -0
- package/dist/types/src/components/Form/Layout/parser.d.ts.map +1 -0
- package/dist/types/src/components/Form/Layout/parser.test.d.ts +2 -0
- package/dist/types/src/components/Form/Layout/parser.test.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/DateField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/DateField.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/InlineRefField.d.ts +13 -0
- package/dist/types/src/components/Form/fields/InlineRefField.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 +89 -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 +34 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/RefField.test.d.ts +2 -0
- package/dist/types/src/components/Form/fields/RefField.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts +13 -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 +89 -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 +14 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/index.d.ts +5 -4
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/meta-tags.d.ts +30 -0
- package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -0
- package/dist/types/src/components/Form/meta-tags.test.d.ts +2 -0
- package/dist/types/src/components/Form/meta-tags.test.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +8 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.stories.d.ts +87 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.stories.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 +68 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +92 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
- 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 +132 -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/ObjectTree/ObjectTree.d.ts +9 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.d.ts.map +1 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts +91 -0
- package/dist/types/src/components/ObjectTree/ObjectTree.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectTree/index.d.ts +2 -0
- package/dist/types/src/components/ObjectTree/index.d.ts.map +1 -0
- package/dist/types/src/components/RefEditor/RefEditor.d.ts +73 -0
- package/dist/types/src/components/RefEditor/RefEditor.d.ts.map +1 -0
- package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts +92 -0
- package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/RefEditor/index.d.ts +3 -0
- package/dist/types/src/components/RefEditor/index.d.ts.map +1 -0
- package/dist/types/src/components/RefEditor/ref-editor-extension.d.ts +47 -0
- package/dist/types/src/components/RefEditor/ref-editor-extension.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 +89 -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 +21 -11
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +91 -9
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +6 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +14 -4
- 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 +70 -55
- 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 +35 -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 +3 -3
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +64 -47
- package/src/components/FieldEditor/FieldEditor.stories.tsx +49 -31
- package/src/components/FieldEditor/FieldEditor.test.tsx +4 -2
- package/src/components/FieldEditor/FieldEditor.tsx +100 -97
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +151 -221
- package/src/components/Form/Form.tsx +461 -95
- package/src/components/Form/FormField.tsx +345 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +253 -0
- package/src/components/Form/FormFieldSet.tsx +213 -0
- package/src/components/Form/FormTooltipsContext.ts +25 -0
- package/src/components/Form/Layout/FormLayout.stories.test.tsx +51 -0
- package/src/components/Form/Layout/FormLayout.stories.tsx +422 -0
- package/src/components/Form/Layout/FormLayout.test.ts +59 -0
- package/src/components/Form/Layout/FormLayout.tsx +251 -0
- package/src/components/Form/Layout/annotation.ts +38 -0
- package/src/components/Form/Layout/index.ts +7 -0
- package/src/components/Form/Layout/parser.test.ts +120 -0
- package/src/components/Form/Layout/parser.ts +167 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +173 -0
- package/src/components/Form/fields/BooleanField.tsx +22 -0
- package/src/components/Form/fields/DateField.tsx +101 -0
- package/src/components/Form/fields/GeoPointField.tsx +124 -0
- package/src/components/Form/fields/InlineRefField.tsx +138 -0
- package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
- package/src/components/Form/fields/MarkdownField.tsx +167 -0
- package/src/components/Form/fields/NumberField.tsx +76 -0
- package/src/components/Form/fields/RefField.test.ts +45 -0
- package/src/components/Form/fields/RefField.tsx +266 -0
- package/src/components/Form/fields/SelectField.tsx +77 -0
- package/src/components/Form/fields/SelectOptionField.stories.tsx +66 -0
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +39 -32
- package/src/components/Form/fields/TextAreaField.tsx +38 -0
- package/src/components/Form/fields/TextField.tsx +38 -0
- package/src/components/Form/fields/TupleField.tsx +55 -0
- package/src/components/Form/fields/index.ts +17 -0
- package/src/components/Form/index.ts +6 -4
- package/src/components/Form/meta-tags.test.ts +47 -0
- package/src/components/Form/meta-tags.ts +33 -0
- package/src/components/ObjectForm/ObjectForm.stories.tsx +62 -0
- package/src/components/ObjectForm/ObjectForm.tsx +111 -0
- package/src/components/{ControlSection → ObjectForm}/index.ts +1 -1
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +212 -0
- package/src/components/ObjectPicker/ObjectPicker.tsx +202 -0
- package/src/components/ObjectPicker/index.ts +5 -0
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +350 -0
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +111 -0
- package/src/components/ObjectProperties/ObjectProperties.tsx +146 -0
- package/src/components/ObjectProperties/index.ts +5 -0
- package/src/components/ObjectTree/ObjectTree.stories.tsx +134 -0
- package/src/components/ObjectTree/ObjectTree.tsx +190 -0
- package/src/components/ObjectTree/index.ts +5 -0
- package/src/components/RefEditor/RefEditor.stories.tsx +148 -0
- package/src/components/RefEditor/RefEditor.tsx +275 -0
- package/src/components/RefEditor/index.ts +6 -0
- package/src/components/RefEditor/ref-editor-extension.ts +254 -0
- package/src/components/Settings/AUDIT.md +51 -0
- package/src/components/Settings/Settings.stories.tsx +104 -0
- package/src/components/Settings/Settings.tsx +81 -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 +132 -64
- package/src/components/ViewEditor/ViewEditor.test.tsx +23 -22
- package/src/components/ViewEditor/ViewEditor.tsx +378 -216
- package/src/components/index.ts +6 -3
- package/src/components/testing.tsx +31 -12
- 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 +76 -59
- package/src/util/field-type.ts +60 -0
- package/src/util/field.ts +33 -43
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +2 -1
- package/src/util/properties.test.ts +153 -0
- package/src/util/properties.ts +100 -0
- package/src/util/refs.ts +20 -19
- package/src/vite-env.d.ts +10 -0
- 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 -8
- 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 -57
- 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 -26
- package/dist/types/src/components/Form/Input.d.ts.map +0 -1
- package/dist/types/src/components/Form/RefField.d.ts +0 -12
- 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 -7
- 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/components/deprecated/Form.d.ts +0 -15
- package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
- package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/index.d.ts +0 -2
- package/dist/types/src/components/deprecated/index.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 -23
- 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 -42
- package/src/components/ControlSection/ControlSection.tsx +0 -139
- package/src/components/Form/ArrayField.tsx +0 -125
- package/src/components/Form/Defaults.tsx +0 -204
- package/src/components/Form/FormActions.tsx +0 -54
- package/src/components/Form/FormContent.tsx +0 -241
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -51
- package/src/components/Form/RefField.tsx +0 -214
- package/src/components/Form/custom/GeoPointInput.tsx +0 -72
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +0 -62
- package/src/components/Form/custom/TupleInput.tsx +0 -47
- package/src/components/Form/custom/index.ts +0 -7
- package/src/components/Form/factory.tsx +0 -31
- package/src/components/deprecated/Form.stories.tsx +0 -63
- package/src/components/deprecated/Form.tsx +0 -119
- package/src/components/deprecated/FormInput.tsx +0 -71
- package/src/components/deprecated/index.ts +0 -5
- package/src/hooks/useForm.ts +0 -293
- package/src/hooks/useQueryRefOptions.ts +0 -53
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -14
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import '@dxos/lit-ui/dx-tag-picker.pcss';
|
|
8
|
+
import { type Database, Entity, Filter, Obj, Query, Ref, Scope, Type } from '@dxos/echo';
|
|
9
|
+
import { useQuery, useType as defaultUseType } from '@dxos/echo-react';
|
|
10
|
+
import { ANY_OBJECT_TYPENAME, ReferenceAnnotationId, type ReferenceAnnotationValue } from '@dxos/echo/internal';
|
|
11
|
+
import { SchemaEx } from '@dxos/effect';
|
|
12
|
+
import { DXN, EID, URI } from '@dxos/keys';
|
|
13
|
+
import { DxAnchor } from '@dxos/lit-ui/react';
|
|
14
|
+
import { Button, Icon, Input, useTranslation } from '@dxos/react-ui';
|
|
15
|
+
import { ParentLabelAnnotationId } from '@dxos/schema';
|
|
16
|
+
|
|
17
|
+
import { translationKey } from '#translations';
|
|
18
|
+
|
|
19
|
+
import { ObjectPicker, type ObjectPickerContentProps, type RefOption } from '../../ObjectPicker';
|
|
20
|
+
import { omitHiddenFormFields, omitId } from '../Form';
|
|
21
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
22
|
+
|
|
23
|
+
// TODO(burdon): Factor out.
|
|
24
|
+
const isRefSnapshot = (val: any): val is { '/': string } => {
|
|
25
|
+
return typeof val === 'object' && typeof (val as any)?.['/'] === 'string';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Find the option a ref-like form value points at. Matches on the local (entity-id) form so a bare local EID
|
|
30
|
+
* (`echo:/<id>`, produced by `Ref.make`) still resolves against an option keyed by the entity's qualified
|
|
31
|
+
* self URI (`echo://<space>/<id>`). Returns `undefined` when the value is not a ref or no option matches.
|
|
32
|
+
*
|
|
33
|
+
* Comparing by entity id is only sound within one space (ids are unique there, not globally). Two EIDs that
|
|
34
|
+
* both carry a space authority must therefore agree on it: a qualified value and a qualified option from
|
|
35
|
+
* different spaces never match, even when their entity ids coincide.
|
|
36
|
+
*/
|
|
37
|
+
export const findRefOption = (value: unknown, options: RefOption[]): RefOption | undefined => {
|
|
38
|
+
const isRef = Ref.isRef(value);
|
|
39
|
+
if (!isRef && !isRefSnapshot(value)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const valueEid = EID.tryParse(isRef ? value.uri : value['/']);
|
|
43
|
+
if (!valueEid) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const valueSpaceId = EID.getSpaceId(valueEid);
|
|
47
|
+
const valueLocal = EID.toLocal(valueEid);
|
|
48
|
+
return options.find((option) => {
|
|
49
|
+
const optionEid = EID.tryParse(option.id);
|
|
50
|
+
if (!optionEid) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const optionSpaceId = EID.getSpaceId(optionEid);
|
|
54
|
+
if (valueSpaceId != null && optionSpaceId != null && valueSpaceId !== optionSpaceId) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return EID.equals(EID.toLocal(optionEid), valueLocal);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const defaultGetOptions: NonNullable<RefFieldProps['getOptions']> = (
|
|
62
|
+
results,
|
|
63
|
+
{ parentLabel, getTypePlaceholder } = {},
|
|
64
|
+
) =>
|
|
65
|
+
results
|
|
66
|
+
// When labeling by parent, only surface objects that actually have a parent — an unparented object
|
|
67
|
+
// (e.g. a system feed) has no meaningful parent to label by.
|
|
68
|
+
.filter((result) => !parentLabel || Obj.getParent(result as Obj.Unknown) != null)
|
|
69
|
+
.map((result) => {
|
|
70
|
+
const id = Entity.getURI(result, { prefer: 'named' });
|
|
71
|
+
const parent = parentLabel ? Obj.getParent(result as Obj.Unknown) : undefined;
|
|
72
|
+
const labelEntity = parent ?? result;
|
|
73
|
+
const typename = Entity.getTypename(labelEntity);
|
|
74
|
+
// Fall back to the entity's type placeholder when it has no label of its own — matching the
|
|
75
|
+
// app-graph/navtree (`object-name.placeholder` in the entity's typename namespace).
|
|
76
|
+
const label = Entity.getLabel(labelEntity) ?? (typename ? getTypePlaceholder?.(typename) : undefined) ?? id;
|
|
77
|
+
return { id, label };
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const defaultUseResults: NonNullable<RefFieldProps['useResults']> = (db, typename) =>
|
|
81
|
+
useQuery(
|
|
82
|
+
db,
|
|
83
|
+
!typename
|
|
84
|
+
? Query.select(Filter.nothing())
|
|
85
|
+
: typename === ANY_OBJECT_TYPENAME
|
|
86
|
+
? // Untyped refs show space objects only; the registry is too broad for "any".
|
|
87
|
+
Query.select(Filter.everything())
|
|
88
|
+
: // Include registry scope so keyed entities (blueprints, operations) appear as options.
|
|
89
|
+
Query.select(Filter.type(DXN.make(typename))).from(Scope.space(), Scope.registry()),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
export type RefFieldProps = FormFieldComponentProps &
|
|
93
|
+
Pick<
|
|
94
|
+
ObjectPickerContentProps,
|
|
95
|
+
'createOptionLabel' | 'createOptionIcon' | 'createInitialValuePath' | 'createFieldMap'
|
|
96
|
+
> & {
|
|
97
|
+
db?: Database.Database;
|
|
98
|
+
// TODO(burdon): Replace hooks with callbacks.
|
|
99
|
+
useType?: (db?: Database.Database, typeUri?: URI.URI) => Type.AnyEntity;
|
|
100
|
+
useResults?: (db?: Database.Database, typename?: string) => Entity.Any[];
|
|
101
|
+
getOptions?: (
|
|
102
|
+
objects: Entity.Any[],
|
|
103
|
+
options?: { parentLabel?: boolean; getTypePlaceholder?: (typename: string) => string },
|
|
104
|
+
) => RefOption[];
|
|
105
|
+
/**
|
|
106
|
+
* Persist a newly-created object. Called after the user fills out the
|
|
107
|
+
* inline create form and clicks Save. Should add the object to the
|
|
108
|
+
* database and return it (sync or async). The returned object is then
|
|
109
|
+
* wired into this slot's form value as a Ref.
|
|
110
|
+
*/
|
|
111
|
+
onCreate?: (schema: Type.AnyEntity, values: any) => Obj.Unknown | Promise<Obj.Unknown> | undefined | void;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const RefField = (props: RefFieldProps) => {
|
|
115
|
+
const {
|
|
116
|
+
type,
|
|
117
|
+
readonly,
|
|
118
|
+
label,
|
|
119
|
+
jsonPath,
|
|
120
|
+
placeholder,
|
|
121
|
+
layout,
|
|
122
|
+
getStatus,
|
|
123
|
+
getValue,
|
|
124
|
+
createOptionLabel,
|
|
125
|
+
createOptionIcon,
|
|
126
|
+
createInitialValuePath,
|
|
127
|
+
createFieldMap,
|
|
128
|
+
db,
|
|
129
|
+
useType = defaultUseType,
|
|
130
|
+
useResults = defaultUseResults,
|
|
131
|
+
getOptions = defaultGetOptions,
|
|
132
|
+
onCreate,
|
|
133
|
+
onValueChange,
|
|
134
|
+
} = props;
|
|
135
|
+
const { t } = useTranslation(translationKey);
|
|
136
|
+
const { status, error } = getStatus();
|
|
137
|
+
|
|
138
|
+
const typename = useMemo(
|
|
139
|
+
() => (type ? SchemaEx.findAnnotation<ReferenceAnnotationValue>(type, ReferenceAnnotationId)?.typename : undefined),
|
|
140
|
+
[type],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Resolve a type-based placeholder label for entities that have no label of their own — the entity's
|
|
144
|
+
// `object-name.placeholder` in its own typename namespace, like the app-graph/navtree. Falls back to a
|
|
145
|
+
// humanized typename segment so we never surface a raw DXN for a type that hasn't registered one.
|
|
146
|
+
const getTypePlaceholder = useCallback(
|
|
147
|
+
(placeholderTypename: string): string => {
|
|
148
|
+
const segment = placeholderTypename.split(/[.:/]/).filter(Boolean).pop() ?? placeholderTypename;
|
|
149
|
+
const humanized = segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
150
|
+
return t('object-name.placeholder', { ns: placeholderTypename, defaultValue: humanized });
|
|
151
|
+
},
|
|
152
|
+
[t],
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const results = useResults(db, typename);
|
|
156
|
+
const options = useMemo(() => {
|
|
157
|
+
const parentLabel = type ? SchemaEx.findAnnotation<boolean>(type, ParentLabelAnnotationId) === true : false;
|
|
158
|
+
return getOptions(results, { parentLabel, getTypePlaceholder });
|
|
159
|
+
}, [results, getOptions, type, getTypePlaceholder]);
|
|
160
|
+
|
|
161
|
+
const handleGetValue = useCallback(() => findRefOption(getValue(), options), [options, getValue]);
|
|
162
|
+
|
|
163
|
+
const item = handleGetValue();
|
|
164
|
+
const selectedIds = useMemo(() => (item ? [item.id] : []), [item]);
|
|
165
|
+
const createSchema = useType(db, typename && typename !== ANY_OBJECT_TYPENAME ? DXN.make(typename) : undefined);
|
|
166
|
+
|
|
167
|
+
// Lift the popover open state so we can dismiss it after a successful create.
|
|
168
|
+
const [open, setOpen] = useState(false);
|
|
169
|
+
|
|
170
|
+
const handleCreate = useCallback(
|
|
171
|
+
async (values: any) => {
|
|
172
|
+
if (!createSchema || !onCreate) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const newObject = await onCreate(createSchema, values);
|
|
176
|
+
if (newObject) {
|
|
177
|
+
// Wire the newly-created object into this slot's form value. ArrayField
|
|
178
|
+
// owns the array; this RefField represents a single slot, so writing a
|
|
179
|
+
// Ref via `onValueChange` populates that slot.
|
|
180
|
+
onValueChange(type, Ref.make(newObject));
|
|
181
|
+
}
|
|
182
|
+
setOpen(false);
|
|
183
|
+
},
|
|
184
|
+
[createSchema, onCreate, type, onValueChange],
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const handleUpdate = useCallback(
|
|
188
|
+
(id: string | undefined) => {
|
|
189
|
+
const item = options.find((option) => option.id === id);
|
|
190
|
+
const ref = item ? Ref.fromURI(URI.make(item.id)) : undefined;
|
|
191
|
+
onValueChange(type, ref);
|
|
192
|
+
},
|
|
193
|
+
[options, type, onValueChange],
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const handleSelect = useCallback(
|
|
197
|
+
(id: string) => {
|
|
198
|
+
if (item?.id === id) {
|
|
199
|
+
handleUpdate(undefined);
|
|
200
|
+
} else {
|
|
201
|
+
handleUpdate(id);
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
[item, handleUpdate],
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (!typename || ((readonly || layout === 'static') && !item)) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<Input.Root validationValence={status}>
|
|
213
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} path={jsonPath} />}
|
|
214
|
+
<div>
|
|
215
|
+
{readonly ? (
|
|
216
|
+
!item ? (
|
|
217
|
+
<p className='text-description mb-2'>{t('empty-readonly-ref-field.label')}</p>
|
|
218
|
+
) : (
|
|
219
|
+
<DxAnchor key={item.id} dxn={item.id} rootclassname='me-1'>
|
|
220
|
+
{item.label}
|
|
221
|
+
</DxAnchor>
|
|
222
|
+
)
|
|
223
|
+
) : (
|
|
224
|
+
<ObjectPicker.Root open={open} onOpenChange={setOpen}>
|
|
225
|
+
<ObjectPicker.Trigger asChild classNames='p-0'>
|
|
226
|
+
{item ? (
|
|
227
|
+
<div className='flex gap-form-gap w-full'>
|
|
228
|
+
<Input.Root key={item.id}>
|
|
229
|
+
<Input.TextInput value={item.label} readOnly classNames='w-full' />
|
|
230
|
+
</Input.Root>
|
|
231
|
+
</div>
|
|
232
|
+
) : (
|
|
233
|
+
<Button classNames='w-full text-start gap-form-gap'>
|
|
234
|
+
<div className='grow overflow-hidden'>
|
|
235
|
+
<span className='truncate text-description'>
|
|
236
|
+
{placeholder || label || t('ref-field.placeholder')}
|
|
237
|
+
</span>
|
|
238
|
+
</div>
|
|
239
|
+
<Icon size={3} icon='ph--caret-down--bold' />
|
|
240
|
+
</Button>
|
|
241
|
+
)}
|
|
242
|
+
</ObjectPicker.Trigger>
|
|
243
|
+
<ObjectPicker.Portal>
|
|
244
|
+
<ObjectPicker.Content
|
|
245
|
+
classNames='dx-card-popover-width'
|
|
246
|
+
options={options}
|
|
247
|
+
selectedIds={selectedIds}
|
|
248
|
+
// Strip hidden (`FormInputAnnotation.set(false)`) fields so the
|
|
249
|
+
// form's validator doesn't reject required-but-hidden fields
|
|
250
|
+
// such as backing-object refs supplied by a `FactoryAnnotation`.
|
|
251
|
+
createSchema={createSchema && omitHiddenFormFields(omitId(Type.getSchema(createSchema)))}
|
|
252
|
+
createOptionLabel={createOptionLabel}
|
|
253
|
+
createOptionIcon={createOptionIcon}
|
|
254
|
+
createInitialValuePath={createInitialValuePath}
|
|
255
|
+
createFieldMap={createFieldMap}
|
|
256
|
+
onCreate={handleCreate}
|
|
257
|
+
onSelect={handleSelect}
|
|
258
|
+
/>
|
|
259
|
+
</ObjectPicker.Portal>
|
|
260
|
+
</ObjectPicker.Root>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
{layout === 'full' && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
264
|
+
</Input.Root>
|
|
265
|
+
);
|
|
266
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Icon, Input, Select, type SelectRootProps } from '@dxos/react-ui';
|
|
8
|
+
import { getStyles } from '@dxos/ui-theme';
|
|
9
|
+
|
|
10
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
11
|
+
|
|
12
|
+
export type SelectFieldOptions = FormFieldComponentProps & {
|
|
13
|
+
options?: Array<{ value: string | number; label?: string; secondaryLabel?: string; icon?: string; iconHue?: string }>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const SelectField = ({
|
|
17
|
+
type,
|
|
18
|
+
readonly,
|
|
19
|
+
label,
|
|
20
|
+
jsonPath,
|
|
21
|
+
layout,
|
|
22
|
+
placeholder,
|
|
23
|
+
options,
|
|
24
|
+
getStatus,
|
|
25
|
+
getValue,
|
|
26
|
+
onValueChange,
|
|
27
|
+
}: SelectFieldOptions) => {
|
|
28
|
+
const { status, error } = getStatus();
|
|
29
|
+
const value = getValue() as string | undefined;
|
|
30
|
+
|
|
31
|
+
const handleValueChange = useCallback<NonNullable<SelectRootProps['onValueChange']>>(
|
|
32
|
+
(value) => onValueChange(type, value),
|
|
33
|
+
[type, onValueChange],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if ((readonly || layout === 'static') && value == null) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Input.Root validationValence={status}>
|
|
42
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} path={jsonPath} />}
|
|
43
|
+
{layout === 'static' ? (
|
|
44
|
+
<p>{options?.find(({ value: optionValue }) => optionValue === value)?.label ?? String(value)}</p>
|
|
45
|
+
) : (
|
|
46
|
+
<Select.Root value={value} onValueChange={handleValueChange}>
|
|
47
|
+
{/* TODO(burdon): Placeholder not working? */}
|
|
48
|
+
<Select.TriggerButton classNames='w-full' disabled={!!readonly} placeholder={placeholder} />
|
|
49
|
+
<Select.Portal>
|
|
50
|
+
<Select.Content>
|
|
51
|
+
<Select.Viewport>
|
|
52
|
+
{options?.map(({ value, label, secondaryLabel, icon, iconHue }) => (
|
|
53
|
+
// NOTE: Numeric values are converted to and from strings.
|
|
54
|
+
<Select.Option key={String(value)} value={String(value)}>
|
|
55
|
+
<span className='flex items-center flex-row gap-2'>
|
|
56
|
+
{icon && <Icon icon={icon} classNames={getIconHueStyles(iconHue)} />}
|
|
57
|
+
{label ?? String(value)}
|
|
58
|
+
{secondaryLabel && <span className='text-subdued text-xs'>{secondaryLabel}</span>}
|
|
59
|
+
</span>
|
|
60
|
+
</Select.Option>
|
|
61
|
+
))}
|
|
62
|
+
</Select.Viewport>
|
|
63
|
+
<Select.Arrow />
|
|
64
|
+
</Select.Content>
|
|
65
|
+
</Select.Portal>
|
|
66
|
+
</Select.Root>
|
|
67
|
+
)}
|
|
68
|
+
{layout === 'full' && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
69
|
+
</Input.Root>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getIconHueStyles = (iconHue?: string): string | undefined => {
|
|
74
|
+
const styles = iconHue ? getStyles(iconHue) : undefined;
|
|
75
|
+
|
|
76
|
+
return styles?.fg;
|
|
77
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import React, { useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { SelectOption } from '@dxos/echo/Format';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
|
|
12
|
+
import { translations } from '#translations';
|
|
13
|
+
|
|
14
|
+
import { TestLayout } from '../../testing';
|
|
15
|
+
import { Form } from '../Form';
|
|
16
|
+
import { SelectOptionField } from './SelectOptionField';
|
|
17
|
+
|
|
18
|
+
const schema = Schema.Struct({
|
|
19
|
+
options: Schema.Array(SelectOption).pipe(Schema.mutable),
|
|
20
|
+
}).pipe(Schema.mutable);
|
|
21
|
+
|
|
22
|
+
const DefaultStory = () => {
|
|
23
|
+
const [values, setValues] = useState<{ options: SelectOption[] }>({
|
|
24
|
+
// TODO(Zaymon): Pick colors from our limited set.
|
|
25
|
+
options: [
|
|
26
|
+
{ id: 'opt_1', title: 'First option', color: 'amber' },
|
|
27
|
+
{ id: 'opt_2', title: 'Second option', color: 'emerald' },
|
|
28
|
+
{ id: 'opt_3', title: 'Third option', color: 'rose' },
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<TestLayout>
|
|
34
|
+
<Form.Root
|
|
35
|
+
schema={schema}
|
|
36
|
+
values={values}
|
|
37
|
+
fieldMap={{ options: SelectOptionField }}
|
|
38
|
+
onSave={(values) => setValues(values)}
|
|
39
|
+
>
|
|
40
|
+
<Form.Viewport>
|
|
41
|
+
<Form.Content>
|
|
42
|
+
<Form.FieldSet />
|
|
43
|
+
<Form.Actions />
|
|
44
|
+
</Form.Content>
|
|
45
|
+
</Form.Viewport>
|
|
46
|
+
</Form.Root>
|
|
47
|
+
</TestLayout>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const meta = {
|
|
52
|
+
title: 'ui/react-ui-form/SelectOptionField',
|
|
53
|
+
component: SelectOptionField as any,
|
|
54
|
+
render: DefaultStory,
|
|
55
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
56
|
+
parameters: {
|
|
57
|
+
layout: 'fullscreen',
|
|
58
|
+
translations,
|
|
59
|
+
},
|
|
60
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
61
|
+
|
|
62
|
+
export default meta;
|
|
63
|
+
|
|
64
|
+
type Story = StoryObj<typeof meta>;
|
|
65
|
+
|
|
66
|
+
export const Default: Story = {};
|
|
@@ -4,30 +4,39 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type ChangeEvent, type KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type SelectOption } from '@dxos/echo
|
|
7
|
+
import { type SelectOption } from '@dxos/echo/Format';
|
|
8
8
|
import { PublicKey } from '@dxos/keys';
|
|
9
9
|
import { type ChromaticPalette, IconButton, Input, Tag, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
import { List } from '@dxos/react-ui-list';
|
|
11
11
|
import { HuePicker } from '@dxos/react-ui-pickers';
|
|
12
|
-
import { hues,
|
|
12
|
+
import { hues, osTranslations } from '@dxos/ui-theme';
|
|
13
13
|
|
|
14
|
-
import { translationKey } from '
|
|
15
|
-
import { InputHeader, type InputProps } from '../Input';
|
|
14
|
+
import { translationKey } from '#translations';
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
17
|
+
|
|
18
|
+
export const SelectOptionField = ({
|
|
19
|
+
type,
|
|
20
|
+
readonly,
|
|
21
|
+
label,
|
|
22
|
+
jsonPath,
|
|
23
|
+
getStatus,
|
|
24
|
+
getValue,
|
|
25
|
+
onValueChange,
|
|
26
|
+
}: FormFieldComponentProps<SelectOption[] | undefined>) => {
|
|
27
|
+
const { t } = useTranslation(translationKey);
|
|
28
|
+
const { status, error } = getStatus();
|
|
18
29
|
const [selected, setSelectedId] = useState<string | null>(null);
|
|
19
30
|
const [isNewOption, setIsNewOption] = useState(false);
|
|
20
31
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
21
|
-
const
|
|
22
|
-
const { status, error } = getStatus();
|
|
23
|
-
const options = getValue<SelectOption[] | undefined>();
|
|
32
|
+
const options = getValue();
|
|
24
33
|
|
|
25
34
|
// Initialization.
|
|
26
35
|
useEffect(() => {
|
|
27
36
|
if (options === undefined) {
|
|
28
37
|
onValueChange(type, []);
|
|
29
38
|
}
|
|
30
|
-
}, [options,
|
|
39
|
+
}, [options, type, onValueChange]);
|
|
31
40
|
|
|
32
41
|
const randomHue = () => {
|
|
33
42
|
const usedHues = new Set(options?.map((option) => option.color) ?? []);
|
|
@@ -88,8 +97,8 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
88
97
|
[options, type, onValueChange],
|
|
89
98
|
);
|
|
90
99
|
|
|
91
|
-
const handleKeyDown = useCallback((
|
|
92
|
-
if (
|
|
100
|
+
const handleKeyDown = useCallback((ev: KeyboardEvent<HTMLInputElement>) => {
|
|
101
|
+
if (ev.key === 'Enter') {
|
|
93
102
|
setSelectedId(null);
|
|
94
103
|
}
|
|
95
104
|
}, []);
|
|
@@ -111,14 +120,14 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
111
120
|
|
|
112
121
|
return (
|
|
113
122
|
<Input.Root validationValence={status}>
|
|
114
|
-
<
|
|
115
|
-
<div
|
|
123
|
+
<FormFieldLabel error={error} readonly={readonly} label={label} path={jsonPath} />
|
|
124
|
+
<div>
|
|
116
125
|
{options && (
|
|
117
126
|
<List.Root
|
|
118
127
|
items={options}
|
|
119
|
-
isItem={(
|
|
120
|
-
onMove={
|
|
121
|
-
readonly={
|
|
128
|
+
isItem={(_item) => true}
|
|
129
|
+
onMove={readonly ? undefined : handleMove}
|
|
130
|
+
readonly={!!readonly}
|
|
122
131
|
>
|
|
123
132
|
{({ items }) => (
|
|
124
133
|
<div role='list' className='w-full overflow-auto'>
|
|
@@ -127,11 +136,11 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
127
136
|
<List.Item
|
|
128
137
|
role='button'
|
|
129
138
|
item={item}
|
|
130
|
-
classNames=
|
|
139
|
+
classNames='flex flex-col cursor-pointer rounded-xs dx-hover'
|
|
131
140
|
aria-expanded={selected === item.id}
|
|
132
141
|
>
|
|
133
142
|
<div className='flex items-center'>
|
|
134
|
-
<List.ItemDragHandle disabled={
|
|
143
|
+
<List.ItemDragHandle disabled={!!readonly} />
|
|
135
144
|
<List.ItemTitle onClick={() => handleClick(item.id)} classNames='flex-1'>
|
|
136
145
|
{/* TODO(ZaymonFC): Move spacer into Tag component. */}
|
|
137
146
|
<Tag palette={item.color as ChromaticPalette}>{item.title || '\u200b'}</Tag>
|
|
@@ -139,32 +148,30 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
139
148
|
<IconButton
|
|
140
149
|
iconOnly
|
|
141
150
|
variant='ghost'
|
|
142
|
-
label={t(selected === item.id ? 'collapse
|
|
151
|
+
label={t(selected === item.id ? 'collapse.label' : 'expand.label', { ns: osTranslations })}
|
|
143
152
|
icon={selected === item.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
144
153
|
onClick={() => handleClick(item.id)}
|
|
145
154
|
/>
|
|
146
155
|
</div>
|
|
147
156
|
{selected === item.id && (
|
|
148
|
-
<div className='
|
|
149
|
-
{
|
|
150
|
-
<
|
|
151
|
-
<div className='grid grid-cols-[1fr_min-content_min-content] gap-1'>
|
|
157
|
+
<div className='flex flex-col p-form-padding gap-form-gap dx-density-md'>
|
|
158
|
+
<Input.Label classNames='text-sm'>{t('select-option.label')}</Input.Label>
|
|
159
|
+
<div className='grid grid-cols-[1fr_min-content_min-content] gap-form-gap'>
|
|
152
160
|
<Input.TextInput
|
|
153
|
-
disabled={
|
|
154
|
-
placeholder={t('select
|
|
161
|
+
disabled={!!readonly}
|
|
162
|
+
placeholder={t('select-option-label.placeholder')}
|
|
155
163
|
ref={selected === item.id ? inputRef : undefined}
|
|
156
164
|
value={item.title}
|
|
157
165
|
onChange={handleTitleChange(item.id)}
|
|
158
166
|
onKeyDown={handleKeyDown}
|
|
159
167
|
classNames='flex-1'
|
|
160
|
-
data-no-submit
|
|
161
168
|
/>
|
|
162
|
-
<HuePicker disabled={
|
|
169
|
+
<HuePicker disabled={!!readonly} value={item.color} onChange={handleColorChange(item.id)} />
|
|
163
170
|
<IconButton
|
|
164
|
-
disabled={
|
|
171
|
+
disabled={!!readonly}
|
|
165
172
|
icon='ph--trash--fill'
|
|
166
173
|
iconOnly
|
|
167
|
-
label={t('select
|
|
174
|
+
label={t('select-option-delete.button')}
|
|
168
175
|
onClick={() => handleDelete(item.id)}
|
|
169
176
|
/>
|
|
170
177
|
</div>
|
|
@@ -176,10 +183,10 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
176
183
|
<IconButton
|
|
177
184
|
variant='ghost'
|
|
178
185
|
icon='ph--plus--regular'
|
|
179
|
-
label={t('select
|
|
186
|
+
label={t('select-option-add.button')}
|
|
180
187
|
onClick={handleAdd}
|
|
181
|
-
disabled={
|
|
182
|
-
classNames='
|
|
188
|
+
disabled={!!readonly}
|
|
189
|
+
classNames='w-full'
|
|
183
190
|
/>
|
|
184
191
|
</div>
|
|
185
192
|
)}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Input, type TextAreaProps } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type FormFieldComponentProps, FormFieldWrapper } from '../FormFieldComponent';
|
|
10
|
+
|
|
11
|
+
export const TextAreaField = ({
|
|
12
|
+
type,
|
|
13
|
+
readonly,
|
|
14
|
+
placeholder,
|
|
15
|
+
onValueChange,
|
|
16
|
+
onBlur,
|
|
17
|
+
...props
|
|
18
|
+
}: FormFieldComponentProps<string>) => {
|
|
19
|
+
const handleChange = useCallback<NonNullable<TextAreaProps['onChange']>>(
|
|
20
|
+
(event) => onValueChange(type, event.target.value),
|
|
21
|
+
[type, onValueChange],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<FormFieldWrapper<string> readonly={readonly} {...props}>
|
|
26
|
+
{({ value = '' }) => (
|
|
27
|
+
<Input.TextArea
|
|
28
|
+
rows={5}
|
|
29
|
+
disabled={!!readonly}
|
|
30
|
+
placeholder={placeholder}
|
|
31
|
+
value={value}
|
|
32
|
+
onChange={handleChange}
|
|
33
|
+
onBlur={onBlur}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
</FormFieldWrapper>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Input, type TextInputProps } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type FormFieldComponentProps, FormFieldWrapper } from '../FormFieldComponent';
|
|
10
|
+
|
|
11
|
+
export const TextField = ({
|
|
12
|
+
type,
|
|
13
|
+
readonly,
|
|
14
|
+
placeholder,
|
|
15
|
+
onBlur,
|
|
16
|
+
onValueChange,
|
|
17
|
+
...props
|
|
18
|
+
}: FormFieldComponentProps<string>) => {
|
|
19
|
+
const handleChange = useCallback<NonNullable<TextInputProps['onChange']>>(
|
|
20
|
+
(event) => onValueChange(type, event.target.value),
|
|
21
|
+
[type, onValueChange],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<FormFieldWrapper<string> readonly={readonly} {...props}>
|
|
26
|
+
{({ value = '' }) => (
|
|
27
|
+
<Input.TextInput
|
|
28
|
+
noAutoFill
|
|
29
|
+
disabled={!!readonly}
|
|
30
|
+
placeholder={placeholder}
|
|
31
|
+
value={value}
|
|
32
|
+
onBlur={onBlur}
|
|
33
|
+
onChange={handleChange}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
</FormFieldWrapper>
|
|
37
|
+
);
|
|
38
|
+
};
|