@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,81 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Cause from 'effect/Cause';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Exit from 'effect/Exit';
|
|
8
|
+
import { useEffect, useRef, useState } from 'react';
|
|
9
|
+
|
|
10
|
+
import { type AnyProperties } from '@dxos/echo/internal';
|
|
11
|
+
|
|
12
|
+
/** Picks the declared dependency fields from the form values — the subset a dynamic field subscribes to. */
|
|
13
|
+
export const pickValues = (values: AnyProperties | undefined, deps: readonly string[]): Record<string, unknown> => {
|
|
14
|
+
const subset: Record<string, unknown> = {};
|
|
15
|
+
for (const dep of deps) {
|
|
16
|
+
subset[dep] = values?.[dep];
|
|
17
|
+
}
|
|
18
|
+
return subset;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type AsyncFieldEffectState<A> = {
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: unknown;
|
|
24
|
+
data: A | undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type UseAsyncFieldEffectOptions = {
|
|
28
|
+
/** Debounce applied before running, so per-keystroke value changes coalesce into one run. */
|
|
29
|
+
debounceMs?: number;
|
|
30
|
+
/** When false, the effect is not run and prior data/error is cleared. */
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Runs a self-contained Effect (`R = never`) whenever `key` changes, debounced and abortable. Used by
|
|
36
|
+
* dynamic form fields to load select options, derive values, or validate against an external source from
|
|
37
|
+
* the current form values. The latest `factory` is always used, but a run is only (re)triggered when
|
|
38
|
+
* `key` changes — derive `key` from the inputs the factory actually reads. Re-running interrupts the
|
|
39
|
+
* prior run (cancelling any in-flight request).
|
|
40
|
+
*/
|
|
41
|
+
export const useAsyncFieldEffect = <A>(
|
|
42
|
+
factory: () => Effect.Effect<A, unknown>,
|
|
43
|
+
key: string,
|
|
44
|
+
{ debounceMs = 400, enabled = true }: UseAsyncFieldEffectOptions = {},
|
|
45
|
+
): AsyncFieldEffectState<A> => {
|
|
46
|
+
const factoryRef = useRef(factory);
|
|
47
|
+
factoryRef.current = factory;
|
|
48
|
+
const [state, setState] = useState<AsyncFieldEffectState<A>>({ loading: false, error: undefined, data: undefined });
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!enabled) {
|
|
52
|
+
setState({ loading: false, error: undefined, data: undefined });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let cancel: (() => void) | undefined;
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
setState((prev) => ({ ...prev, loading: true, error: undefined }));
|
|
59
|
+
cancel = Effect.runCallback(factoryRef.current(), {
|
|
60
|
+
onExit: (exit: Exit.Exit<A, unknown>) => {
|
|
61
|
+
Exit.match(exit, {
|
|
62
|
+
onSuccess: (data) => setState({ loading: false, error: undefined, data }),
|
|
63
|
+
onFailure: (cause) => {
|
|
64
|
+
// A superseding run interrupts this one; ignore the resulting interruption.
|
|
65
|
+
if (!Cause.isInterruptedOnly(cause)) {
|
|
66
|
+
setState((prev) => ({ loading: false, error: Cause.squash(cause), data: prev.data }));
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}, debounceMs);
|
|
73
|
+
|
|
74
|
+
return () => {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
cancel?.();
|
|
77
|
+
};
|
|
78
|
+
}, [key, enabled, debounceMs]);
|
|
79
|
+
|
|
80
|
+
return state;
|
|
81
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
8
|
+
|
|
9
|
+
import { type AnyProperties } from '@dxos/echo/internal';
|
|
10
|
+
import { SchemaEx } from '@dxos/effect';
|
|
11
|
+
|
|
12
|
+
import { type FieldContext, type FormFieldStateProps } from '#types';
|
|
13
|
+
|
|
14
|
+
import { type FormVariant } from '../components/Form/Form.theme';
|
|
15
|
+
import { type FormHandler } from './useFormHandler';
|
|
16
|
+
|
|
17
|
+
//
|
|
18
|
+
// Context
|
|
19
|
+
//
|
|
20
|
+
|
|
21
|
+
export type FormContextValue<T extends AnyProperties = any> = {
|
|
22
|
+
/**
|
|
23
|
+
* Form handler.
|
|
24
|
+
*/
|
|
25
|
+
form: FormHandler<T>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Testing.
|
|
29
|
+
*/
|
|
30
|
+
testId?: string;
|
|
31
|
+
|
|
32
|
+
/** Visual variant applied across the form's parts (see Form.theme). */
|
|
33
|
+
variant?: FormVariant;
|
|
34
|
+
} & FieldContext;
|
|
35
|
+
|
|
36
|
+
export const [FormContextProvider, useFormContext] = createContext<FormContextValue>('Form');
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the current form values.
|
|
40
|
+
*/
|
|
41
|
+
export const useFormValues: {
|
|
42
|
+
<T extends AnyProperties>(componentName: string, path?: (string | number)[]): T;
|
|
43
|
+
<T extends AnyProperties>(
|
|
44
|
+
componentName: string,
|
|
45
|
+
path: (string | number)[] | undefined,
|
|
46
|
+
defaultValue: () => T,
|
|
47
|
+
): T | undefined;
|
|
48
|
+
} = (componentName: string, path?: (string | number)[], defaultValue?: () => any) => {
|
|
49
|
+
const stablePath = useMemo(() => path ?? [], [path ? path.join('.') : undefined]);
|
|
50
|
+
const jsonPath = SchemaEx.createJsonPath(stablePath);
|
|
51
|
+
const {
|
|
52
|
+
form: { values, onValueChange },
|
|
53
|
+
} = useFormContext(componentName);
|
|
54
|
+
|
|
55
|
+
const value = SchemaEx.getValue(values, jsonPath);
|
|
56
|
+
|
|
57
|
+
// Apply default value once when the field has no value. lastAppliedPathRef prevents
|
|
58
|
+
// re-applying on every render (e.g. when defaultValue() returns null) and ensures
|
|
59
|
+
// we apply per path when the hook is used for different fields.
|
|
60
|
+
const lastAppliedPathRef = useRef<string | null>(null);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (value == null && defaultValue && lastAppliedPathRef.current !== jsonPath) {
|
|
63
|
+
lastAppliedPathRef.current = jsonPath;
|
|
64
|
+
onValueChange(stablePath, SchemaAST.stringKeyword, defaultValue());
|
|
65
|
+
}
|
|
66
|
+
}, [value, defaultValue, onValueChange, stablePath, jsonPath]);
|
|
67
|
+
|
|
68
|
+
return value;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the state props for the given field.
|
|
73
|
+
*/
|
|
74
|
+
export const useFormFieldState = (componentName: string, path: (string | number)[] = []): FormFieldStateProps => {
|
|
75
|
+
const stablePath = useMemo(() => path, [Array.isArray(path) ? path.join('.') : path]);
|
|
76
|
+
const {
|
|
77
|
+
form: { getStatus, getValue, onBlur, onValueChange },
|
|
78
|
+
} = useFormContext(componentName);
|
|
79
|
+
|
|
80
|
+
return useMemo(
|
|
81
|
+
() => ({
|
|
82
|
+
getStatus: () => getStatus(stablePath),
|
|
83
|
+
getValue: () => getValue(stablePath),
|
|
84
|
+
onBlur: () => onBlur(stablePath),
|
|
85
|
+
onValueChange: (ast: SchemaAST.AST, value: any) => onValueChange(stablePath, ast, value),
|
|
86
|
+
}),
|
|
87
|
+
[getStatus, getValue, onBlur, onValueChange, stablePath],
|
|
88
|
+
);
|
|
89
|
+
};
|
|
@@ -14,10 +14,7 @@ import { useDefaultValue } from '@dxos/react-ui';
|
|
|
14
14
|
import { type ValidationError, validateSchema } from '@dxos/schema';
|
|
15
15
|
import { type MaybePromise } from '@dxos/util';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
status?: 'error';
|
|
19
|
-
error?: string;
|
|
20
|
-
};
|
|
17
|
+
import { type FormFieldStatus } from '#types';
|
|
21
18
|
|
|
22
19
|
/**
|
|
23
20
|
* Form properties.
|
|
@@ -80,17 +77,21 @@ export type FormHandler<T extends AnyProperties> = Pick<FormHandlerProps<T>, 'sc
|
|
|
80
77
|
/** Initial values (which may not pass validation). */
|
|
81
78
|
values: Partial<T>;
|
|
82
79
|
|
|
83
|
-
/**
|
|
80
|
+
/** Fields whose value has been edited (set by `onValueChange`). Gates autosave-on-blur. */
|
|
84
81
|
changed: Record<SchemaEx.JsonPath, boolean>;
|
|
85
|
-
|
|
82
|
+
/** Fields the user has interacted with (set by `onBlur`). Gates error *visibility* — errors only surface once touched. */
|
|
86
83
|
touched: Record<SchemaEx.JsonPath, boolean>;
|
|
87
84
|
|
|
88
85
|
/** Map of error strings. */
|
|
89
86
|
errors: Record<SchemaEx.JsonPath, string>;
|
|
90
87
|
|
|
91
|
-
/** Whether the
|
|
88
|
+
/** Whether the values pass validation (no errors at all). */
|
|
92
89
|
isValid: boolean;
|
|
93
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Whether the submit affordance should be enabled. Stricter than `isValid`: it also requires no save
|
|
92
|
+
* in flight (`saving`) and only blocks on errors for *touched* fields, so a pristine form with
|
|
93
|
+
* not-yet-touched required fields can still show an enabled control.
|
|
94
|
+
*/
|
|
94
95
|
canSave: boolean;
|
|
95
96
|
|
|
96
97
|
onSave: () => void;
|
|
@@ -328,6 +329,7 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
328
329
|
}),
|
|
329
330
|
[
|
|
330
331
|
schema,
|
|
332
|
+
autoSave,
|
|
331
333
|
values,
|
|
332
334
|
errors,
|
|
333
335
|
touched,
|
|
@@ -338,8 +340,8 @@ export const useFormHandler = <T extends AnyProperties>({
|
|
|
338
340
|
getValue,
|
|
339
341
|
onBlur,
|
|
340
342
|
onValueChange,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
+
handleSave,
|
|
344
|
+
handleCancel,
|
|
343
345
|
],
|
|
344
346
|
);
|
|
345
347
|
};
|
package/src/index.ts
CHANGED
|
@@ -7,23 +7,26 @@ import React, { type PropsWithChildren } from 'react';
|
|
|
7
7
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
8
|
import { composableProps, slottable } from '@dxos/react-ui';
|
|
9
9
|
import { Syntax } from '@dxos/react-ui-syntax-highlighter';
|
|
10
|
+
import { mx } from '@dxos/ui-theme';
|
|
10
11
|
|
|
11
|
-
type TestLayoutProps = PropsWithChildren<{ json?:
|
|
12
|
+
type TestLayoutProps = PropsWithChildren<{ json?: unknown }>;
|
|
12
13
|
|
|
13
14
|
export const TestLayout = ({ children, json }: TestLayoutProps) => {
|
|
14
15
|
return (
|
|
15
|
-
<div className='dx-container grid grid-cols-
|
|
16
|
+
<div className={mx('dx-container grid grid-cols-1 p-4 gap-4', !!json && 'grid-cols-[1fr_1fr]')}>
|
|
16
17
|
<TestPanel>{children}</TestPanel>
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
<Syntax.
|
|
20
|
-
<Syntax.
|
|
21
|
-
|
|
22
|
-
<Syntax.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
{!!json && (
|
|
19
|
+
<TestPanel>
|
|
20
|
+
<Syntax.Root data={json}>
|
|
21
|
+
<Syntax.Content>
|
|
22
|
+
<Syntax.Filter />
|
|
23
|
+
<Syntax.Viewport>
|
|
24
|
+
<Syntax.Code testId='debug' classNames='text-sm' />
|
|
25
|
+
</Syntax.Viewport>
|
|
26
|
+
</Syntax.Content>
|
|
27
|
+
</Syntax.Root>
|
|
28
|
+
</TestPanel>
|
|
29
|
+
)}
|
|
27
30
|
</div>
|
|
28
31
|
);
|
|
29
32
|
};
|
|
@@ -32,7 +35,7 @@ type TestPanelProps = ThemedClassName<PropsWithChildren>;
|
|
|
32
35
|
|
|
33
36
|
export const TestPanel = slottable<HTMLDivElement, TestPanelProps>(({ children }, forwardedRef) => {
|
|
34
37
|
return (
|
|
35
|
-
<div {...composableProps({ classNames: 'dx-container
|
|
38
|
+
<div {...composableProps({ classNames: 'dx-container dx-card-surface rounded-sm' })} ref={forwardedRef}>
|
|
36
39
|
{children}
|
|
37
40
|
</div>
|
|
38
41
|
);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
|
|
7
|
+
import { Annotation, DXN, Format, Ref, Tag, Type } from '@dxos/echo';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Shared test schemas for form stories. Intentionally small, hand-written types
|
|
11
|
+
* (not `@dxos/types`) exercising the range of field renderers: scalars, nested
|
|
12
|
+
* structs, refs, ref arrays, enums, and the various formats.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export class Organization extends Type.makeObject<Organization>(DXN.make('com.example.type.organization', '0.1.0'))(
|
|
16
|
+
Schema.Struct({
|
|
17
|
+
name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
|
|
18
|
+
}),
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
export class Person extends Type.makeObject<Person>(DXN.make('org.dxos.type.person', '0.1.0'))(
|
|
22
|
+
Schema.Struct({
|
|
23
|
+
name: Schema.String.pipe(Schema.minLength(1)).annotations({ title: 'Full name' }),
|
|
24
|
+
ignore: Schema.String.pipe(Annotation.FormInputAnnotation.set(false), Schema.optional),
|
|
25
|
+
active: Schema.optional(Schema.Boolean.annotations({ title: 'Active' })),
|
|
26
|
+
address: Schema.optional(
|
|
27
|
+
Schema.Struct({
|
|
28
|
+
street: Schema.String,
|
|
29
|
+
city: Schema.String,
|
|
30
|
+
// TODO(burdon): Constrain input control.
|
|
31
|
+
state: Schema.String.pipe(Schema.minLength(2), Schema.maxLength(2)).annotations({
|
|
32
|
+
title: 'State',
|
|
33
|
+
description: 'State code',
|
|
34
|
+
}),
|
|
35
|
+
zip: Schema.Number.annotations({ title: 'ZIP Code' }),
|
|
36
|
+
}).annotations({ title: 'Address' }),
|
|
37
|
+
),
|
|
38
|
+
employer: Schema.optional(Ref.Ref(Organization).annotations({ title: 'Employer' })),
|
|
39
|
+
tags: Schema.optional(Schema.Array(Ref.Ref(Tag.Tag)).annotations({ title: 'Tags' })),
|
|
40
|
+
status: Schema.optional(Schema.Literal('active', 'inactive').annotations({ title: 'Status' })),
|
|
41
|
+
notes: Schema.optional(Format.Text.annotations({ title: 'Notes' })),
|
|
42
|
+
location: Schema.optional(Format.GeoPoint.annotations({ title: 'Location' })),
|
|
43
|
+
birthday: Schema.optional(Format.DateOnly.annotations({ title: 'Birthday' })),
|
|
44
|
+
meetingAt: Schema.optional(Format.DateTime.annotations({ title: 'Next meeting' })),
|
|
45
|
+
reminderAt: Schema.optional(Format.TimeOnly.annotations({ title: 'Reminder time' })),
|
|
46
|
+
tasks: Schema.optional(Schema.Array(Schema.String).annotations({ title: 'Tasks' })),
|
|
47
|
+
locations: Schema.optional(Schema.Array(Format.GeoPoint).annotations({ title: 'Locations' })),
|
|
48
|
+
identities: Schema.optional(
|
|
49
|
+
Schema.Array(
|
|
50
|
+
Schema.Struct({
|
|
51
|
+
type: Schema.String.annotations({ title: 'Type' }),
|
|
52
|
+
value: Schema.String.annotations({ title: 'Value' }),
|
|
53
|
+
}).annotations({ title: 'Identities' }),
|
|
54
|
+
).annotations({
|
|
55
|
+
title: 'Identities',
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
}),
|
|
59
|
+
) {}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type * as Schema from 'effect/Schema';
|
|
6
|
+
import type * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
import { type FC, type FocusEvent, type ReactElement } from 'react';
|
|
8
|
+
|
|
9
|
+
import { type Database, type Entity, type Format, type Obj, type Type } from '@dxos/echo';
|
|
10
|
+
import { type URI } from '@dxos/keys';
|
|
11
|
+
import { type Palette } from '@dxos/react-ui';
|
|
12
|
+
import { type ProjectionModel } from '@dxos/schema';
|
|
13
|
+
|
|
14
|
+
//
|
|
15
|
+
// Field component contracts.
|
|
16
|
+
//
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validation status of a field.
|
|
20
|
+
*/
|
|
21
|
+
export type FormFieldStatus = {
|
|
22
|
+
status?: 'error';
|
|
23
|
+
error?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* FormPresentation mode.
|
|
28
|
+
* - full: Show label, control, and status.
|
|
29
|
+
* - compact: Show label and control.
|
|
30
|
+
* - inline: Control only.
|
|
31
|
+
* - static: Plain DOM; omit all undefined values.
|
|
32
|
+
*/
|
|
33
|
+
export type FormPresentation = 'full' | 'compact' | 'inline' | 'static';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Dynamic props passed to input components.
|
|
37
|
+
*/
|
|
38
|
+
export type FormFieldStateProps<T = any> = {
|
|
39
|
+
getStatus: () => FormFieldStatus;
|
|
40
|
+
getValue: () => T | undefined;
|
|
41
|
+
onBlur: (event: FocusEvent<HTMLElement>) => void;
|
|
42
|
+
onValueChange: (type: SchemaAST.AST, value: T) => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Props passed to input components.
|
|
47
|
+
*/
|
|
48
|
+
export type FormFieldRendererProps<T = any> = {
|
|
49
|
+
/** Database the form is editing against; populated for fields whose value is an ECHO object/Ref. */
|
|
50
|
+
db?: Database.Database;
|
|
51
|
+
type: SchemaAST.AST;
|
|
52
|
+
format?: Format.TypeFormat;
|
|
53
|
+
readonly?: boolean;
|
|
54
|
+
label: string;
|
|
55
|
+
/**
|
|
56
|
+
* Human-readable description (from the schema's description annotation). Rendered as visible text
|
|
57
|
+
* only when the resolved presentation opts in; otherwise it falls back to the input placeholder.
|
|
58
|
+
*/
|
|
59
|
+
description?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Dotted JSON path of this field within the form values (e.g. `runtime.client.storage.persistent`).
|
|
62
|
+
*/
|
|
63
|
+
jsonPath?: string;
|
|
64
|
+
placeholder?: string;
|
|
65
|
+
autoFocus?: boolean;
|
|
66
|
+
/** Presentation mode for the field (full/compact/inline/static); see {@link FormPresentation}. */
|
|
67
|
+
presentation?: FormPresentation;
|
|
68
|
+
/** Whether the field is required (non-optional in the schema); surfaces a trailing asterisk on the label. */
|
|
69
|
+
required?: boolean;
|
|
70
|
+
} & FormFieldStateProps<T>;
|
|
71
|
+
|
|
72
|
+
export type FormFieldRenderer = FC<FormFieldRendererProps>;
|
|
73
|
+
|
|
74
|
+
export type FormFieldMap = Record<string, FormFieldRenderer>;
|
|
75
|
+
|
|
76
|
+
export type FormFieldProvider = (props: {
|
|
77
|
+
prop: string;
|
|
78
|
+
schema: Schema.Schema<any>;
|
|
79
|
+
fieldProps: FormFieldRendererProps;
|
|
80
|
+
}) => ReactElement | null | undefined;
|
|
81
|
+
|
|
82
|
+
//
|
|
83
|
+
// Ref / create contracts.
|
|
84
|
+
//
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Per-typename override for the ref-field inline create form and its submission handler.
|
|
88
|
+
* The type parameter S links the form schema's decoded type to the createObject values argument,
|
|
89
|
+
* ensuring the schema and the handler agree on the shape of the form data.
|
|
90
|
+
*/
|
|
91
|
+
export type CreateEntryOverride<S extends Schema.Schema.AnyNoContext = Schema.Schema.AnyNoContext> = {
|
|
92
|
+
/** Replaces the raw ECHO type schema for the inline create form. */
|
|
93
|
+
inputSchema?: S;
|
|
94
|
+
/** Runs instead of the default onCreate(schema, values) path; values are typed from inputSchema. */
|
|
95
|
+
createObject?: (values: Schema.Schema.Type<S>, db: Database.Database) => Promise<Obj.Unknown>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Configuration for the inline "create a new referenced object" affordance, shared by every
|
|
100
|
+
* surface that can spawn a ref target (the picker, `RefField`, and the `Form*` props that forward
|
|
101
|
+
* to them). `onCreate` is intentionally NOT part of this group: its signature differs per consumer
|
|
102
|
+
* (the picker takes `(values)`, `RefField` takes `(schema, values)`).
|
|
103
|
+
*/
|
|
104
|
+
export type CreateOptions = {
|
|
105
|
+
createOptionLabel?: [string, { ns: string | readonly string[] }];
|
|
106
|
+
createOptionIcon?: string;
|
|
107
|
+
createInitialValuePath?: string;
|
|
108
|
+
createFieldMap?: FormFieldMap;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export type RefOption = {
|
|
112
|
+
id: string;
|
|
113
|
+
label: string;
|
|
114
|
+
/** Optional secondary line shown beneath the label in the picker list. */
|
|
115
|
+
description?: string;
|
|
116
|
+
hue?: Palette;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Data plumbing for resolving and creating ref targets: how to look up the ref type, query
|
|
121
|
+
* candidate objects, map them to options, and persist a newly-created one.
|
|
122
|
+
*/
|
|
123
|
+
export type RefFieldDataProps = {
|
|
124
|
+
// TODO(burdon): Replace hooks with callbacks?
|
|
125
|
+
useType?: (db?: Database.Database, typeUri?: URI.URI) => Type.AnyEntity;
|
|
126
|
+
useResults?: (db?: Database.Database, typename?: string) => Entity.Any[];
|
|
127
|
+
getOptions?: (
|
|
128
|
+
objects: Entity.Any[],
|
|
129
|
+
options?: { parentLabel?: boolean; getTypePlaceholder?: (typename: string) => string },
|
|
130
|
+
) => RefOption[];
|
|
131
|
+
/**
|
|
132
|
+
* Persist a newly-created object. Called after the user fills out the inline create form and
|
|
133
|
+
* clicks Save. Should add the object to the database and return it (sync or async). The returned
|
|
134
|
+
* object is then wired into this slot's form value as a Ref.
|
|
135
|
+
*/
|
|
136
|
+
onCreate?: (schema: Type.AnyEntity, values: any) => Obj.Unknown | Promise<Obj.Unknown> | undefined | void;
|
|
137
|
+
/**
|
|
138
|
+
* Supply default values for a newly-created owned object when the user clicks "add" on an owned-ref
|
|
139
|
+
* array field (see `FormCreateAnnotation`). Keyed by the field's json path so a container can pre-populate
|
|
140
|
+
* the new object (e.g. a back-reference to the parent). The returned values are passed to
|
|
141
|
+
* `onCreate(schema, values)` before the object is persisted.
|
|
142
|
+
*/
|
|
143
|
+
getCreateDefaults?: (props: { jsonPath: string; schema: Type.AnyEntity }) => Record<string, unknown> | undefined;
|
|
144
|
+
/**
|
|
145
|
+
* Optional resolver that maps a ref's typename to a {@link CreateEntryOverride}, replacing the
|
|
146
|
+
* raw ECHO type schema and/or the default `onCreate` path with plugin-registered create logic
|
|
147
|
+
* (e.g. `SpaceCapabilities.CreateObjectEntry`). When absent or returning `undefined` for a given
|
|
148
|
+
* typename the existing fallback behaviour is preserved.
|
|
149
|
+
*/
|
|
150
|
+
resolveCreateEntry?: (typename: string) => CreateEntryOverride | undefined;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Form-wide field options carried by the form context and threaded down to every field. The
|
|
155
|
+
* canonical source for the config that `FormContextValue`, `FormFieldProps`, and `FormFieldSetProps`
|
|
156
|
+
* all share — so none of them has to reach "up" into another's props to express it.
|
|
157
|
+
*/
|
|
158
|
+
export type FormFieldOptions = {
|
|
159
|
+
db?: Database.Database;
|
|
160
|
+
readonly?: boolean;
|
|
161
|
+
layout?: FormPresentation;
|
|
162
|
+
projection?: ProjectionModel;
|
|
163
|
+
fieldMap?: FormFieldMap;
|
|
164
|
+
fieldProvider?: FormFieldProvider;
|
|
165
|
+
/**
|
|
166
|
+
* Typename of the ref type that the create props apply to.
|
|
167
|
+
* When set, the create affordance is only offered to ref fields whose typename matches.
|
|
168
|
+
*/
|
|
169
|
+
createTypename?: string;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The subset of {@link RefFieldDataProps} that is forwarded down the field recursion. `useResults`
|
|
174
|
+
* is consumed only by `RefField` itself (it defaults internally), so it is not threaded.
|
|
175
|
+
*/
|
|
176
|
+
export type RefThreadedProps = Pick<
|
|
177
|
+
RefFieldDataProps,
|
|
178
|
+
'useType' | 'getOptions' | 'onCreate' | 'getCreateDefaults' | 'resolveCreateEntry'
|
|
179
|
+
>;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Form-wide configuration threaded unchanged down the entire field recursion. This is the single
|
|
183
|
+
* canonical source for the bag that `FormContextValue`, `FormFieldProps`, and `FormFieldSetProps`
|
|
184
|
+
* all carry — so none of them re-Picks fragments of the others.
|
|
185
|
+
*/
|
|
186
|
+
export type FieldContext = FormFieldOptions & CreateOptions & RefThreadedProps;
|
package/src/util/index.ts
CHANGED
package/src/util/omit.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Option from 'effect/Option';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
8
|
+
|
|
9
|
+
import { Annotation, Type } from '@dxos/echo';
|
|
10
|
+
|
|
11
|
+
// TODO(burdon): Reconcile with @dxos/echo.
|
|
12
|
+
// Distributive: applied per union member so `ExcludeId<A | B>` → `ExcludeId<A> | ExcludeId<B>` rather
|
|
13
|
+
// than `Omit<InstanceType<A> | InstanceType<B>, 'id'>` (which would intersect away per-member narrowing).
|
|
14
|
+
export type ExcludeId<S extends Schema.Schema.AnyNoContext | Type.AnyEntity> = S extends Type.AnyEntity
|
|
15
|
+
? Omit<Type.InstanceType<S>, 'id'>
|
|
16
|
+
: S extends Schema.Schema.AnyNoContext
|
|
17
|
+
? Omit<Schema.Schema.Type<S>, 'id'>
|
|
18
|
+
: never;
|
|
19
|
+
|
|
20
|
+
// TODO(burdon): Move to @dxos/schema (re-export here).
|
|
21
|
+
export const omitId = <S extends Schema.Schema.AnyNoContext | Type.AnyEntity>(
|
|
22
|
+
schemaOrType: S,
|
|
23
|
+
): Schema.Schema<ExcludeId<S>, ExcludeId<S>> => {
|
|
24
|
+
const schema = Type.isType(schemaOrType)
|
|
25
|
+
? Type.getSchema(schemaOrType)
|
|
26
|
+
: (schemaOrType as Schema.Schema.AnyNoContext);
|
|
27
|
+
// Cast: `Schema.omit` cannot statically express the `ExcludeId<S>` result type, so the result is a
|
|
28
|
+
// widened schema; the runtime shape matches the declared return type.
|
|
29
|
+
return omitIdFromSchema(schema) as any;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Removes the `id` field where present, preserving the schema's shape. Recurses into unions so a
|
|
34
|
+
* discriminated (create) schema keeps its discriminant — `Schema.omit('id')` is a struct operation and
|
|
35
|
+
* would otherwise flatten the union, breaking variant rendering. A no-op when there is no `id` (plain
|
|
36
|
+
* create structs that never carried one).
|
|
37
|
+
*/
|
|
38
|
+
const omitIdFromSchema = (schema: Schema.Schema.AnyNoContext): Schema.Schema.AnyNoContext => {
|
|
39
|
+
const ast = schema.ast;
|
|
40
|
+
if (SchemaAST.isUnion(ast)) {
|
|
41
|
+
return Schema.Union(...ast.types.map((type) => omitIdFromSchema(Schema.make(type))));
|
|
42
|
+
}
|
|
43
|
+
const hasId = SchemaAST.getPropertySignatures(ast).some((prop) => prop.name === 'id');
|
|
44
|
+
return hasId ? schema.pipe(Schema.omit('id')) : schema;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Drop fields annotated with `FormInputAnnotation.set(false)` from a schema so
|
|
49
|
+
* the form's validator doesn't trip on required-but-hidden fields. Used by
|
|
50
|
+
* the picker's inline create form, where a `FactoryAnnotation` typically
|
|
51
|
+
* supplies the hidden values (e.g. a backing-object Ref) outside the form.
|
|
52
|
+
*/
|
|
53
|
+
export const omitHiddenFormFields = <S extends Schema.Schema.AnyNoContext>(schema: S): S => {
|
|
54
|
+
const properties = SchemaAST.getPropertySignatures(schema.ast);
|
|
55
|
+
const hidden = properties
|
|
56
|
+
.filter((prop) => Option.getOrElse(Annotation.FormInputAnnotation.getFromAst(prop.type), () => true) === false)
|
|
57
|
+
.map((prop) => prop.name as string);
|
|
58
|
+
// Cast: omitting a dynamically-computed set of keys can't be expressed as the original `S`, but
|
|
59
|
+
// the result is structurally a subset of `S` and callers treat it as `S`.
|
|
60
|
+
return hidden.length === 0 ? schema : (schema.pipe(Schema.omit(...(hidden as [string, ...string[]]))) as any);
|
|
61
|
+
};
|