@dxos/react-ui-form 0.9.0 → 0.9.1-staging.ee54ba693a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +2337 -1919
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2337 -1919
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/annotations.d.ts +47 -0
- package/dist/types/src/annotations.d.ts.map +1 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +3 -3
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +23 -110
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +48 -101
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.theme.d.ts +252 -0
- package/dist/types/src/components/Form/Form.theme.d.ts.map +1 -0
- package/dist/types/src/components/Form/Form.theme.test.d.ts +2 -0
- package/dist/types/src/components/Form/Form.theme.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormControls.d.ts +75 -0
- package/dist/types/src/components/Form/FormControls.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/FormField.d.ts +44 -0
- package/dist/types/src/components/Form/FormField/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/FormFieldHeader.d.ts +27 -0
- package/dist/types/src/components/Form/FormField/FormFieldHeader.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/FormRow.d.ts +85 -0
- package/dist/types/src/components/Form/FormField/FormRow.d.ts.map +1 -0
- package/dist/types/src/components/Form/{FormFieldComponent.stories.d.ts → FormField/FormRow.stories.d.ts} +6 -3
- package/dist/types/src/components/Form/FormField/FormRow.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/{fields → FormField/fields/ArrayField}/ArrayField.d.ts +2 -2
- package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/ArrayField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/ArrayField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.d.ts +12 -0
- package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/AsyncSelectField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/AutofillField/AutofillField.d.ts +12 -0
- package/dist/types/src/components/Form/FormField/fields/AutofillField/AutofillField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/AutofillField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/AutofillField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.d.ts +4 -0
- package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.d.ts.map +1 -0
- package/dist/types/src/components/{Settings/Settings.stories.d.ts → Form/FormField/fields/BooleanField/BooleanField.stories.d.ts} +1 -2
- package/dist/types/src/components/Form/FormField/fields/BooleanField/BooleanField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/BooleanField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/BooleanField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/ComboboxField/ComboboxField.d.ts +12 -0
- package/dist/types/src/components/Form/FormField/fields/ComboboxField/ComboboxField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/ComboboxField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/ComboboxField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/DateField/DateField.d.ts +4 -0
- package/dist/types/src/components/Form/FormField/fields/DateField/DateField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/DateField/DateField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/DateField/DateField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/DateField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/DateField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.d.ts +5 -0
- package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/GeoPointField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/GeoPointField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.d.ts +2 -2
- package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/MarkdownField/MarkdownField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/MarkdownField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/MarkdownField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.d.ts +15 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.test.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/NumberField.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/NumberField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.d.ts +4 -0
- package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/PasswordField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/PasswordField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/InlineRefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.d.ts +16 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.stories.d.ts +92 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/RefField.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/index.d.ts +3 -0
- package/dist/types/src/components/Form/FormField/fields/RefField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.d.ts +29 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/SelectField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/SelectField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.d.ts +5 -0
- package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectOptionField/SelectOptionField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/SelectOptionField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/SelectOptionField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.d.ts +4 -0
- package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/TextAreaField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/TextAreaField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/TextField/TextField.d.ts +4 -0
- package/dist/types/src/components/Form/FormField/fields/TextField/TextField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/TextField/TextField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/TextField/TextField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/TextField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/TextField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/{fields → FormField/fields/TupleField}/TupleField.d.ts +2 -2
- package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.stories.d.ts +88 -0
- package/dist/types/src/components/Form/FormField/fields/TupleField/TupleField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/fields/TupleField/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormField/fields/TupleField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/{fields → FormField/fields}/index.d.ts +4 -1
- package/dist/types/src/components/Form/FormField/fields/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/index.d.ts +6 -0
- package/dist/types/src/components/Form/FormField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/presentation.d.ts +22 -0
- package/dist/types/src/components/Form/FormField/presentation.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet/FormFieldSet.d.ts +30 -0
- package/dist/types/src/components/Form/FormFieldSet/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet/FormFieldSetContainer.d.ts +22 -0
- package/dist/types/src/components/Form/FormFieldSet/FormFieldSetContainer.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet/index.d.ts +3 -0
- package/dist/types/src/components/Form/FormFieldSet/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.d.ts +2 -0
- package/dist/types/src/components/Form/FormLayout/FormLayout.d.ts.map +1 -0
- package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.stories.d.ts +5 -5
- package/dist/types/src/components/Form/FormLayout/FormLayout.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormLayout/FormLayout.stories.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormLayout/FormLayout.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormLayout/index.d.ts +3 -0
- package/dist/types/src/components/Form/FormLayout/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormLayout/parser.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormLayout/parser.test.d.ts.map +1 -0
- package/dist/types/src/components/Form/index.d.ts +3 -5
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/meta-tags.d.ts +1 -12
- package/dist/types/src/components/Form/meta-tags.d.ts.map +1 -1
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +6 -23
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +2 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -1
- package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts +2 -2
- package/dist/types/src/components/RefEditor/RefEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +2 -2
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAsyncFieldEffect.d.ts +24 -0
- package/dist/types/src/hooks/useAsyncFieldEffect.d.ts.map +1 -0
- package/dist/types/src/hooks/useFormContext.d.ts +42 -0
- package/dist/types/src/hooks/useFormContext.d.ts.map +1 -0
- package/dist/types/src/hooks/useFormHandler.d.ts +9 -6
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/{components/testing.d.ts → testing/TestLayout.d.ts} +2 -2
- package/dist/types/src/testing/TestLayout.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +3 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/schema.d.ts +40 -0
- package/dist/types/src/testing/schema.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +161 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +1 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/omit.d.ts +12 -0
- package/dist/types/src/util/omit.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +43 -41
- package/src/annotations.ts +63 -0
- package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.test.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.tsx +10 -6
- package/src/components/Form/DESIGN.md +24 -0
- package/src/components/Form/Form.stories.tsx +260 -61
- package/src/components/Form/Form.theme.test.ts +33 -0
- package/src/components/Form/Form.theme.ts +86 -0
- package/src/components/Form/Form.tsx +30 -453
- package/src/components/Form/FormControls.tsx +377 -0
- package/src/components/Form/{FormField.tsx → FormField/FormField.tsx} +98 -71
- package/src/components/Form/FormField/FormFieldHeader.tsx +68 -0
- package/src/components/Form/FormField/FormRow.stories.tsx +97 -0
- package/src/components/Form/FormField/FormRow.tsx +298 -0
- package/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.tsx +119 -0
- package/src/components/Form/FormField/fields/ArrayField/ArrayField.tsx +328 -0
- package/src/components/Form/FormField/fields/ArrayField/index.ts +5 -0
- package/src/components/Form/FormField/fields/AsyncSelectField/AsyncSelectField.tsx +59 -0
- package/src/components/Form/FormField/fields/AsyncSelectField/index.ts +5 -0
- package/src/components/Form/FormField/fields/AutofillField/AutofillField.tsx +67 -0
- package/src/components/Form/FormField/fields/AutofillField/index.ts +5 -0
- package/src/components/Form/FormField/fields/BooleanField/BooleanField.stories.tsx +52 -0
- package/src/components/Form/FormField/fields/BooleanField/BooleanField.tsx +28 -0
- package/src/components/Form/FormField/fields/BooleanField/index.ts +5 -0
- package/src/components/Form/FormField/fields/ComboboxField/ComboboxField.tsx +104 -0
- package/src/components/Form/FormField/fields/ComboboxField/index.ts +5 -0
- package/src/components/Form/FormField/fields/DateField/DateField.stories.tsx +60 -0
- package/src/components/Form/{fields → FormField/fields/DateField}/DateField.tsx +12 -10
- package/src/components/Form/FormField/fields/DateField/index.ts +5 -0
- package/src/components/Form/FormField/fields/GeoPointField/GeoPointField.stories.tsx +53 -0
- package/src/components/Form/{fields → FormField/fields/GeoPointField}/GeoPointField.tsx +19 -21
- package/src/components/Form/FormField/fields/GeoPointField/index.ts +5 -0
- package/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.stories.tsx +8 -8
- package/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.tsx +41 -41
- package/src/components/Form/FormField/fields/MarkdownField/index.ts +5 -0
- package/src/components/Form/FormField/fields/NumberField/NumberField.stories.tsx +52 -0
- package/src/components/Form/FormField/fields/NumberField/NumberField.test.ts +29 -0
- package/src/components/Form/FormField/fields/NumberField/NumberField.tsx +140 -0
- package/src/components/Form/FormField/fields/NumberField/index.ts +5 -0
- package/src/components/Form/FormField/fields/PasswordField/PasswordField.stories.tsx +53 -0
- package/src/components/Form/FormField/fields/PasswordField/PasswordField.tsx +41 -0
- package/src/components/Form/FormField/fields/PasswordField/index.ts +5 -0
- package/src/components/Form/{fields → FormField/fields/RefField}/InlineRefField.tsx +21 -14
- package/src/components/Form/FormField/fields/RefField/RefField.stories.tsx +97 -0
- package/src/components/Form/{fields → FormField/fields/RefField}/RefField.test.ts +10 -1
- package/src/components/Form/{fields → FormField/fields/RefField}/RefField.tsx +55 -47
- package/src/components/Form/FormField/fields/RefField/index.ts +6 -0
- package/src/components/Form/FormField/fields/SelectField/SelectField.stories.tsx +53 -0
- package/src/components/Form/FormField/fields/SelectField/SelectField.tsx +136 -0
- package/src/components/Form/FormField/fields/SelectField/index.ts +5 -0
- package/src/components/Form/{fields → FormField/fields/SelectOptionField}/SelectOptionField.stories.tsx +5 -5
- package/src/components/Form/{fields → FormField/fields/SelectOptionField}/SelectOptionField.tsx +56 -54
- package/src/components/Form/FormField/fields/SelectOptionField/index.ts +5 -0
- package/src/components/Form/FormField/fields/TextAreaField/TextAreaField.stories.tsx +53 -0
- package/src/components/Form/{fields → FormField/fields/TextAreaField}/TextAreaField.tsx +6 -4
- package/src/components/Form/FormField/fields/TextAreaField/index.ts +5 -0
- package/src/components/Form/FormField/fields/TextField/TextField.stories.tsx +52 -0
- package/src/components/Form/{fields → FormField/fields/TextField}/TextField.tsx +6 -4
- package/src/components/Form/FormField/fields/TextField/index.ts +5 -0
- package/src/components/Form/FormField/fields/TupleField/TupleField.stories.tsx +55 -0
- package/src/components/Form/{fields → FormField/fields/TupleField}/TupleField.tsx +4 -2
- package/src/components/Form/FormField/fields/TupleField/index.ts +5 -0
- package/src/components/Form/{fields → FormField/fields}/index.ts +4 -1
- package/src/components/Form/FormField/index.ts +10 -0
- package/src/components/Form/FormField/presentation.tsx +32 -0
- package/src/components/Form/{FormFieldSet.tsx → FormFieldSet/FormFieldSet.tsx} +53 -90
- package/src/components/Form/FormFieldSet/FormFieldSetContainer.tsx +93 -0
- package/src/components/Form/FormFieldSet/index.ts +6 -0
- package/src/components/Form/{Layout → FormLayout}/FormLayout.stories.tsx +11 -9
- package/src/components/Form/{Layout → FormLayout}/FormLayout.tsx +29 -18
- package/src/components/Form/FormLayout/index.ts +6 -0
- package/src/components/Form/index.ts +3 -5
- package/src/components/Form/meta-tags.test.ts +1 -1
- package/src/components/Form/meta-tags.ts +1 -1
- package/src/components/ObjectForm/ObjectForm.tsx +15 -14
- package/src/components/ObjectPicker/ObjectPicker.tsx +20 -27
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +2 -2
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +1 -1
- package/src/components/ObjectProperties/ObjectProperties.tsx +13 -8
- package/src/components/RefEditor/RefEditor.stories.tsx +2 -3
- package/src/components/RefEditor/RefEditor.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +6 -7
- package/src/components/ViewEditor/ViewEditor.test.tsx +9 -3
- package/src/components/ViewEditor/ViewEditor.tsx +98 -128
- package/src/components/index.ts +0 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAsyncFieldEffect.ts +81 -0
- package/src/hooks/useFormContext.ts +89 -0
- package/src/hooks/useFormHandler.ts +12 -10
- package/src/index.ts +2 -0
- package/src/{components/testing.tsx → testing/TestLayout.tsx} +16 -13
- package/src/testing/index.ts +7 -0
- package/src/testing/schema.ts +59 -0
- package/src/types.ts +186 -0
- package/src/util/index.ts +1 -1
- package/src/util/omit.ts +61 -0
- package/src/util/properties.test.ts +110 -28
- package/src/util/refs.ts +1 -1
- package/src/vitest-setup.ts +40 -0
- package/dist/types/src/components/Form/FormField.d.ts +0 -52
- package/dist/types/src/components/Form/FormField.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +0 -98
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +0 -26
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormTooltipsContext.d.ts +0 -19
- package/dist/types/src/components/Form/FormTooltipsContext.d.ts.map +0 -1
- package/dist/types/src/components/Form/Layout/FormLayout.d.ts.map +0 -1
- package/dist/types/src/components/Form/Layout/FormLayout.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/Layout/FormLayout.stories.test.d.ts.map +0 -1
- package/dist/types/src/components/Form/Layout/FormLayout.test.d.ts.map +0 -1
- package/dist/types/src/components/Form/Layout/annotation.d.ts +0 -30
- package/dist/types/src/components/Form/Layout/annotation.d.ts.map +0 -1
- package/dist/types/src/components/Form/Layout/index.d.ts +0 -4
- package/dist/types/src/components/Form/Layout/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/Layout/parser.d.ts.map +0 -1
- package/dist/types/src/components/Form/Layout/parser.test.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/BooleanField.d.ts +0 -4
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/DateField.d.ts +0 -4
- package/dist/types/src/components/Form/fields/DateField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +0 -5
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/InlineRefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/NumberField.d.ts +0 -4
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +0 -34
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/RefField.test.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/SelectField.d.ts +0 -13
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +0 -5
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts +0 -4
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts +0 -4
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +0 -1
- package/dist/types/src/components/Form/fields/index.d.ts.map +0 -1
- package/dist/types/src/components/Settings/Settings.d.ts +0 -51
- package/dist/types/src/components/Settings/Settings.d.ts.map +0 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +0 -1
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +0 -43
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +0 -1
- package/dist/types/src/components/Settings/SettingsItem.d.ts +0 -16
- package/dist/types/src/components/Settings/SettingsItem.d.ts.map +0 -1
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts +0 -18
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts.map +0 -1
- package/dist/types/src/components/Settings/index.d.ts +0 -4
- package/dist/types/src/components/Settings/index.d.ts.map +0 -1
- package/dist/types/src/components/testing.d.ts.map +0 -1
- package/dist/types/src/util/field-type.d.ts +0 -19
- package/dist/types/src/util/field-type.d.ts.map +0 -1
- package/src/components/Form/AUDIT.md +0 -17
- package/src/components/Form/FormFieldComponent.stories.tsx +0 -65
- package/src/components/Form/FormFieldComponent.tsx +0 -253
- package/src/components/Form/FormTooltipsContext.ts +0 -25
- package/src/components/Form/Layout/annotation.ts +0 -38
- package/src/components/Form/Layout/index.ts +0 -7
- package/src/components/Form/README.md +0 -3
- package/src/components/Form/fields/ArrayField.tsx +0 -173
- package/src/components/Form/fields/BooleanField.tsx +0 -22
- package/src/components/Form/fields/NumberField.tsx +0 -76
- package/src/components/Form/fields/SelectField.tsx +0 -77
- package/src/components/Settings/AUDIT.md +0 -51
- package/src/components/Settings/Settings.stories.tsx +0 -104
- package/src/components/Settings/Settings.tsx +0 -81
- package/src/components/Settings/SettingsFieldSet.tsx +0 -257
- package/src/components/Settings/SettingsItem.tsx +0 -58
- package/src/components/Settings/SettingsSelectField.tsx +0 -59
- package/src/components/Settings/index.ts +0 -7
- package/src/util/field-type.ts +0 -60
- /package/dist/types/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.stories.d.ts +0 -0
- /package/dist/types/src/components/Form/{fields → FormField/fields/RefField}/InlineRefField.d.ts +0 -0
- /package/dist/types/src/components/Form/{fields → FormField/fields/RefField}/RefField.test.d.ts +0 -0
- /package/dist/types/src/components/Form/{fields → FormField/fields/SelectOptionField}/SelectOptionField.stories.d.ts +0 -0
- /package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.stories.test.d.ts +0 -0
- /package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.test.d.ts +0 -0
- /package/dist/types/src/components/Form/{Layout → FormLayout}/parser.d.ts +0 -0
- /package/dist/types/src/components/Form/{Layout → FormLayout}/parser.test.d.ts +0 -0
- /package/src/components/Form/{Layout → FormLayout}/FormLayout.stories.test.tsx +0 -0
- /package/src/components/Form/{Layout → FormLayout}/FormLayout.test.ts +0 -0
- /package/src/components/Form/{Layout → FormLayout}/parser.test.ts +0 -0
- /package/src/components/Form/{Layout → FormLayout}/parser.ts +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Option from 'effect/Option';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { Input, type TextInputProps } from '@dxos/react-ui';
|
|
10
|
+
import { safeParseFloat } from '@dxos/util';
|
|
11
|
+
|
|
12
|
+
import { type FormFieldRendererProps } from '#types';
|
|
13
|
+
|
|
14
|
+
import { FormRow } from '../../FormRow';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extracts numeric constraints (`minimum`/`maximum` bounds and whether the value must be an integer)
|
|
18
|
+
* by walking the chain of Refinement ASTs and reading each one's JSON schema annotation (e.g. produced
|
|
19
|
+
* by `Schema.between` / `Schema.int`). Returns empty constraints when none are declared.
|
|
20
|
+
*/
|
|
21
|
+
export const getNumericConstraints = (ast: SchemaAST.AST): { min?: number; max?: number; integer: boolean } => {
|
|
22
|
+
let node: SchemaAST.AST | undefined = ast;
|
|
23
|
+
let min: number | undefined;
|
|
24
|
+
let max: number | undefined;
|
|
25
|
+
let integer = false;
|
|
26
|
+
// Nested refinements (e.g. `Schema.int()` + `Schema.between()`) each carry their own JSON schema
|
|
27
|
+
// fragment, so accumulate across the chain rather than reading only the outermost node.
|
|
28
|
+
while (node && SchemaAST.isRefinement(node)) {
|
|
29
|
+
const jsonSchema = Option.getOrUndefined(SchemaAST.getJSONSchemaAnnotation(node));
|
|
30
|
+
if (jsonSchema != null) {
|
|
31
|
+
// Stacked refinements intersect: keep the strictest bound (largest min, smallest max).
|
|
32
|
+
if ('minimum' in jsonSchema && typeof jsonSchema.minimum === 'number') {
|
|
33
|
+
min = min === undefined ? jsonSchema.minimum : Math.max(min, jsonSchema.minimum);
|
|
34
|
+
}
|
|
35
|
+
if ('maximum' in jsonSchema && typeof jsonSchema.maximum === 'number') {
|
|
36
|
+
max = max === undefined ? jsonSchema.maximum : Math.min(max, jsonSchema.maximum);
|
|
37
|
+
}
|
|
38
|
+
if (
|
|
39
|
+
('type' in jsonSchema && jsonSchema.type === 'integer') ||
|
|
40
|
+
('multipleOf' in jsonSchema && jsonSchema.multipleOf === 1)
|
|
41
|
+
) {
|
|
42
|
+
integer = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
node = node.from;
|
|
46
|
+
}
|
|
47
|
+
return { min, max, integer };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const NumberField = ({
|
|
51
|
+
type,
|
|
52
|
+
readonly,
|
|
53
|
+
placeholder,
|
|
54
|
+
getValue,
|
|
55
|
+
onValueChange,
|
|
56
|
+
onBlur,
|
|
57
|
+
...props
|
|
58
|
+
}: FormFieldRendererProps<number>) => {
|
|
59
|
+
const { min, max, integer } = getNumericConstraints(type);
|
|
60
|
+
|
|
61
|
+
// Clamp to the declared bounds (and round when integer) so the committed value respects the schema's
|
|
62
|
+
// natural limits — `<input type="number">`'s min/max only constrain the spinner, not typed input.
|
|
63
|
+
const clamp = useCallback(
|
|
64
|
+
(n: number) => {
|
|
65
|
+
let value = integer ? Math.round(n) : n;
|
|
66
|
+
if (min !== undefined) {
|
|
67
|
+
value = Math.max(min, value);
|
|
68
|
+
}
|
|
69
|
+
if (max !== undefined) {
|
|
70
|
+
value = Math.min(max, value);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
},
|
|
74
|
+
[min, max, integer],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Track raw string input so the user can clear the field before typing a new number.
|
|
78
|
+
// We only commit to onValueChange when the raw string parses to a valid number.
|
|
79
|
+
const [raw, setRaw] = useState<string>(() => {
|
|
80
|
+
const v = getValue();
|
|
81
|
+
return v !== undefined ? String(v) : '';
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Sync display when an external change updates the committed value (e.g. reactive form
|
|
85
|
+
// calculations). Only overwrite raw when the external value differs from what raw parses
|
|
86
|
+
// to, preserving partial edits like "1." which correctly parse to 1.
|
|
87
|
+
const externalValue = getValue();
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (externalValue !== safeParseFloat(raw)) {
|
|
90
|
+
setRaw(externalValue !== undefined ? String(externalValue) : '');
|
|
91
|
+
}
|
|
92
|
+
}, [externalValue]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
93
|
+
|
|
94
|
+
const handleChange = useCallback<NonNullable<TextInputProps['onChange']>>(
|
|
95
|
+
(event) => {
|
|
96
|
+
const value = event.target.value;
|
|
97
|
+
setRaw(value);
|
|
98
|
+
const parsed = safeParseFloat(value);
|
|
99
|
+
if (parsed !== undefined) {
|
|
100
|
+
onValueChange(type, clamp(parsed));
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
[type, onValueChange, clamp],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const handleBlur = useCallback(
|
|
107
|
+
(event: React.FocusEvent<HTMLElement>) => {
|
|
108
|
+
const parsed = safeParseFloat(raw);
|
|
109
|
+
if (parsed === undefined) {
|
|
110
|
+
// The field was left empty or invalid; reset to the last committed value.
|
|
111
|
+
const committed = getValue();
|
|
112
|
+
setRaw(committed !== undefined ? String(committed) : '');
|
|
113
|
+
} else {
|
|
114
|
+
// Reflect the clamped value even when it equals the previously committed one (so `externalValue`
|
|
115
|
+
// doesn't change), otherwise the input keeps showing the out-of-range text the user typed.
|
|
116
|
+
setRaw(String(clamp(parsed)));
|
|
117
|
+
}
|
|
118
|
+
onBlur(event);
|
|
119
|
+
},
|
|
120
|
+
[raw, getValue, onBlur, clamp],
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<FormRow<number> readonly={readonly} getValue={getValue} {...props}>
|
|
125
|
+
{() => (
|
|
126
|
+
<Input.TextInput
|
|
127
|
+
type='number'
|
|
128
|
+
disabled={!!readonly}
|
|
129
|
+
placeholder={placeholder}
|
|
130
|
+
value={raw}
|
|
131
|
+
min={min}
|
|
132
|
+
max={max}
|
|
133
|
+
step={integer ? 1 : undefined}
|
|
134
|
+
onChange={handleChange}
|
|
135
|
+
onBlur={handleBlur}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
</FormRow>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
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 { Format } from '@dxos/echo';
|
|
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
|
+
|
|
17
|
+
const schema = Schema.Struct({
|
|
18
|
+
name: Schema.String.annotations({ title: 'Name' }).pipe(Format.FormatAnnotation.set(Format.TypeFormat.Password)),
|
|
19
|
+
}).pipe(Schema.mutable);
|
|
20
|
+
|
|
21
|
+
type Values = Schema.Schema.Type<typeof schema>;
|
|
22
|
+
|
|
23
|
+
const DefaultStory = () => {
|
|
24
|
+
const [values, setValues] = useState<Values>({ name: 'Alice' });
|
|
25
|
+
return (
|
|
26
|
+
<TestLayout json={values}>
|
|
27
|
+
<Form.Root
|
|
28
|
+
schema={schema}
|
|
29
|
+
values={values}
|
|
30
|
+
onValuesChanged={(values) => setValues((prev) => ({ ...prev, ...values }))}
|
|
31
|
+
>
|
|
32
|
+
<Form.Viewport>
|
|
33
|
+
<Form.Content>
|
|
34
|
+
<Form.FieldSet />
|
|
35
|
+
</Form.Content>
|
|
36
|
+
</Form.Viewport>
|
|
37
|
+
</Form.Root>
|
|
38
|
+
</TestLayout>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const meta = {
|
|
43
|
+
title: 'ui/react-ui-form/FormField/PasswordField',
|
|
44
|
+
render: DefaultStory,
|
|
45
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
46
|
+
parameters: { layout: 'fullscreen', translations },
|
|
47
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
|
|
51
|
+
type Story = StoryObj<typeof meta>;
|
|
52
|
+
|
|
53
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,41 @@
|
|
|
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 FormFieldRendererProps } from '#types';
|
|
10
|
+
|
|
11
|
+
import { FormRow } from '../../FormRow';
|
|
12
|
+
|
|
13
|
+
export const PasswordField = ({
|
|
14
|
+
type,
|
|
15
|
+
readonly,
|
|
16
|
+
placeholder,
|
|
17
|
+
onBlur,
|
|
18
|
+
onValueChange,
|
|
19
|
+
...props
|
|
20
|
+
}: FormFieldRendererProps<string>) => {
|
|
21
|
+
const handleChange = useCallback<NonNullable<TextInputProps['onChange']>>(
|
|
22
|
+
(event) => onValueChange(type, event.target.value),
|
|
23
|
+
[type, onValueChange],
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<FormRow<string> readonly={readonly} {...props}>
|
|
28
|
+
{({ value = '' }) => (
|
|
29
|
+
<Input.TextInput
|
|
30
|
+
type='password'
|
|
31
|
+
noAutoFill
|
|
32
|
+
disabled={!!readonly}
|
|
33
|
+
placeholder={placeholder}
|
|
34
|
+
value={value}
|
|
35
|
+
onBlur={onBlur}
|
|
36
|
+
onChange={handleChange}
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
</FormRow>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -10,12 +10,14 @@ import { useType as defaultUseType } from '@dxos/echo-react';
|
|
|
10
10
|
import { ReferenceAnnotationId, type ReferenceAnnotationValue } from '@dxos/echo/Annotation';
|
|
11
11
|
import { SchemaEx } from '@dxos/effect';
|
|
12
12
|
import { DXN, URI } from '@dxos/keys';
|
|
13
|
-
import {
|
|
13
|
+
import { IconButton, Input, useTranslation } from '@dxos/react-ui';
|
|
14
14
|
|
|
15
15
|
import { translationKey } from '#translations';
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { omitId } from '../../../../../util';
|
|
18
|
+
import { FormContent, FormFieldSetController, FormRoot } from '../../../FormControls';
|
|
19
|
+
import { FormFieldLabel } from '../../FormRow';
|
|
20
|
+
import { presentationFor } from '../../presentation';
|
|
19
21
|
import { type RefFieldProps } from './RefField';
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -33,7 +35,8 @@ export const InlineRefField = (props: RefFieldProps) => {
|
|
|
33
35
|
readonly,
|
|
34
36
|
label,
|
|
35
37
|
jsonPath,
|
|
36
|
-
|
|
38
|
+
presentation,
|
|
39
|
+
required,
|
|
37
40
|
db,
|
|
38
41
|
getValue,
|
|
39
42
|
onValueChange,
|
|
@@ -41,6 +44,7 @@ export const InlineRefField = (props: RefFieldProps) => {
|
|
|
41
44
|
useType = defaultUseType,
|
|
42
45
|
} = props;
|
|
43
46
|
const { t } = useTranslation(translationKey);
|
|
47
|
+
const resolved = presentationFor(presentation);
|
|
44
48
|
|
|
45
49
|
const reference = getValue() as Ref.Ref<any> | undefined;
|
|
46
50
|
const typename = useMemo(
|
|
@@ -66,16 +70,19 @@ export const InlineRefField = (props: RefFieldProps) => {
|
|
|
66
70
|
|
|
67
71
|
return (
|
|
68
72
|
<Input.Root>
|
|
69
|
-
{
|
|
73
|
+
{resolved.showLabel && <FormFieldLabel readonly={readonly} required={required} label={label} path={jsonPath} />}
|
|
70
74
|
{reference ? (
|
|
71
75
|
<InlineForm reference={reference} db={db} readonly={readonly} useType={useType} />
|
|
72
76
|
) : (
|
|
73
77
|
!readonly &&
|
|
74
78
|
onCreate && (
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
<IconButton
|
|
80
|
+
classNames='w-full gap-form-gap'
|
|
81
|
+
disabled={!createType || !db}
|
|
82
|
+
icon='ph--plus--regular'
|
|
83
|
+
label={label || t('ref-field.placeholder')}
|
|
84
|
+
onClick={() => void handleCreate()}
|
|
85
|
+
/>
|
|
79
86
|
)
|
|
80
87
|
)}
|
|
81
88
|
</Input.Root>
|
|
@@ -129,10 +136,10 @@ const InlineForm = ({ reference, db, readonly, useType = defaultUseType }: Inlin
|
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
return (
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
<
|
|
135
|
-
</
|
|
136
|
-
</
|
|
139
|
+
<FormRoot db={db} schema={formSchema} defaultValues={defaultValues as any} onValuesChanged={handleChange}>
|
|
140
|
+
<FormContent>
|
|
141
|
+
<FormFieldSetController collapsible readonly={readonly} />
|
|
142
|
+
</FormContent>
|
|
143
|
+
</FormRoot>
|
|
137
144
|
);
|
|
138
145
|
};
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { Annotation, Obj, Ref } from '@dxos/echo';
|
|
10
|
+
import { useSpaces } from '@dxos/react-client/echo';
|
|
11
|
+
import { withClientProvider } from '@dxos/react-client/testing';
|
|
12
|
+
import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
13
|
+
|
|
14
|
+
import { translations } from '#translations';
|
|
15
|
+
|
|
16
|
+
import { Organization, TestLayout } from '../../../../../testing';
|
|
17
|
+
import { Form } from '../../../Form';
|
|
18
|
+
|
|
19
|
+
// Picker: select an existing Organization.
|
|
20
|
+
const RefSchema = Schema.Struct({
|
|
21
|
+
employer: Ref.Ref(Organization).annotations({ title: 'Employer' }),
|
|
22
|
+
}).pipe(Schema.mutable);
|
|
23
|
+
|
|
24
|
+
// Inline: edit the referenced Organization's fields in a nested form.
|
|
25
|
+
const InlineSchema = Schema.Struct({
|
|
26
|
+
employer: Ref.Ref(Organization).pipe(
|
|
27
|
+
Schema.annotations({ title: 'Employer' }),
|
|
28
|
+
Annotation.FormInlineAnnotation.set(true),
|
|
29
|
+
),
|
|
30
|
+
}).pipe(Schema.mutable);
|
|
31
|
+
|
|
32
|
+
const RefStory = ({ schema }: { schema: Schema.Schema<any> }) => {
|
|
33
|
+
const spaces = useSpaces();
|
|
34
|
+
const space = spaces[0];
|
|
35
|
+
const [values, setValues] = useState<Record<string, unknown>>({});
|
|
36
|
+
if (!space) {
|
|
37
|
+
return <Loading />;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<TestLayout json={values}>
|
|
42
|
+
<Form.Root
|
|
43
|
+
schema={schema}
|
|
44
|
+
values={values}
|
|
45
|
+
db={space.db}
|
|
46
|
+
onValuesChanged={(values) => setValues((prev) => ({ ...prev, ...values }))}
|
|
47
|
+
// `name` is required; the inline create starts from `{}`, so seed a default the form can then edit.
|
|
48
|
+
onCreate={(_type, props: any) => space.db.add(Obj.make(Organization, { name: 'New organization', ...props }))}
|
|
49
|
+
>
|
|
50
|
+
<Form.Viewport>
|
|
51
|
+
<Form.Content>
|
|
52
|
+
<Form.FieldSet />
|
|
53
|
+
</Form.Content>
|
|
54
|
+
</Form.Viewport>
|
|
55
|
+
</Form.Root>
|
|
56
|
+
</TestLayout>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const meta = {
|
|
61
|
+
title: 'ui/react-ui-form/FormField/RefField',
|
|
62
|
+
render: RefStory,
|
|
63
|
+
decorators: [
|
|
64
|
+
withTheme(),
|
|
65
|
+
withLayout({ layout: 'fullscreen' }),
|
|
66
|
+
withClientProvider({
|
|
67
|
+
createIdentity: true,
|
|
68
|
+
createSpace: true,
|
|
69
|
+
types: [Organization],
|
|
70
|
+
onCreateSpace: ({ space }) => {
|
|
71
|
+
Array.from({ length: 8 }).forEach((_, i) =>
|
|
72
|
+
space.db.add(Obj.make(Organization, { name: `Organization ${i}` })),
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
],
|
|
77
|
+
parameters: {
|
|
78
|
+
layout: 'fullscreen',
|
|
79
|
+
translations,
|
|
80
|
+
},
|
|
81
|
+
} satisfies Meta<typeof RefStory>;
|
|
82
|
+
|
|
83
|
+
export default meta;
|
|
84
|
+
|
|
85
|
+
type Story = StoryObj<typeof meta>;
|
|
86
|
+
|
|
87
|
+
export const Default: Story = {
|
|
88
|
+
args: {
|
|
89
|
+
schema: RefSchema,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const Inline: Story = {
|
|
94
|
+
args: {
|
|
95
|
+
schema: InlineSchema,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, test } from 'vitest';
|
|
6
6
|
|
|
7
|
-
import { type RefOption } from '
|
|
7
|
+
import { type RefOption } from '#types';
|
|
8
|
+
|
|
8
9
|
import { findRefOption } from './RefField';
|
|
9
10
|
|
|
10
11
|
// Sample qualified self URIs as produced by an in-space object (Entity.getURI / `echo://<space>/<id>`).
|
|
@@ -28,6 +29,14 @@ describe('findRefOption', () => {
|
|
|
28
29
|
expect(findRefOption({ '/': LOCAL }, options)?.label).toBe('Target');
|
|
29
30
|
});
|
|
30
31
|
|
|
32
|
+
test('matches a keyed-entity ref by direct URI equality', ({ expect }) => {
|
|
33
|
+
// Keyed/registry entities (skills, operations) are referenced by a named DXN with no parseable
|
|
34
|
+
// EID; the option id and the ref uri are the same named URI, so they match by direct equality.
|
|
35
|
+
const named = 'dxn:type:example.com/skill/research';
|
|
36
|
+
const keyedOptions: RefOption[] = [...options, { id: named, label: 'Research' }];
|
|
37
|
+
expect(findRefOption({ '/': named }, keyedOptions)?.label).toBe('Research');
|
|
38
|
+
});
|
|
39
|
+
|
|
31
40
|
test('returns undefined for a non-ref value', ({ expect }) => {
|
|
32
41
|
expect(findRefOption('not-a-ref', options)).toBeUndefined();
|
|
33
42
|
expect(findRefOption(undefined, options)).toBeUndefined();
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import '@dxos/lit-ui/dx-tag-picker.pcss';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Entity, Filter, Obj, Query, Ref, Scope, Type } from '@dxos/echo';
|
|
9
|
+
import { useType as defaultUseType, useQuery } from '@dxos/echo-react';
|
|
10
10
|
import { ANY_OBJECT_TYPENAME, ReferenceAnnotationId, type ReferenceAnnotationValue } from '@dxos/echo/internal';
|
|
11
11
|
import { SchemaEx } from '@dxos/effect';
|
|
12
12
|
import { DXN, EID, URI } from '@dxos/keys';
|
|
@@ -15,10 +15,12 @@ import { Button, Icon, Input, useTranslation } from '@dxos/react-ui';
|
|
|
15
15
|
import { ParentLabelAnnotationId } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { translationKey } from '#translations';
|
|
18
|
+
import { type CreateOptions, type FormFieldRendererProps, type RefFieldDataProps, type RefOption } from '#types';
|
|
18
19
|
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
20
|
+
import { omitHiddenFormFields, omitId } from '../../../../../util';
|
|
21
|
+
import { ObjectPicker } from '../../../../ObjectPicker';
|
|
22
|
+
import { FormFieldLabel } from '../../FormRow';
|
|
23
|
+
import { presentationFor } from '../../presentation';
|
|
22
24
|
|
|
23
25
|
// TODO(burdon): Factor out.
|
|
24
26
|
const isRefSnapshot = (val: any): val is { '/': string } => {
|
|
@@ -39,7 +41,14 @@ export const findRefOption = (value: unknown, options: RefOption[]): RefOption |
|
|
|
39
41
|
if (!isRef && !isRefSnapshot(value)) {
|
|
40
42
|
return undefined;
|
|
41
43
|
}
|
|
42
|
-
const
|
|
44
|
+
const valueUri = isRef ? value.uri : value['/'];
|
|
45
|
+
// Keyed/registry entities (skills, operations) are referenced by a named DXN rather than an
|
|
46
|
+
// entity-id, so they carry no parseable EID; match those by direct URI equality against the option id.
|
|
47
|
+
const directMatch = options.find((option) => option.id === valueUri);
|
|
48
|
+
if (directMatch) {
|
|
49
|
+
return directMatch;
|
|
50
|
+
}
|
|
51
|
+
const valueEid = EID.tryParse(valueUri);
|
|
43
52
|
if (!valueEid) {
|
|
44
53
|
return undefined;
|
|
45
54
|
}
|
|
@@ -85,31 +94,11 @@ const defaultUseResults: NonNullable<RefFieldProps['useResults']> = (db, typenam
|
|
|
85
94
|
: typename === ANY_OBJECT_TYPENAME
|
|
86
95
|
? // Untyped refs show space objects only; the registry is too broad for "any".
|
|
87
96
|
Query.select(Filter.everything())
|
|
88
|
-
: // Include registry scope so keyed entities (
|
|
97
|
+
: // Include registry scope so keyed entities (skills, operations) appear as options.
|
|
89
98
|
Query.select(Filter.type(DXN.make(typename))).from(Scope.space(), Scope.registry()),
|
|
90
99
|
);
|
|
91
100
|
|
|
92
|
-
export type RefFieldProps =
|
|
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
|
-
};
|
|
101
|
+
export type RefFieldProps = FormFieldRendererProps & RefFieldDataProps & CreateOptions;
|
|
113
102
|
|
|
114
103
|
export const RefField = (props: RefFieldProps) => {
|
|
115
104
|
const {
|
|
@@ -118,7 +107,8 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
118
107
|
label,
|
|
119
108
|
jsonPath,
|
|
120
109
|
placeholder,
|
|
121
|
-
|
|
110
|
+
presentation,
|
|
111
|
+
required,
|
|
122
112
|
getStatus,
|
|
123
113
|
getValue,
|
|
124
114
|
createOptionLabel,
|
|
@@ -130,10 +120,12 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
130
120
|
useResults = defaultUseResults,
|
|
131
121
|
getOptions = defaultGetOptions,
|
|
132
122
|
onCreate,
|
|
123
|
+
resolveCreateEntry,
|
|
133
124
|
onValueChange,
|
|
134
125
|
} = props;
|
|
135
126
|
const { t } = useTranslation(translationKey);
|
|
136
127
|
const { status, error } = getStatus();
|
|
128
|
+
const resolved = presentationFor(presentation);
|
|
137
129
|
|
|
138
130
|
const typename = useMemo(
|
|
139
131
|
() => (type ? SchemaEx.findAnnotation<ReferenceAnnotationValue>(type, ReferenceAnnotationId)?.typename : undefined),
|
|
@@ -164,24 +156,31 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
164
156
|
const selectedIds = useMemo(() => (item ? [item.id] : []), [item]);
|
|
165
157
|
const createSchema = useType(db, typename && typename !== ANY_OBJECT_TYPENAME ? DXN.make(typename) : undefined);
|
|
166
158
|
|
|
159
|
+
// Per-typename override supplied by the caller (e.g. SpaceCapabilities.CreateObjectEntry).
|
|
160
|
+
const createEntry = typename ? resolveCreateEntry?.(typename) : undefined;
|
|
161
|
+
|
|
167
162
|
// Lift the popover open state so we can dismiss it after a successful create.
|
|
168
163
|
const [open, setOpen] = useState(false);
|
|
169
164
|
|
|
170
165
|
const handleCreate = useCallback(
|
|
171
166
|
async (values: any) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
167
|
+
// Wire the newly-created object into this slot's form value. ArrayField
|
|
168
|
+
// owns the array; this RefField represents a single slot, so writing a
|
|
169
|
+
// Ref via `onValueChange` populates that slot.
|
|
170
|
+
if (createEntry?.createObject && db) {
|
|
171
|
+
const newObject = await createEntry.createObject(values, db);
|
|
172
|
+
if (newObject) {
|
|
173
|
+
onValueChange(type, Ref.make(newObject));
|
|
174
|
+
}
|
|
175
|
+
} else if (createSchema && onCreate) {
|
|
176
|
+
const newObject = await onCreate(createSchema, values);
|
|
177
|
+
if (newObject) {
|
|
178
|
+
onValueChange(type, Ref.make(newObject));
|
|
179
|
+
}
|
|
181
180
|
}
|
|
182
181
|
setOpen(false);
|
|
183
182
|
},
|
|
184
|
-
[createSchema, onCreate, type, onValueChange],
|
|
183
|
+
[createEntry, db, createSchema, onCreate, type, onValueChange],
|
|
185
184
|
);
|
|
186
185
|
|
|
187
186
|
const handleUpdate = useCallback(
|
|
@@ -204,13 +203,15 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
204
203
|
[item, handleUpdate],
|
|
205
204
|
);
|
|
206
205
|
|
|
207
|
-
if (!typename || ((readonly ||
|
|
206
|
+
if (!typename || ((readonly || resolved.isStatic) && !item)) {
|
|
208
207
|
return null;
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
return (
|
|
212
211
|
<Input.Root validationValence={status}>
|
|
213
|
-
{
|
|
212
|
+
{resolved.showLabel && (
|
|
213
|
+
<FormFieldLabel error={error} readonly={readonly} required={required} label={label} path={jsonPath} />
|
|
214
|
+
)}
|
|
214
215
|
<div>
|
|
215
216
|
{readonly ? (
|
|
216
217
|
!item ? (
|
|
@@ -245,22 +246,29 @@ export const RefField = (props: RefFieldProps) => {
|
|
|
245
246
|
classNames='dx-card-popover-width'
|
|
246
247
|
options={options}
|
|
247
248
|
selectedIds={selectedIds}
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
// such as backing-object refs
|
|
251
|
-
|
|
249
|
+
// Prefer the plugin-registered inputSchema; fall back to stripping hidden fields
|
|
250
|
+
// (`FormInputAnnotation.set(false)`) from the raw ECHO type schema so the form's
|
|
251
|
+
// validator doesn't reject required-but-hidden fields such as backing-object refs
|
|
252
|
+
// supplied by a `FactoryAnnotation`.
|
|
253
|
+
createSchema={
|
|
254
|
+
createEntry?.inputSchema ??
|
|
255
|
+
(createSchema && omitHiddenFormFields(omitId(Type.getSchema(createSchema))))
|
|
256
|
+
}
|
|
252
257
|
createOptionLabel={createOptionLabel}
|
|
253
258
|
createOptionIcon={createOptionIcon}
|
|
254
259
|
createInitialValuePath={createInitialValuePath}
|
|
255
260
|
createFieldMap={createFieldMap}
|
|
256
|
-
|
|
261
|
+
// Offer inline create when the caller wired a handler OR a plugin-registered
|
|
262
|
+
// createObject override is available. A resolvable `createSchema` alone is not
|
|
263
|
+
// enough (e.g. operation refs, whose objects can't be created ad hoc).
|
|
264
|
+
onCreate={onCreate || createEntry?.createObject ? handleCreate : undefined}
|
|
257
265
|
onSelect={handleSelect}
|
|
258
266
|
/>
|
|
259
267
|
</ObjectPicker.Portal>
|
|
260
268
|
</ObjectPicker.Root>
|
|
261
269
|
)}
|
|
262
270
|
</div>
|
|
263
|
-
{
|
|
271
|
+
{resolved.showError && <Input.DescriptionAndValidation>{error}</Input.DescriptionAndValidation>}
|
|
264
272
|
</Input.Root>
|
|
265
273
|
);
|
|
266
274
|
};
|