@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,60 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
6
|
+
|
|
7
|
+
import { SchemaEx } from '@dxos/effect';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detected field type for a schema property.
|
|
11
|
+
*/
|
|
12
|
+
export type SettingsFieldType = 'boolean' | 'string' | 'number' | 'select' | 'struct';
|
|
13
|
+
|
|
14
|
+
export type SelectOption = {
|
|
15
|
+
value: string | number;
|
|
16
|
+
label?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect the field type from an Effect Schema AST node.
|
|
21
|
+
*/
|
|
22
|
+
export const detectFieldType = (ast: SchemaAST.AST): SettingsFieldType | undefined => {
|
|
23
|
+
if (SchemaEx.isLiteralUnion(ast)) {
|
|
24
|
+
return 'select';
|
|
25
|
+
}
|
|
26
|
+
if (SchemaAST.isTypeLiteral(ast)) {
|
|
27
|
+
return 'struct';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch (ast._tag) {
|
|
31
|
+
case 'BooleanKeyword':
|
|
32
|
+
return 'boolean';
|
|
33
|
+
case 'StringKeyword':
|
|
34
|
+
return 'string';
|
|
35
|
+
case 'NumberKeyword':
|
|
36
|
+
return 'number';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract select options from a literal union AST node.
|
|
44
|
+
* Reads title annotations from individual literals for labels.
|
|
45
|
+
*/
|
|
46
|
+
export const getSelectOptionsFromAst = (ast: SchemaAST.AST): SelectOption[] | undefined => {
|
|
47
|
+
if (!SchemaEx.isLiteralUnion(ast)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (ast as SchemaAST.Union<SchemaAST.Literal>).types.flatMap((literalNode) => {
|
|
52
|
+
const value = literalNode.literal;
|
|
53
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const title = SchemaEx.getAnnotation<string>(SchemaAST.TitleAnnotationId, false)(literalNode);
|
|
57
|
+
const label = title ?? (typeof value === 'string' ? value.charAt(0).toUpperCase() + value.slice(1) : String(value));
|
|
58
|
+
return [{ value, label }];
|
|
59
|
+
});
|
|
60
|
+
};
|
package/src/util/field.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Format, GeoLocation, TypeEnum } from '@dxos/echo/Format';
|
|
6
6
|
import { type ValidationError } from '@dxos/schema';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -13,7 +13,7 @@ import { type ValidationError } from '@dxos/schema';
|
|
|
13
13
|
*/
|
|
14
14
|
export type ParseProps = {
|
|
15
15
|
type?: TypeEnum;
|
|
16
|
-
format?:
|
|
16
|
+
format?: Format.TypeFormat;
|
|
17
17
|
value: any;
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -57,31 +57,31 @@ export const parseValue = ({ type, format, value }: ParseProps) => {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
switch (format) {
|
|
60
|
-
case
|
|
60
|
+
case Format.TypeFormat.Boolean:
|
|
61
61
|
return parseScalar(TypeEnum.Boolean);
|
|
62
62
|
|
|
63
|
-
case
|
|
64
|
-
case
|
|
65
|
-
case
|
|
63
|
+
case Format.TypeFormat.Number:
|
|
64
|
+
case Format.TypeFormat.Percent:
|
|
65
|
+
case Format.TypeFormat.Currency:
|
|
66
66
|
return parseScalar(TypeEnum.Number);
|
|
67
67
|
|
|
68
|
-
case
|
|
69
|
-
case
|
|
68
|
+
case Format.TypeFormat.String:
|
|
69
|
+
case Format.TypeFormat.Markdown: {
|
|
70
70
|
return parseScalar(TypeEnum.String);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
case
|
|
74
|
-
throw new Error(`unexpected format: ${
|
|
73
|
+
case Format.TypeFormat.Ref:
|
|
74
|
+
throw new Error(`unexpected format: ${Format.TypeFormat.Ref}`);
|
|
75
75
|
|
|
76
|
-
case
|
|
77
|
-
case
|
|
78
|
-
case
|
|
79
|
-
case
|
|
76
|
+
case Format.TypeFormat.DateTime:
|
|
77
|
+
case Format.TypeFormat.Date:
|
|
78
|
+
case Format.TypeFormat.Time:
|
|
79
|
+
case Format.TypeFormat.Timestamp: {
|
|
80
80
|
const date = new Date(value as string | number);
|
|
81
81
|
return isNaN(date.getTime()) ? null : date;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
case
|
|
84
|
+
case Format.TypeFormat.GeoPoint: {
|
|
85
85
|
// Parse string in format "lat,long" to GeoPoint [longitude, latitude].
|
|
86
86
|
if (typeof value === 'string') {
|
|
87
87
|
// Only keep digits, decimal points, minus signs, and commas.
|
|
@@ -108,41 +108,31 @@ export const parseValue = ({ type, format, value }: ParseProps) => {
|
|
|
108
108
|
// TODO(burdon): Type and format.
|
|
109
109
|
export type CellClassesForFieldTypeProps = {
|
|
110
110
|
type?: TypeEnum;
|
|
111
|
-
format?:
|
|
111
|
+
format?: Format.TypeFormat;
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
export const cellClassesForFieldType = ({ type, format }: CellClassesForFieldTypeProps): string[] | undefined => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
case TypeEnum.Number:
|
|
118
|
-
return ['text-right', 'font-mono'];
|
|
119
|
-
case TypeEnum.Boolean:
|
|
120
|
-
return ['text-right', 'font-mono'];
|
|
121
|
-
case TypeEnum.String:
|
|
122
|
-
return undefined;
|
|
123
|
-
case TypeEnum.Ref:
|
|
124
|
-
return undefined;
|
|
125
|
-
|
|
126
|
-
default: {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
switch (format) {
|
|
133
|
-
case FormatEnum.Markdown:
|
|
115
|
+
switch (format || type) {
|
|
116
|
+
case Format.TypeFormat.Markdown:
|
|
134
117
|
return undefined;
|
|
135
|
-
case
|
|
136
|
-
case
|
|
137
|
-
case
|
|
138
|
-
case
|
|
139
|
-
case
|
|
118
|
+
case Format.TypeFormat.Time:
|
|
119
|
+
case Format.TypeFormat.Timestamp:
|
|
120
|
+
case Format.TypeFormat.DateTime:
|
|
121
|
+
case Format.TypeFormat.Date:
|
|
122
|
+
case Format.TypeFormat.Duration:
|
|
140
123
|
return ['font-mono', 'text-right'];
|
|
141
|
-
case
|
|
124
|
+
case Format.TypeFormat.Currency:
|
|
142
125
|
return ['text-right'];
|
|
143
|
-
case
|
|
144
|
-
case
|
|
126
|
+
case Format.TypeFormat.JSON:
|
|
127
|
+
case Format.TypeFormat.DID:
|
|
145
128
|
return ['font-mono'];
|
|
129
|
+
case TypeEnum.Number:
|
|
130
|
+
case TypeEnum.Boolean:
|
|
131
|
+
return ['text-right', 'font-mono'];
|
|
132
|
+
case TypeEnum.String:
|
|
133
|
+
return undefined;
|
|
134
|
+
case TypeEnum.Ref:
|
|
135
|
+
return undefined;
|
|
146
136
|
default:
|
|
147
137
|
return undefined;
|
|
148
138
|
}
|
package/src/util/formatting.ts
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import { format as formatDate } from 'date-fns/format';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Format, GeoLocation, TypeEnum } from '@dxos/echo/Format';
|
|
8
8
|
|
|
9
9
|
type ValueFormatProps = {
|
|
10
10
|
type: TypeEnum;
|
|
11
|
-
format?:
|
|
11
|
+
format?: Format.TypeFormat | undefined;
|
|
12
12
|
value: any;
|
|
13
13
|
locale?: string | undefined;
|
|
14
14
|
};
|
|
@@ -41,20 +41,20 @@ export const formatForDisplay = ({ type, format, value, locale = undefined }: Va
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
switch (format) {
|
|
44
|
-
case
|
|
44
|
+
case Format.TypeFormat.Boolean: {
|
|
45
45
|
return formatScalar(TypeEnum.Boolean);
|
|
46
46
|
}
|
|
47
|
-
case
|
|
47
|
+
case Format.TypeFormat.Number: {
|
|
48
48
|
return formatScalar(TypeEnum.Number);
|
|
49
49
|
}
|
|
50
|
-
case
|
|
51
|
-
case
|
|
50
|
+
case Format.TypeFormat.String:
|
|
51
|
+
case Format.TypeFormat.Ref: {
|
|
52
52
|
return formatScalar(TypeEnum.String);
|
|
53
53
|
}
|
|
54
|
-
case
|
|
54
|
+
case Format.TypeFormat.Percent: {
|
|
55
55
|
return `${(value as number) * 100}%`;
|
|
56
56
|
}
|
|
57
|
-
case
|
|
57
|
+
case Format.TypeFormat.Currency: {
|
|
58
58
|
// TODO(burdon): Get from property annotation.
|
|
59
59
|
return (value as number).toLocaleString(locale, {
|
|
60
60
|
style: 'currency',
|
|
@@ -63,28 +63,28 @@ export const formatForDisplay = ({ type, format, value, locale = undefined }: Va
|
|
|
63
63
|
maximumFractionDigits: 2,
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
-
case
|
|
66
|
+
case Format.TypeFormat.Date: {
|
|
67
67
|
try {
|
|
68
68
|
return formatDate(new Date(value as number), 'yyyy-MM-dd');
|
|
69
69
|
} catch (error) {
|
|
70
70
|
return 'Invalid Date';
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
case
|
|
73
|
+
case Format.TypeFormat.Time: {
|
|
74
74
|
try {
|
|
75
75
|
return formatDate(new Date(value as number), 'HH:mm:ss');
|
|
76
76
|
} catch (error) {
|
|
77
77
|
return 'Invalid Time';
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
case
|
|
80
|
+
case Format.TypeFormat.DateTime: {
|
|
81
81
|
try {
|
|
82
82
|
return formatDate(new Date(value as number), 'yyyy-MM-dd HH:mm:ss');
|
|
83
83
|
} catch (error) {
|
|
84
84
|
return 'Invalid DateTime';
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
case
|
|
87
|
+
case Format.TypeFormat.GeoPoint: {
|
|
88
88
|
if (value === null || value === undefined) {
|
|
89
89
|
return '';
|
|
90
90
|
}
|
|
@@ -125,31 +125,31 @@ export const formatForEditing = ({ type, format, value, locale = undefined }: Va
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
switch (format) {
|
|
128
|
-
case
|
|
129
|
-
case
|
|
130
|
-
case
|
|
131
|
-
case
|
|
128
|
+
case Format.TypeFormat.Boolean:
|
|
129
|
+
case Format.TypeFormat.Number:
|
|
130
|
+
case Format.TypeFormat.String:
|
|
131
|
+
case Format.TypeFormat.Ref: {
|
|
132
132
|
return formatScalar(type);
|
|
133
133
|
}
|
|
134
|
-
case
|
|
134
|
+
case Format.TypeFormat.Percent: {
|
|
135
135
|
return String(value * 100);
|
|
136
136
|
}
|
|
137
|
-
case
|
|
137
|
+
case Format.TypeFormat.Currency: {
|
|
138
138
|
return String(value);
|
|
139
139
|
}
|
|
140
|
-
case
|
|
140
|
+
case Format.TypeFormat.DateTime: {
|
|
141
141
|
const date = new Date(value as number);
|
|
142
142
|
return date.toISOString();
|
|
143
143
|
}
|
|
144
|
-
case
|
|
144
|
+
case Format.TypeFormat.Date: {
|
|
145
145
|
const date = new Date(value as number);
|
|
146
146
|
return date.toISOString().split('T')[0];
|
|
147
147
|
}
|
|
148
|
-
case
|
|
148
|
+
case Format.TypeFormat.Time: {
|
|
149
149
|
const date = new Date(value as number);
|
|
150
150
|
return date.toISOString().split('T')[1].split('.')[0];
|
|
151
151
|
}
|
|
152
|
-
case
|
|
152
|
+
case Format.TypeFormat.GeoPoint: {
|
|
153
153
|
// Handle null or undefined
|
|
154
154
|
if (value === null || value === undefined) {
|
|
155
155
|
return '';
|
package/src/util/index.ts
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
import { describe, test } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { DXN, Annotation, JsonSchema, Type } from '@dxos/echo';
|
|
10
|
+
import { Format } from '@dxos/echo/Format';
|
|
11
|
+
import { SchemaEx } from '@dxos/effect';
|
|
12
|
+
|
|
13
|
+
import { getFormProperties, getRootFormProperties } from './properties';
|
|
14
|
+
|
|
15
|
+
describe('getFormProperties', () => {
|
|
16
|
+
test('filters out keyword fields annotated FormInputAnnotation.set(false)', ({ expect }) => {
|
|
17
|
+
const TestSchema = Schema.Struct({
|
|
18
|
+
name: Schema.optional(Schema.String),
|
|
19
|
+
hidden: Schema.String.pipe(Annotation.FormInputAnnotation.set(false)),
|
|
20
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.keywordHidden', '0.1.0')));
|
|
21
|
+
|
|
22
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
23
|
+
expect(names).toContain('name');
|
|
24
|
+
expect(names).not.toContain('hidden');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('filters out struct fields annotated FormInputAnnotation.set(false)', ({ expect }) => {
|
|
28
|
+
// Regression: JsonSchema fields are a Schema.Struct; encodedBoundAST strips
|
|
29
|
+
// annotations from non-keyword inner types, which previously bypassed the
|
|
30
|
+
// form-input filter (e.g., Routine.input).
|
|
31
|
+
const TestSchema = Schema.Struct({
|
|
32
|
+
name: Schema.optional(Schema.String),
|
|
33
|
+
schema: JsonSchema.JsonSchema.pipe(Annotation.FormInputAnnotation.set(false)),
|
|
34
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.structHidden', '0.1.0')));
|
|
35
|
+
|
|
36
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
37
|
+
expect(names).toContain('name');
|
|
38
|
+
expect(names).not.toContain('schema');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('filters out array fields annotated FormInputAnnotation.set(false)', ({ expect }) => {
|
|
42
|
+
const TestSchema = Schema.Struct({
|
|
43
|
+
name: Schema.optional(Schema.String),
|
|
44
|
+
context: Schema.Array(Schema.Any).pipe(Annotation.FormInputAnnotation.set(false)),
|
|
45
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.arrayHidden', '0.1.0')));
|
|
46
|
+
|
|
47
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
48
|
+
expect(names).toContain('name');
|
|
49
|
+
expect(names).not.toContain('context');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('filters out optional fields annotated FormInputAnnotation.set(false)', ({ expect }) => {
|
|
53
|
+
// Regression: optional fields' `prop.type` is the union `T | undefined`; the
|
|
54
|
+
// annotation lives on the inner `T`. Without unwrapping, the filter would
|
|
55
|
+
// miss the annotation and the hidden field would render. Covers the
|
|
56
|
+
// conventional pattern `Schema.X.pipe(FormInputAnnotation.set(false), Schema.optional)`.
|
|
57
|
+
const TestSchema = Schema.Struct({
|
|
58
|
+
name: Schema.optional(Schema.String),
|
|
59
|
+
userId: Schema.String.pipe(Annotation.FormInputAnnotation.set(false), Schema.optional),
|
|
60
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.optionalHidden', '0.1.0')));
|
|
61
|
+
|
|
62
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
63
|
+
expect(names).toContain('name');
|
|
64
|
+
expect(names).not.toContain('userId');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('preserves a deeply-nested FormInputAnnotation through unions and arrays', ({ expect }) => {
|
|
68
|
+
// Regression: a hidden field nested below a discriminated union and arrays of structs (e.g.
|
|
69
|
+
// Segment.details(road).routes[].legs[].geometry). `encodedBoundAST` strips annotations from
|
|
70
|
+
// non-keyword inner types, so without keeping container types raw the annotation is lost by
|
|
71
|
+
// the time the form recurses to the leaf field. The transformation element (GeoPoint) is the
|
|
72
|
+
// trigger that forces the encode rebuild.
|
|
73
|
+
const Leg = Schema.Struct({
|
|
74
|
+
distance: Schema.Number,
|
|
75
|
+
geometry: Schema.Array(Format.GeoPoint).pipe(Annotation.FormInputAnnotation.set(false)),
|
|
76
|
+
});
|
|
77
|
+
const Route = Schema.Struct({ legs: Schema.Array(Leg) });
|
|
78
|
+
const TestSchema = Schema.Struct({
|
|
79
|
+
details: Schema.Union(
|
|
80
|
+
Schema.TaggedStruct('road', { routes: Schema.optional(Schema.Array(Route)) }),
|
|
81
|
+
Schema.TaggedStruct('other', { note: Schema.optional(Schema.String) }),
|
|
82
|
+
),
|
|
83
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.nestedHidden', '0.1.0')));
|
|
84
|
+
|
|
85
|
+
// Drill as the form does: getFormProperties at each level, descending via the property type
|
|
86
|
+
// (array element / type literal) returned by the previous level.
|
|
87
|
+
const findTypeLiteralWith = (ast: SchemaAST.AST, prop: string) =>
|
|
88
|
+
SchemaEx.findNode(
|
|
89
|
+
ast,
|
|
90
|
+
(node) => SchemaAST.isTypeLiteral(node) && SchemaAST.getPropertySignatures(node).some((p) => p.name === prop),
|
|
91
|
+
)!;
|
|
92
|
+
const propType = (ast: SchemaAST.AST, name: string) => getFormProperties(ast).find((p) => p.name === name)!.type;
|
|
93
|
+
|
|
94
|
+
const detailsType = propType(Type.getSchema(TestSchema).ast, 'details');
|
|
95
|
+
const roadTypeLiteral = findTypeLiteralWith(detailsType, 'routes');
|
|
96
|
+
const routeTypeLiteral = SchemaEx.findNode(
|
|
97
|
+
SchemaEx.getArrayElementType(propType(roadTypeLiteral, 'routes'))!,
|
|
98
|
+
SchemaAST.isTypeLiteral,
|
|
99
|
+
)!;
|
|
100
|
+
const legTypeLiteral = SchemaEx.findNode(
|
|
101
|
+
SchemaEx.getArrayElementType(propType(routeTypeLiteral, 'legs'))!,
|
|
102
|
+
SchemaAST.isTypeLiteral,
|
|
103
|
+
)!;
|
|
104
|
+
|
|
105
|
+
const names = getFormProperties(legTypeLiteral).map((prop) => prop.name);
|
|
106
|
+
expect(names).toContain('distance');
|
|
107
|
+
expect(names).not.toContain('geometry');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('preserves annotation when chained with .annotations()', ({ expect }) => {
|
|
111
|
+
// Regression: `.pipe(FormInputAnnotation.set(false)).annotations({...})` must
|
|
112
|
+
// not lose the form-input annotation.
|
|
113
|
+
const TestSchema = Schema.Struct({
|
|
114
|
+
name: Schema.optional(Schema.String),
|
|
115
|
+
hidden: JsonSchema.JsonSchema.pipe(Annotation.FormInputAnnotation.set(false)).annotations({
|
|
116
|
+
description: 'Hidden field',
|
|
117
|
+
}),
|
|
118
|
+
}).pipe(Type.makeObject(DXN.make('org.dxos.test.chainedHidden', '0.1.0')));
|
|
119
|
+
|
|
120
|
+
const names = getFormProperties(Type.getSchema(TestSchema).ast).map((prop) => prop.name);
|
|
121
|
+
expect(names).toContain('name');
|
|
122
|
+
expect(names).not.toContain('hidden');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('getRootFormProperties', () => {
|
|
127
|
+
const Timer = Schema.Struct({ kind: Schema.Literal('timer'), cron: Schema.String });
|
|
128
|
+
const Feed = Schema.Struct({ kind: Schema.Literal('feed'), feed: Schema.String });
|
|
129
|
+
const Union = Schema.Union(Timer, Feed);
|
|
130
|
+
|
|
131
|
+
test('renders a non-union root unchanged', ({ expect }) => {
|
|
132
|
+
const ast = Schema.Struct({ a: Schema.String, b: Schema.Number }).ast;
|
|
133
|
+
expect(getRootFormProperties(ast, {}).map((prop) => prop.name)).toEqual(['a', 'b']);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('expands a top-level discriminated union to the active member by discriminator value', ({ expect }) => {
|
|
137
|
+
// `getPropertySignatures` on the union alone yields only the common `kind`; the active member's
|
|
138
|
+
// variant field (`cron`/`feed`) must also render.
|
|
139
|
+
expect(getRootFormProperties(Union.ast, { kind: 'timer' }).map((prop) => prop.name)).toEqual(['kind', 'cron']);
|
|
140
|
+
expect(getRootFormProperties(Union.ast, { kind: 'feed' }).map((prop) => prop.name)).toEqual(['kind', 'feed']);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('keeps the discriminator field switchable (union-wide literals, not the matched single literal)', ({
|
|
144
|
+
expect,
|
|
145
|
+
}) => {
|
|
146
|
+
const kind = getRootFormProperties(Union.ast, { kind: 'feed' }).find((prop) => prop.name === 'kind');
|
|
147
|
+
expect(kind && SchemaEx.getLiteralValues(Schema.make(kind.type))).toEqual(['timer', 'feed']);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('falls back to the discriminator alone when no value selects a member', ({ expect }) => {
|
|
151
|
+
expect(getRootFormProperties(Union.ast, {}).map((prop) => prop.name)).toEqual(['kind']);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Option from 'effect/Option';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
|
|
8
|
+
import { Annotation } from '@dxos/echo';
|
|
9
|
+
import { type AnyProperties } from '@dxos/echo/internal';
|
|
10
|
+
import { SchemaEx } from '@dxos/effect';
|
|
11
|
+
|
|
12
|
+
/** The property's type with an optional `T | undefined` union unwrapped to its inner `T`. */
|
|
13
|
+
const unwrapOptional = (prop: SchemaAST.PropertySignature): SchemaAST.AST => {
|
|
14
|
+
if (!prop.isOptional || !SchemaAST.isUnion(prop.type)) {
|
|
15
|
+
return prop.type;
|
|
16
|
+
}
|
|
17
|
+
// Drop the `undefined` member, preserving the remaining union (don't collapse `A | B | undefined` to `A`).
|
|
18
|
+
const defined = prop.type.types.filter((type) => type._tag !== 'UndefinedKeyword');
|
|
19
|
+
if (defined.length === 0) {
|
|
20
|
+
return prop.type;
|
|
21
|
+
}
|
|
22
|
+
return defined.length === 1 ? defined[0] : SchemaAST.Union.make(defined, prop.type.annotations);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the properties to render for a field set's *root* schema, given the current form values.
|
|
27
|
+
*
|
|
28
|
+
* A top-level discriminated union is rendered flat: the fields of the member selected by the current
|
|
29
|
+
* discriminator value, with the discriminator field keeping the union-wide set of literals so it stays
|
|
30
|
+
* switchable. (`SchemaAST.getPropertySignatures` on a union returns only the common discriminator, so a
|
|
31
|
+
* union root would otherwise render just that one field.) Non-union roots are unchanged. Nested unions are
|
|
32
|
+
* unaffected — those are expanded per-field by `FormField`, which passes a single-member type literal here.
|
|
33
|
+
*/
|
|
34
|
+
export const getRootFormProperties = (
|
|
35
|
+
ast: SchemaAST.AST,
|
|
36
|
+
values: AnyProperties | undefined,
|
|
37
|
+
): SchemaEx.SchemaProperty[] => {
|
|
38
|
+
if (!SchemaEx.isDiscriminatedUnion(ast)) {
|
|
39
|
+
return getFormProperties(ast);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const activeType = SchemaEx.getDiscriminatedType(ast, values ?? {});
|
|
43
|
+
if (!activeType) {
|
|
44
|
+
return getFormProperties(ast);
|
|
45
|
+
}
|
|
46
|
+
const activeProps = getFormProperties(activeType);
|
|
47
|
+
|
|
48
|
+
const discriminators = new Set((SchemaEx.getDiscriminatingProps(ast) ?? []).map((name) => name.toString()));
|
|
49
|
+
if (discriminators.size === 0) {
|
|
50
|
+
return activeProps;
|
|
51
|
+
}
|
|
52
|
+
// The matched member types its discriminator as a single literal; swap in the union-wide literal so the
|
|
53
|
+
// field renders as a full select rather than a fixed value.
|
|
54
|
+
const fullType = SchemaEx.getDiscriminatedType(ast, {});
|
|
55
|
+
const fullDiscriminatorProps = fullType ? getFormProperties(fullType) : activeProps;
|
|
56
|
+
return activeProps.map((prop) =>
|
|
57
|
+
discriminators.has(prop.name.toString())
|
|
58
|
+
? (fullDiscriminatorProps.find((candidate) => candidate.name === prop.name) ?? prop)
|
|
59
|
+
: prop,
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the property types of an AST and filter out properties that are not form inputs.
|
|
65
|
+
*
|
|
66
|
+
* The `FormInputAnnotation` is read from the raw property signature's AST (before
|
|
67
|
+
* `encodedBoundAST` is applied by `SchemaEx.getProperties`), because `encodedBoundAST` strips
|
|
68
|
+
* annotations from non-keyword inner types (e.g., `Schema.Struct`, `Schema.Array`).
|
|
69
|
+
* Without this, fields like `Routine.input` (a JsonSchema struct) annotated
|
|
70
|
+
* `FormInputAnnotation.set(false)` would still render in the form.
|
|
71
|
+
*
|
|
72
|
+
* For optional fields, `prop.type` is the union `T | undefined` — the annotation
|
|
73
|
+
* lives on the inner `T`. Unwrap before reading so that the conventional pattern
|
|
74
|
+
* `Schema.String.pipe(FormInputAnnotation.set(false), Schema.optional)` works.
|
|
75
|
+
*
|
|
76
|
+
* Container properties (nested structs, unions, arrays) keep their *raw* AST rather than
|
|
77
|
+
* the `encodedBoundAST` produced by `SchemaEx.getProperties`. `encodedBoundAST` strips annotations
|
|
78
|
+
* from non-keyword inner types, so a `FormInputAnnotation` nested beneath an encoded container
|
|
79
|
+
* (e.g. `RouteLeg.geometry`, reached via `Segment → details → routes → Route → legs`) would
|
|
80
|
+
* otherwise be lost by the time recursion reaches it. Leaf/transformation fields stay encoded
|
|
81
|
+
* (e.g. a `DateTime` still renders as its encoded string input).
|
|
82
|
+
*/
|
|
83
|
+
export const getFormProperties = (ast: SchemaAST.AST): SchemaEx.SchemaProperty[] => {
|
|
84
|
+
const signatures = SchemaAST.getPropertySignatures(ast);
|
|
85
|
+
const rawByName = new Map<PropertyKey, SchemaAST.AST>(signatures.map((prop) => [prop.name, unwrapOptional(prop)]));
|
|
86
|
+
const hidden = new Set(
|
|
87
|
+
signatures
|
|
88
|
+
.filter(
|
|
89
|
+
(prop) =>
|
|
90
|
+
Annotation.FormInputAnnotation.getFromAst(unwrapOptional(prop)).pipe(Option.getOrElse(() => true)) === false,
|
|
91
|
+
)
|
|
92
|
+
.map((prop) => prop.name),
|
|
93
|
+
);
|
|
94
|
+
return SchemaEx.getProperties(ast)
|
|
95
|
+
.filter((prop) => !hidden.has(prop.name))
|
|
96
|
+
.map((prop) => {
|
|
97
|
+
const raw = rawByName.get(prop.name);
|
|
98
|
+
return raw && (SchemaEx.isNestedType(raw) || SchemaEx.isArrayType(raw)) ? { ...prop, type: raw } : prop;
|
|
99
|
+
});
|
|
100
|
+
};
|
package/src/util/refs.ts
CHANGED
|
@@ -2,37 +2,38 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import type * as SchemaAST from 'effect/SchemaAST';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import { findArrayElementType } from './schema';
|
|
7
|
+
import { Ref } from '@dxos/echo';
|
|
8
|
+
import { type ReferenceAnnotationValue, ReferenceAnnotationId } from '@dxos/echo/Annotation';
|
|
9
|
+
import { SchemaEx } from '@dxos/effect';
|
|
12
10
|
|
|
13
11
|
type RefProps = {
|
|
14
12
|
ast: SchemaAST.AST;
|
|
15
13
|
isArray: boolean;
|
|
14
|
+
typename?: string;
|
|
16
15
|
};
|
|
17
16
|
|
|
18
|
-
export const getRefProps = (
|
|
19
|
-
const { ast, format, array } = property;
|
|
20
|
-
|
|
21
|
-
// Direct reference.
|
|
22
|
-
if (format === FormatEnum.Ref) {
|
|
23
|
-
return { ast, isArray: false };
|
|
24
|
-
}
|
|
25
|
-
|
|
17
|
+
export const getRefProps = (ast: SchemaAST.AST): RefProps | undefined => {
|
|
26
18
|
// Array of references.
|
|
27
|
-
if (
|
|
28
|
-
const elementType =
|
|
19
|
+
if (SchemaEx.isArrayType(ast)) {
|
|
20
|
+
const elementType = SchemaEx.getArrayElementType(ast);
|
|
29
21
|
if (elementType) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
if (Ref.isRefType(elementType)) {
|
|
23
|
+
const typename = SchemaEx.findAnnotation<ReferenceAnnotationValue>(
|
|
24
|
+
elementType,
|
|
25
|
+
ReferenceAnnotationId,
|
|
26
|
+
)?.typename;
|
|
27
|
+
return { ast: elementType, isArray: true, typename };
|
|
33
28
|
}
|
|
34
29
|
}
|
|
35
30
|
}
|
|
36
31
|
|
|
32
|
+
// Direct reference.
|
|
33
|
+
if (Ref.isRefType(ast)) {
|
|
34
|
+
const typename = SchemaEx.findAnnotation<ReferenceAnnotationValue>(ast, ReferenceAnnotationId)?.typename;
|
|
35
|
+
return { ast, isArray: false, typename };
|
|
36
|
+
}
|
|
37
|
+
|
|
37
38
|
return undefined;
|
|
38
39
|
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React, { type ComponentPropsWithoutRef, type PropsWithChildren } from 'react';
|
|
2
|
-
import { type ButtonProps, type Label, type ThemedClassName } from '@dxos/react-ui';
|
|
3
|
-
export type ControlPageProps = ThemedClassName<ComponentPropsWithoutRef<'div'>>;
|
|
4
|
-
export declare const ControlPage: ({ children, classNames, ...props }: ControlPageProps) => React.JSX.Element;
|
|
5
|
-
export type ControlSectionProps = PropsWithChildren<{
|
|
6
|
-
title: Label;
|
|
7
|
-
description?: Label;
|
|
8
|
-
}>;
|
|
9
|
-
export declare const ControlSection: ({ title, description, children }: ControlSectionProps) => React.JSX.Element;
|
|
10
|
-
export declare const ControlSectionHeading: ({ title, description }: Omit<ControlSectionProps, "children">) => React.JSX.Element;
|
|
11
|
-
export declare const ControlGroupButton: ({ classNames, ...props }: ButtonProps) => React.JSX.Element;
|
|
12
|
-
export type ControlGroupProps = ThemedClassName<PropsWithChildren<{}>>;
|
|
13
|
-
export declare const ControlGroup: ({ children, classNames }: ControlGroupProps) => React.JSX.Element;
|
|
14
|
-
export declare const ControlFrame: ({ children }: ControlGroupProps) => React.JSX.Element;
|
|
15
|
-
export type ControlItemProps = PropsWithChildren<{
|
|
16
|
-
title: Label;
|
|
17
|
-
description?: Label;
|
|
18
|
-
}>;
|
|
19
|
-
export declare const controlItemClasses = "pli-trimMd plb-trimMd container-max-width grid md:col-span-2 grid-cols-subgrid items-center gap-trimSm border border-separator rounded-md *:first:!mbs-0 *:last:!mbe-0";
|
|
20
|
-
export declare const ControlItem: ({ title, description, children }: ControlItemProps) => React.JSX.Element;
|
|
21
|
-
export declare const ControlItemInput: ({ title, description, children }: ControlItemProps) => React.JSX.Element;
|
|
22
|
-
export declare const ControlFrameItem: ({ title, description, children }: ControlItemProps) => React.JSX.Element;
|
|
23
|
-
//# sourceMappingURL=ControlSection.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ControlSection.d.ts","sourceRoot":"","sources":["../../../../../src/components/ControlSection/ControlSection.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAErF,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,KAAK,EACV,KAAK,eAAe,EAGrB,MAAM,gBAAgB,CAAC;AAKxB,MAAM,MAAM,gBAAgB,GAAG,eAAe,CAAC,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC;AAEhF,eAAO,MAAM,WAAW,GAAI,oCAAoC,gBAAgB,sBAM/E,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;IAClD,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB,CAAC,CAAC;AAEH,eAAO,MAAM,cAAc,GAAI,kCAAkC,mBAAmB,sBAOnF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,wBAAwB,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,sBAYlG,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,0BAA0B,WAAW,sBAEvE,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,eAAe,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC;AAEvE,eAAO,MAAM,YAAY,GAAI,0BAA0B,iBAAiB,sBAOvE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,cAAc,iBAAiB,sBAO3D,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;IAC/C,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB,CAAC,CAAC;AAEH,eAAO,MAAM,kBAAkB,2KAC2I,CAAC;AAK3K,eAAO,MAAM,WAAW,GAAI,kCAAkC,gBAAgB,sBAY7E,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,kCAAkC,gBAAgB,sBAoBlF,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,kCAAkC,gBAAgB,sBAUlF,CAAC"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import '@dxos-theme';
|
|
2
|
-
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
3
|
-
import { type ControlSectionProps } from './ControlSection';
|
|
4
|
-
declare const meta: Meta<ControlSectionProps>;
|
|
5
|
-
export default meta;
|
|
6
|
-
type Story = StoryObj<ControlSectionProps>;
|
|
7
|
-
export declare const Default: Story;
|
|
8
|
-
//# sourceMappingURL=ControlSection.stories.d.ts.map
|