@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,377 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type PropsWithChildren, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type AnyProperties } from '@dxos/echo/internal';
|
|
8
|
+
import {
|
|
9
|
+
Column,
|
|
10
|
+
type ColumnRootProps,
|
|
11
|
+
IconButton,
|
|
12
|
+
type IconButtonProps,
|
|
13
|
+
Input,
|
|
14
|
+
ScrollArea,
|
|
15
|
+
type ThemedClassName,
|
|
16
|
+
composable,
|
|
17
|
+
composableProps,
|
|
18
|
+
useMergeRefs,
|
|
19
|
+
useTranslation,
|
|
20
|
+
withColumn,
|
|
21
|
+
} from '@dxos/react-ui';
|
|
22
|
+
import { MarkdownView } from '@dxos/react-ui-markdown';
|
|
23
|
+
import { mx } from '@dxos/ui-theme';
|
|
24
|
+
import { type Merge } from '@dxos/util';
|
|
25
|
+
|
|
26
|
+
import { translationKey } from '#translations';
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
FormContextProvider,
|
|
30
|
+
type FormContextValue,
|
|
31
|
+
type FormHandlerProps,
|
|
32
|
+
type FormUpdateMeta,
|
|
33
|
+
useFormContext,
|
|
34
|
+
useFormHandler,
|
|
35
|
+
useKeyHandler,
|
|
36
|
+
} from '../../hooks';
|
|
37
|
+
import { formTheme } from './Form.theme';
|
|
38
|
+
import { FormFieldSet, type FormFieldSetProps as NaturalFormFieldSetProps } from './FormFieldSet';
|
|
39
|
+
import { FormLayout, type FormLayoutProps as NaturalFormLayoutProps } from './FormLayout';
|
|
40
|
+
|
|
41
|
+
//
|
|
42
|
+
// Root
|
|
43
|
+
//
|
|
44
|
+
|
|
45
|
+
export type FormRootProps<T extends AnyProperties = AnyProperties> = Merge<
|
|
46
|
+
PropsWithChildren<{
|
|
47
|
+
/**
|
|
48
|
+
* Called when the form is submitted and passes validation.
|
|
49
|
+
*/
|
|
50
|
+
onSave?: (values: T, meta: FormUpdateMeta<T>) => void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Called when the form is canceled to abandon/undo any pending changes.
|
|
54
|
+
*/
|
|
55
|
+
onCancel?: () => void;
|
|
56
|
+
}>,
|
|
57
|
+
Omit<FormContextValue<T>, 'form'>,
|
|
58
|
+
Pick<FormHandlerProps<T>, 'schema' | 'autoSave' | 'values' | 'defaultValues' | 'onValidate' | 'onValuesChanged'>,
|
|
59
|
+
Omit<NaturalFormFieldSetProps<T>, 'schema' | 'path'>
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
// `variant` (from FormContextValue) flows through `...props` into the context for all parts to read.
|
|
63
|
+
export const FormRoot = <T extends AnyProperties = AnyProperties>({
|
|
64
|
+
children,
|
|
65
|
+
schema,
|
|
66
|
+
values,
|
|
67
|
+
onSave,
|
|
68
|
+
onCancel,
|
|
69
|
+
...props
|
|
70
|
+
}: FormRootProps<T>) => {
|
|
71
|
+
const form = useFormHandler({ schema, values, onSave, onCancel, ...props });
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<FormContextProvider form={form} {...props}>
|
|
75
|
+
{children}
|
|
76
|
+
</FormContextProvider>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
FormRoot.displayName = 'Form.Root';
|
|
81
|
+
|
|
82
|
+
//
|
|
83
|
+
// Viewport
|
|
84
|
+
//
|
|
85
|
+
|
|
86
|
+
const FORM_VIEWPORT_NAME = 'Form.Viewport';
|
|
87
|
+
|
|
88
|
+
export type FormViewportProps = {
|
|
89
|
+
scroll?: boolean;
|
|
90
|
+
gutter?: ColumnRootProps['gutter'];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// The viewing window: owns the gutter Column (chrome/side-padding).
|
|
94
|
+
// Content-height by default; `scroll` makes it fill its parent and scroll (the gutter then hosts the scrollbar).
|
|
95
|
+
export const FormViewport = composable<HTMLDivElement, FormViewportProps>(
|
|
96
|
+
({ children, scroll, gutter = 'sm', ...props }, forwardedRef) => {
|
|
97
|
+
const { variant = 'default' } = useFormContext(FORM_VIEWPORT_NAME);
|
|
98
|
+
const styles = formTheme.styles({ variant });
|
|
99
|
+
// Span the full width when nested inside another Column grid (e.g. Card.Root)
|
|
100
|
+
// instead of landing in a single narrow track.
|
|
101
|
+
const span = '[.dx-column-root_&]:col-span-full';
|
|
102
|
+
if (scroll) {
|
|
103
|
+
return (
|
|
104
|
+
<Column.Root gutter={gutter} classNames={['dx-expander', span]}>
|
|
105
|
+
<ScrollArea.Root
|
|
106
|
+
{...composableProps(props, { classNames: styles.viewport() })}
|
|
107
|
+
orientation='vertical'
|
|
108
|
+
centered
|
|
109
|
+
padding
|
|
110
|
+
thin
|
|
111
|
+
ref={forwardedRef}
|
|
112
|
+
>
|
|
113
|
+
<ScrollArea.Viewport>{children}</ScrollArea.Viewport>
|
|
114
|
+
</ScrollArea.Root>
|
|
115
|
+
</Column.Root>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Column.Root
|
|
121
|
+
{...composableProps(props, { classNames: ['w-full min-w-0', span, styles.viewport()] })}
|
|
122
|
+
gutter={gutter}
|
|
123
|
+
ref={forwardedRef}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</Column.Root>
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
FormViewport.displayName = FORM_VIEWPORT_NAME;
|
|
132
|
+
|
|
133
|
+
//
|
|
134
|
+
// Content
|
|
135
|
+
//
|
|
136
|
+
|
|
137
|
+
const FORM_CONTENT_NAME = 'Form.Content';
|
|
138
|
+
|
|
139
|
+
export type FormContentProps = ThemedClassName<PropsWithChildren<{}>>;
|
|
140
|
+
|
|
141
|
+
// The viewed body: centered in the viewport's gutter. Pure body — the gutter Column is owned by `Form.Viewport`.
|
|
142
|
+
export const FormContent = composable<HTMLDivElement, FormContentProps>(({ children, ...props }, forwardedRef) => {
|
|
143
|
+
const { form, testId, variant = 'default' } = useFormContext(FORM_CONTENT_NAME);
|
|
144
|
+
const styles = formTheme.styles({ variant });
|
|
145
|
+
const localRef = useRef<HTMLDivElement>(null);
|
|
146
|
+
const mergedRef = useMergeRefs([forwardedRef, localRef]);
|
|
147
|
+
useKeyHandler(localRef, form);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div
|
|
151
|
+
{...composableProps(props, {
|
|
152
|
+
role: 'form',
|
|
153
|
+
classNames: mx(withColumn.center(), 'flex flex-col w-full dx-document', styles.content()),
|
|
154
|
+
})}
|
|
155
|
+
data-testid={testId}
|
|
156
|
+
ref={mergedRef}
|
|
157
|
+
>
|
|
158
|
+
{children}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
FormContent.displayName = FORM_CONTENT_NAME;
|
|
164
|
+
|
|
165
|
+
//
|
|
166
|
+
// FieldSet
|
|
167
|
+
//
|
|
168
|
+
|
|
169
|
+
const FORM_FIELDSET_NAME = 'Form.FieldSet';
|
|
170
|
+
|
|
171
|
+
export type FormFieldSetControllerProps = ThemedClassName<NaturalFormFieldSetProps<any>>;
|
|
172
|
+
|
|
173
|
+
/** Context-reading binding for `Form.FieldSet`: pulls the schema + field context off the form and delegates to {@link FormFieldSet}. */
|
|
174
|
+
export const FormFieldSetController = ({ classNames, ...props }: FormFieldSetControllerProps) => {
|
|
175
|
+
const { form, variant = 'default', ...contextProps } = useFormContext(FORM_FIELDSET_NAME);
|
|
176
|
+
const styles = formTheme.styles({ variant });
|
|
177
|
+
return (
|
|
178
|
+
<FormFieldSet
|
|
179
|
+
schema={form.schema}
|
|
180
|
+
classNames={styles.fieldSet({ class: classNames })}
|
|
181
|
+
{...contextProps}
|
|
182
|
+
{...props}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
FormFieldSetController.displayName = FORM_FIELDSET_NAME;
|
|
188
|
+
|
|
189
|
+
//
|
|
190
|
+
// Layout
|
|
191
|
+
//
|
|
192
|
+
|
|
193
|
+
const FORM_LAYOUT_NAME = 'Form.Layout';
|
|
194
|
+
|
|
195
|
+
export type FormLayoutProps = Omit<NaturalFormLayoutProps, 'schema'> & { schema?: NaturalFormLayoutProps['schema'] };
|
|
196
|
+
|
|
197
|
+
/** Context-reading binding for `Form.Layout`: resolves the schema (prop or form) and delegates to {@link FormLayout}. */
|
|
198
|
+
export const FormLayoutController = ({ schema, ...props }: FormLayoutProps) => {
|
|
199
|
+
const { form, ...contextProps } = useFormContext(FORM_LAYOUT_NAME);
|
|
200
|
+
const resolvedSchema = schema ?? form.schema;
|
|
201
|
+
if (!resolvedSchema) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return <FormLayout schema={resolvedSchema} {...contextProps} {...props} />;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
FormLayoutController.displayName = FORM_LAYOUT_NAME;
|
|
209
|
+
|
|
210
|
+
//
|
|
211
|
+
// Actions
|
|
212
|
+
//
|
|
213
|
+
|
|
214
|
+
const FORM_ACTIONS_NAME = 'Form.Actions';
|
|
215
|
+
|
|
216
|
+
export type FormActionsProps = ThemedClassName<{}>;
|
|
217
|
+
|
|
218
|
+
export const FormActions = ({ classNames }: FormActionsProps) => {
|
|
219
|
+
const { t } = useTranslation(translationKey);
|
|
220
|
+
const {
|
|
221
|
+
form: { canSave, onSave, onCancel },
|
|
222
|
+
readonly,
|
|
223
|
+
layout,
|
|
224
|
+
} = useFormContext(FORM_ACTIONS_NAME);
|
|
225
|
+
|
|
226
|
+
if (readonly || layout === 'static') {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// TODO(burdon): Currently onCancel is a no-op; implement "revert values".
|
|
231
|
+
// Deprecate FormSubmit ans use FormActions without Cancel button if no callback is supplied.
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<div className={mx(withColumn.center(), formTheme.styles().actions(), classNames)}>
|
|
235
|
+
{onCancel && (
|
|
236
|
+
<IconButton
|
|
237
|
+
icon='ph--x--regular'
|
|
238
|
+
iconEnd
|
|
239
|
+
label={t('cancel-button.label')}
|
|
240
|
+
onClick={onCancel}
|
|
241
|
+
data-testid='cancel-button'
|
|
242
|
+
/>
|
|
243
|
+
)}
|
|
244
|
+
{onSave && (
|
|
245
|
+
<IconButton
|
|
246
|
+
type='submit'
|
|
247
|
+
variant='primary'
|
|
248
|
+
disabled={!canSave}
|
|
249
|
+
icon='ph--check--regular'
|
|
250
|
+
iconEnd
|
|
251
|
+
label={t('save-button.label')}
|
|
252
|
+
onClick={onSave}
|
|
253
|
+
data-testid='save-button'
|
|
254
|
+
/>
|
|
255
|
+
)}
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
FormActions.displayName = FORM_ACTIONS_NAME;
|
|
261
|
+
|
|
262
|
+
//
|
|
263
|
+
// Section
|
|
264
|
+
//
|
|
265
|
+
|
|
266
|
+
const FORM_SECTION_NAME = 'Form.Section';
|
|
267
|
+
|
|
268
|
+
export type FormSectionProps = ThemedClassName<{
|
|
269
|
+
title?: string;
|
|
270
|
+
description?: string;
|
|
271
|
+
}>;
|
|
272
|
+
|
|
273
|
+
export const FormSection = composable<HTMLDivElement, FormSectionProps>(
|
|
274
|
+
({ children, title, description, ...props }, forwardedRef) => {
|
|
275
|
+
const { variant = 'default' } = useFormContext(FORM_SECTION_NAME);
|
|
276
|
+
const styles = formTheme.styles({ variant });
|
|
277
|
+
return (
|
|
278
|
+
<div {...composableProps(props, { classNames: styles.section() })} ref={forwardedRef}>
|
|
279
|
+
{(title || description) && (
|
|
280
|
+
<div className={styles.sectionHeader()}>
|
|
281
|
+
{title && <h2 className={styles.sectionTitle()}>{title}</h2>}
|
|
282
|
+
{description && <MarkdownView classNames={styles.sectionDescription()} content={description} />}
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
{children}
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
},
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
FormSection.displayName = FORM_SECTION_NAME;
|
|
292
|
+
|
|
293
|
+
//
|
|
294
|
+
// Group
|
|
295
|
+
//
|
|
296
|
+
|
|
297
|
+
const FORM_GROUP_NAME = 'Form.Group';
|
|
298
|
+
|
|
299
|
+
export type FormGroupProps = ThemedClassName<PropsWithChildren>;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* A bordered card grouping related rows/controls (e.g. an entity card in a list).
|
|
303
|
+
* Layout-only and context-free — unlike `Form.FieldSet` it is not schema-driven.
|
|
304
|
+
*/
|
|
305
|
+
export const FormGroup = composable<HTMLDivElement, FormGroupProps>(({ children, ...props }, forwardedRef) => {
|
|
306
|
+
const { variant = 'default' } = useFormContext(FORM_SECTION_NAME);
|
|
307
|
+
const styles = formTheme.styles({ variant });
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<div {...composableProps(props, { classNames: styles.group() })} ref={forwardedRef}>
|
|
311
|
+
{children}
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
FormGroup.displayName = FORM_GROUP_NAME;
|
|
317
|
+
|
|
318
|
+
//
|
|
319
|
+
// Submit
|
|
320
|
+
//
|
|
321
|
+
|
|
322
|
+
const FORM_SUBMIT_NAME = 'Form.Submit';
|
|
323
|
+
|
|
324
|
+
export type FormSubmitProps = ThemedClassName<Partial<Pick<IconButtonProps, 'icon' | 'label' | 'disabled'>>>;
|
|
325
|
+
|
|
326
|
+
export const FormSubmit = ({ classNames, label, icon, disabled }: FormSubmitProps) => {
|
|
327
|
+
const { t } = useTranslation(translationKey);
|
|
328
|
+
const {
|
|
329
|
+
form: { canSave, onSave },
|
|
330
|
+
readonly,
|
|
331
|
+
layout,
|
|
332
|
+
} = useFormContext(FORM_SUBMIT_NAME);
|
|
333
|
+
|
|
334
|
+
if (readonly || layout === 'static') {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<div className={mx(formTheme.styles().submit(), classNames)}>
|
|
340
|
+
<IconButton
|
|
341
|
+
classNames='w-full'
|
|
342
|
+
type='submit'
|
|
343
|
+
variant='primary'
|
|
344
|
+
disabled={disabled ?? !canSave}
|
|
345
|
+
icon={icon ?? 'ph--check--regular'}
|
|
346
|
+
label={label ?? t('save-button.label')}
|
|
347
|
+
onClick={onSave}
|
|
348
|
+
data-testid='save-button'
|
|
349
|
+
/>
|
|
350
|
+
</div>
|
|
351
|
+
);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
FormSubmit.displayName = FORM_SUBMIT_NAME;
|
|
355
|
+
|
|
356
|
+
//
|
|
357
|
+
// Error
|
|
358
|
+
//
|
|
359
|
+
|
|
360
|
+
const FORM_ERROR_NAME = 'Form.Error';
|
|
361
|
+
|
|
362
|
+
export type FormErrorProps = ThemedClassName<PropsWithChildren>;
|
|
363
|
+
|
|
364
|
+
/** Form-level error/validation message (e.g. a failed submit), styled via the error valence. */
|
|
365
|
+
export const FormError = ({ children, classNames }: FormErrorProps) => {
|
|
366
|
+
if (!children) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<Input.Root validationValence='error'>
|
|
372
|
+
<Input.Validation classNames={classNames}>{children}</Input.Validation>
|
|
373
|
+
</Input.Root>
|
|
374
|
+
);
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
FormError.displayName = FORM_ERROR_NAME;
|
|
@@ -12,34 +12,31 @@ import React, { useMemo } from 'react';
|
|
|
12
12
|
import { Annotation, Format } from '@dxos/echo';
|
|
13
13
|
import { SchemaEx } from '@dxos/effect';
|
|
14
14
|
import { IconButton, IconButtonProps, useTranslation } from '@dxos/react-ui';
|
|
15
|
-
import { type ProjectionModel } from '@dxos/schema';
|
|
16
|
-
import { mx } from '@dxos/ui-theme';
|
|
17
15
|
|
|
18
16
|
import { translationKey } from '#translations';
|
|
17
|
+
import { type FieldContext, type FormFieldRenderer, type FormFieldRendererProps } from '#types';
|
|
19
18
|
|
|
20
|
-
import {
|
|
19
|
+
import { AutofillAnnotation, OptionsLookupAnnotation } from '../../../annotations';
|
|
20
|
+
import { useFormFieldState } from '../../../hooks';
|
|
21
|
+
import { getRefProps } from '../../../util';
|
|
22
|
+
import { FormFieldSet } from '../FormFieldSet';
|
|
21
23
|
import {
|
|
22
24
|
ArrayField,
|
|
25
|
+
AsyncSelectField,
|
|
26
|
+
AutofillField,
|
|
23
27
|
BooleanField,
|
|
28
|
+
ComboboxField,
|
|
24
29
|
DateField,
|
|
25
30
|
GeoPointField,
|
|
26
31
|
InlineRefField,
|
|
27
32
|
MarkdownField,
|
|
28
33
|
NumberField,
|
|
34
|
+
PasswordField,
|
|
29
35
|
RefField,
|
|
30
|
-
type RefFieldProps,
|
|
31
36
|
SelectField,
|
|
32
37
|
TextAreaField,
|
|
33
38
|
TextField,
|
|
34
39
|
} from './fields';
|
|
35
|
-
import { useFormFieldState } from './Form';
|
|
36
|
-
import {
|
|
37
|
-
type FormFieldComponent,
|
|
38
|
-
type FormFieldComponentProps,
|
|
39
|
-
type FormFieldMap,
|
|
40
|
-
type FormFieldProvider,
|
|
41
|
-
} from './FormFieldComponent';
|
|
42
|
-
import { FormFieldSet } from './FormFieldSet';
|
|
43
40
|
|
|
44
41
|
export type FormFieldProps = {
|
|
45
42
|
/**
|
|
@@ -58,50 +55,34 @@ export type FormFieldProps = {
|
|
|
58
55
|
name: string | null;
|
|
59
56
|
|
|
60
57
|
/**
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Optional projection for projection-based field management.
|
|
67
|
-
*/
|
|
68
|
-
projection?: ProjectionModel;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Map of custom renderers for specific properties.
|
|
72
|
-
* Prefer fieldProvider for plugin specific input surfaces.
|
|
58
|
+
* Explicit label, overriding the `title ?? capitalize(name)` derivation. Used by `ArrayField` to
|
|
59
|
+
* give scalar/ref items the array's resolved title (e.g. `Tags`) rather than re-capitalizing the
|
|
60
|
+
* raw array property name (e.g. `_tags`), since the element type carries no title of its own.
|
|
73
61
|
*/
|
|
74
|
-
|
|
62
|
+
label?: string;
|
|
75
63
|
|
|
76
64
|
/**
|
|
77
|
-
*
|
|
65
|
+
* Path to the current object from the root. Used with nested forms.
|
|
78
66
|
*/
|
|
79
|
-
|
|
80
|
-
|
|
67
|
+
path?: (string | number)[];
|
|
68
|
+
autoFocus?: boolean;
|
|
69
|
+
/** Whether the field is required (non-optional in the schema). Drives the label asterisk. */
|
|
70
|
+
required?: boolean;
|
|
81
71
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
72
|
+
* Force a `Ref` field to render its target inline (a nested form) instead of the picker.
|
|
73
|
+
* Set by `ArrayField` for owned-ref arrays (`FormCreateAnnotation`); equivalent to
|
|
74
|
+
* `FormInlineAnnotation` but driven by the parent array rather than the element's own AST.
|
|
85
75
|
*/
|
|
86
|
-
|
|
87
|
-
} &
|
|
88
|
-
Pick<
|
|
89
|
-
RefFieldProps,
|
|
90
|
-
| 'createOptionLabel'
|
|
91
|
-
| 'createOptionIcon'
|
|
92
|
-
| 'createInitialValuePath'
|
|
93
|
-
| 'createFieldMap'
|
|
94
|
-
| 'db'
|
|
95
|
-
| 'useType'
|
|
96
|
-
| 'getOptions'
|
|
97
|
-
| 'onCreate'
|
|
98
|
-
>;
|
|
76
|
+
refInline?: boolean;
|
|
77
|
+
} & FieldContext;
|
|
99
78
|
|
|
100
79
|
export const FormField = (props: FormFieldProps) => {
|
|
101
80
|
const {
|
|
102
81
|
type,
|
|
103
82
|
name,
|
|
83
|
+
label: labelProp,
|
|
104
84
|
path,
|
|
85
|
+
required,
|
|
105
86
|
projection,
|
|
106
87
|
fieldMap,
|
|
107
88
|
fieldProvider,
|
|
@@ -115,42 +96,49 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
115
96
|
createInitialValuePath,
|
|
116
97
|
createFieldMap,
|
|
117
98
|
db,
|
|
118
|
-
useType
|
|
99
|
+
useType,
|
|
119
100
|
getOptions,
|
|
120
101
|
onCreate,
|
|
102
|
+
resolveCreateEntry,
|
|
103
|
+
refInline,
|
|
121
104
|
} = props;
|
|
122
105
|
const { t } = useTranslation(translationKey);
|
|
123
106
|
const title = SchemaEx.getAnnotation<string>(SchemaAST.TitleAnnotationId)(type);
|
|
124
107
|
const description = SchemaEx.getAnnotation<string>(SchemaAST.DescriptionAnnotationId)(type);
|
|
125
108
|
const examples = SchemaEx.getAnnotation<string[]>(SchemaAST.ExamplesAnnotationId)(type);
|
|
126
109
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const label = useMemo(() => title ?? (name == null ? '' : String.capitalize(name)), [title, name]);
|
|
110
|
+
const label = useMemo(
|
|
111
|
+
() => labelProp ?? title ?? (name == null ? '' : String.capitalize(name)),
|
|
112
|
+
[labelProp, title, name],
|
|
113
|
+
);
|
|
132
114
|
const placeholder = useMemo(
|
|
133
115
|
() => (examples?.length ? `${t('example.placeholder')}: ${examples[0]}` : (description ?? label)),
|
|
134
|
-
[examples, description, label],
|
|
116
|
+
[examples, description, label, t],
|
|
135
117
|
);
|
|
136
118
|
|
|
119
|
+
// Build the schema for `fieldProvider` only when one is registered, memoized by `type` (the AST) so
|
|
120
|
+
// we don't reconstruct it on every render.
|
|
121
|
+
const providerSchema = useMemo(() => (fieldProvider ? Schema.make(type) : undefined), [fieldProvider, type]);
|
|
122
|
+
|
|
137
123
|
const fieldState = useFormFieldState(FormField.displayName, path);
|
|
138
124
|
const jsonPath = SchemaEx.createJsonPath(path ?? []);
|
|
139
|
-
const fieldProps:
|
|
125
|
+
const fieldProps: FormFieldRendererProps = {
|
|
140
126
|
type,
|
|
141
127
|
format: Format.FormatAnnotation.getFromAst(type).pipe((annotation) => Option.getOrUndefined(annotation)),
|
|
142
128
|
readonly,
|
|
143
129
|
label,
|
|
130
|
+
description,
|
|
144
131
|
jsonPath,
|
|
145
132
|
placeholder,
|
|
146
|
-
layout,
|
|
133
|
+
presentation: layout,
|
|
134
|
+
required,
|
|
147
135
|
db,
|
|
148
136
|
...fieldState,
|
|
149
137
|
};
|
|
150
138
|
|
|
151
139
|
// Omit empty fields entirely in read-only mode -- an empty value has nothing
|
|
152
140
|
// to display, so a labelled row with a blank input is just noise. This
|
|
153
|
-
// mirrors what `
|
|
141
|
+
// mirrors what `FormRow` already does for `presentation === 'static'`, but
|
|
154
142
|
// covers every field type (including those that bypass the wrapper:
|
|
155
143
|
// RefField, SelectField, MarkdownField, ...). Container fields
|
|
156
144
|
// (`ArrayField`, nested-struct -> `FormFieldSet`) keep their own
|
|
@@ -169,10 +157,29 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
169
157
|
return <CustomField {...fieldProps} />;
|
|
170
158
|
}
|
|
171
159
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
160
|
+
if (fieldProvider && providerSchema) {
|
|
161
|
+
const component = fieldProvider({ schema: providerSchema, prop: name ?? '', fieldProps });
|
|
162
|
+
if (component) {
|
|
163
|
+
return component;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
//
|
|
168
|
+
// Dynamic, value-driven fields (options/value/validation loaded via a self-contained Effect annotation).
|
|
169
|
+
//
|
|
170
|
+
|
|
171
|
+
const optionsLookup = Option.getOrUndefined(OptionsLookupAnnotation.getFromAst(type));
|
|
172
|
+
if (optionsLookup) {
|
|
173
|
+
return optionsLookup.combobox ? (
|
|
174
|
+
<ComboboxField {...fieldProps} lookup={optionsLookup} />
|
|
175
|
+
) : (
|
|
176
|
+
<AsyncSelectField {...fieldProps} lookup={optionsLookup} />
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const autofill = Option.getOrUndefined(AutofillAnnotation.getFromAst(type));
|
|
181
|
+
if (autofill) {
|
|
182
|
+
return <AutofillField {...fieldProps} autofill={autofill} />;
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
//
|
|
@@ -223,10 +230,21 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
223
230
|
|
|
224
231
|
const refProps = getRefProps(type);
|
|
225
232
|
if (refProps) {
|
|
226
|
-
// Inline a single referenced object's own fields (nested form) instead of a picker.
|
|
227
|
-
|
|
233
|
+
// Inline a single referenced object's own fields (nested form) instead of a picker. `refInline` lets a
|
|
234
|
+
// parent (e.g. an owned-ref `ArrayField`) force this for elements whose own AST carries no annotation.
|
|
235
|
+
const inline =
|
|
236
|
+
refInline || Annotation.FormInlineAnnotation.getFromAst(refProps.ast).pipe(Option.getOrElse(() => false));
|
|
228
237
|
if (inline && !refProps.isArray) {
|
|
229
|
-
return
|
|
238
|
+
return (
|
|
239
|
+
<InlineRefField
|
|
240
|
+
{...fieldProps}
|
|
241
|
+
{...refProps}
|
|
242
|
+
db={db}
|
|
243
|
+
useType={useType}
|
|
244
|
+
onCreate={onCreate}
|
|
245
|
+
resolveCreateEntry={resolveCreateEntry}
|
|
246
|
+
/>
|
|
247
|
+
);
|
|
230
248
|
}
|
|
231
249
|
|
|
232
250
|
const isCreateTarget = !createTypename || refProps.typename === createTypename;
|
|
@@ -239,9 +257,10 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
239
257
|
createInitialValuePath={isCreateTarget ? createInitialValuePath : undefined}
|
|
240
258
|
createFieldMap={isCreateTarget ? createFieldMap : undefined}
|
|
241
259
|
db={db}
|
|
242
|
-
useType={
|
|
260
|
+
useType={useType}
|
|
243
261
|
getOptions={getOptions}
|
|
244
262
|
onCreate={onCreate}
|
|
263
|
+
resolveCreateEntry={resolveCreateEntry}
|
|
245
264
|
/>
|
|
246
265
|
);
|
|
247
266
|
}
|
|
@@ -273,9 +292,10 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
273
292
|
createOptionIcon={createOptionIcon}
|
|
274
293
|
createInitialValuePath={createInitialValuePath}
|
|
275
294
|
db={db}
|
|
276
|
-
useType={
|
|
295
|
+
useType={useType}
|
|
277
296
|
getOptions={getOptions}
|
|
278
297
|
onCreate={onCreate}
|
|
298
|
+
resolveCreateEntry={resolveCreateEntry}
|
|
279
299
|
/>
|
|
280
300
|
);
|
|
281
301
|
}
|
|
@@ -290,28 +310,33 @@ FormField.displayName = 'Form.FormField';
|
|
|
290
310
|
// Layout components
|
|
291
311
|
//
|
|
292
312
|
|
|
293
|
-
export const
|
|
294
|
-
return
|
|
295
|
-
<div className={mx('h-full flex px-1', inline ? 'items-center' : 'flex-col pt-2.5')}>
|
|
296
|
-
<IconButton variant='ghost' density='xs' square iconOnly {...props} />
|
|
297
|
-
</div>
|
|
298
|
-
);
|
|
313
|
+
export const CompactIconButton = (props: IconButtonProps) => {
|
|
314
|
+
return <IconButton variant='ghost' iconOnly {...props} />;
|
|
299
315
|
};
|
|
300
316
|
|
|
301
317
|
/**
|
|
302
318
|
* Get property input component.
|
|
303
319
|
*/
|
|
304
|
-
const getFormField = ({
|
|
320
|
+
const getFormField = ({
|
|
321
|
+
type,
|
|
322
|
+
format,
|
|
323
|
+
}: Pick<FormFieldRendererProps, 'type' | 'format'>): FormFieldRenderer | undefined => {
|
|
324
|
+
// Unwrap refinements (e.g. Schema.Number.pipe(Schema.between(...))) to their base type.
|
|
325
|
+
if (SchemaAST.isRefinement(type)) {
|
|
326
|
+
return getFormField({ type: type.from, format });
|
|
327
|
+
}
|
|
328
|
+
|
|
305
329
|
//
|
|
306
330
|
// Standard formats.
|
|
307
331
|
//
|
|
308
332
|
|
|
309
333
|
const formatField = Match.value(format).pipe(
|
|
310
|
-
Match.withReturnType<
|
|
334
|
+
Match.withReturnType<FormFieldRenderer | undefined>(),
|
|
311
335
|
Match.when(Format.TypeFormat.Date, () => DateField),
|
|
312
336
|
Match.when(Format.TypeFormat.DateTime, () => DateField),
|
|
313
337
|
Match.when(Format.TypeFormat.GeoPoint, () => GeoPointField),
|
|
314
338
|
Match.when(Format.TypeFormat.Markdown, () => MarkdownField),
|
|
339
|
+
Match.when(Format.TypeFormat.Password, () => PasswordField),
|
|
315
340
|
Match.when(Format.TypeFormat.Text, () => TextAreaField),
|
|
316
341
|
Match.when(Format.TypeFormat.Time, () => DateField),
|
|
317
342
|
Match.orElse(() => undefined),
|
|
@@ -334,6 +359,8 @@ const getFormField = ({ type, format }: FormFieldComponentProps): FormFieldCompo
|
|
|
334
359
|
case 'BooleanKeyword':
|
|
335
360
|
return BooleanField;
|
|
336
361
|
}
|
|
362
|
+
|
|
363
|
+
return undefined;
|
|
337
364
|
};
|
|
338
365
|
|
|
339
366
|
const getSelectOptions = (ast: SchemaAST.AST): Format.Options[] | undefined => {
|