@dxos/react-ui-form 0.9.0 → 0.9.1-main.c7dcc2e112
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 +2221 -2147
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2221 -2147
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- 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 +15 -110
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +3 -37
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormControls.d.ts +66 -0
- package/dist/types/src/components/Form/FormControls.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/FieldHeader.d.ts +19 -0
- package/dist/types/src/components/Form/FormField/FieldHeader.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/FormField.d.ts +30 -0
- package/dist/types/src/components/Form/FormField/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormField/FormFieldWrapper.d.ts +47 -0
- package/dist/types/src/components/Form/FormField/FormFieldWrapper.d.ts.map +1 -0
- package/dist/types/src/components/Form/{FormFieldComponent.stories.d.ts → FormField/FormFieldWrapper.stories.d.ts} +3 -3
- package/dist/types/src/components/Form/FormField/FormFieldWrapper.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 +87 -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/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/Form/FormField/fields/BooleanField/BooleanField.stories.d.ts +88 -0
- 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/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/{fields → FormField/fields/GeoPointField}/GeoPointField.d.ts +2 -2
- 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 +4 -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/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/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/{fields → FormField/fields/SelectField}/SelectField.d.ts +2 -2
- 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 +0 -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 +5 -0
- package/dist/types/src/components/Form/FormField/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/{FormFieldSet.d.ts → FormFieldSet/FormFieldSet.d.ts} +4 -3
- package/dist/types/src/components/Form/FormFieldSet/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet/index.d.ts +2 -0
- package/dist/types/src/components/Form/FormFieldSet/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormLayout/FormLayout.d.ts.map +1 -0
- 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 +2 -2
- 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.map +1 -1
- 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.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormContext.d.ts +37 -0
- package/dist/types/src/hooks/useFormContext.d.ts.map +1 -0
- package/dist/types/src/hooks/useFormHandler.d.ts +1 -4
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/{components/testing.d.ts → testing/TestLayout.d.ts} +1 -1
- 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 +43 -0
- package/dist/types/src/testing/schema.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +112 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +1 -0
- 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 +35 -34
- package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.test.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.tsx +5 -4
- package/src/components/Form/Form.stories.tsx +4 -49
- package/src/components/Form/Form.tsx +18 -447
- package/src/components/Form/FormControls.tsx +320 -0
- package/src/components/Form/FormField/FieldHeader.tsx +36 -0
- package/src/components/Form/{FormField.tsx → FormField/FormField.tsx} +35 -59
- package/src/components/Form/{FormFieldComponent.stories.tsx → FormField/FormFieldWrapper.stories.tsx} +24 -17
- package/src/components/Form/{FormFieldComponent.tsx → FormField/FormFieldWrapper.tsx} +21 -95
- package/src/components/Form/FormField/fields/ArrayField/ArrayField.stories.tsx +103 -0
- package/src/components/Form/FormField/fields/ArrayField/ArrayField.tsx +269 -0
- package/src/components/Form/FormField/fields/ArrayField/index.ts +5 -0
- package/src/components/Form/FormField/fields/BooleanField/BooleanField.stories.tsx +52 -0
- package/src/components/Form/{fields → FormField/fields/BooleanField}/BooleanField.tsx +10 -3
- package/src/components/Form/FormField/fields/BooleanField/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 +4 -2
- 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 +4 -3
- package/src/components/Form/FormField/fields/GeoPointField/index.ts +5 -0
- package/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.stories.tsx +6 -6
- package/src/components/Form/{fields → FormField/fields/MarkdownField}/MarkdownField.tsx +3 -2
- 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/{fields → FormField/fields/NumberField}/NumberField.tsx +27 -2
- package/src/components/Form/FormField/fields/NumberField/index.ts +5 -0
- package/src/components/Form/{fields → FormField/fields/RefField}/InlineRefField.tsx +8 -7
- package/src/components/Form/FormField/fields/RefField/RefField.stories.tsx +97 -0
- package/src/components/Form/{fields → FormField/fields/RefField}/RefField.test.ts +2 -1
- package/src/components/Form/{fields → FormField/fields/RefField}/RefField.tsx +6 -25
- 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/{fields → FormField/fields/SelectField}/SelectField.tsx +4 -2
- 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 +55 -53
- 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 +4 -2
- 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 +4 -2
- 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 +0 -1
- package/src/components/Form/FormField/index.ts +8 -0
- package/src/components/Form/{FormFieldSet.tsx → FormFieldSet/FormFieldSet.tsx} +16 -32
- package/src/components/Form/FormFieldSet/index.ts +5 -0
- package/src/components/Form/{Layout → FormLayout}/FormLayout.stories.tsx +4 -4
- package/src/components/Form/{Layout → FormLayout}/FormLayout.tsx +7 -6
- package/src/components/Form/{Layout → FormLayout}/index.ts +1 -2
- package/src/components/Form/index.ts +3 -5
- package/src/components/Form/meta-tags.ts +1 -1
- package/src/components/ObjectForm/ObjectForm.tsx +15 -14
- package/src/components/ObjectPicker/ObjectPicker.tsx +19 -27
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +1 -1
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +1 -1
- package/src/components/ObjectProperties/ObjectProperties.tsx +2 -1
- package/src/components/RefEditor/RefEditor.stories.tsx +0 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +4 -5
- package/src/components/ViewEditor/ViewEditor.test.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.tsx +98 -128
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useFormContext.ts +86 -0
- package/src/hooks/useFormHandler.ts +1 -4
- package/src/index.ts +1 -0
- package/src/{components/testing.tsx → testing/TestLayout.tsx} +1 -1
- package/src/testing/index.ts +7 -0
- package/src/testing/schema.ts +59 -0
- package/src/types.ts +134 -0
- package/src/util/index.ts +1 -0
- package/src/util/omit.ts +47 -0
- 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.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.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.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/testing.d.ts.map +0 -1
- package/src/components/Form/AUDIT.md +0 -17
- package/src/components/Form/FormTooltipsContext.ts +0 -25
- package/src/components/Form/Layout/annotation.ts +0 -38
- package/src/components/Form/README.md +0 -3
- package/src/components/Form/fields/ArrayField.tsx +0 -173
- /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.d.ts +0 -0
- /package/dist/types/src/components/Form/{Layout → FormLayout}/FormLayout.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
|
@@ -3,76 +3,13 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { format as formatDate } from 'date-fns';
|
|
6
|
-
import type
|
|
7
|
-
import type * as SchemaAST from 'effect/SchemaAST';
|
|
8
|
-
import React, {
|
|
9
|
-
Component,
|
|
10
|
-
type FC,
|
|
11
|
-
type FocusEvent,
|
|
12
|
-
type PropsWithChildren,
|
|
13
|
-
type ReactElement,
|
|
14
|
-
type ReactNode,
|
|
15
|
-
} from 'react';
|
|
16
|
-
|
|
17
|
-
import { type Database, Format } from '@dxos/echo';
|
|
18
|
-
import { Icon, Input, ThemedClassName, Tooltip } from '@dxos/react-ui';
|
|
19
|
-
import { inputTextLabel } from '@dxos/react-ui';
|
|
20
|
-
import { mx } from '@dxos/ui-theme';
|
|
21
|
-
|
|
22
|
-
import { type FormFieldStatus } from '../../hooks';
|
|
23
|
-
import { useFormTooltips } from './FormTooltipsContext';
|
|
6
|
+
import React, { Component, type PropsWithChildren, type ReactNode } from 'react';
|
|
24
7
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export type FormFieldStateProps<T = any> = {
|
|
29
|
-
getStatus: () => FormFieldStatus;
|
|
30
|
-
getValue: () => T | undefined;
|
|
31
|
-
onBlur: (event: FocusEvent<HTMLElement>) => void;
|
|
32
|
-
onValueChange: (type: SchemaAST.AST, value: T) => void;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Presentation mode.
|
|
37
|
-
* - full: Show label, control, and status.
|
|
38
|
-
* - compact: Show label and control.
|
|
39
|
-
* - inline: Control only.
|
|
40
|
-
* - static: Plain DOM; omit all undefined values.
|
|
41
|
-
*/
|
|
42
|
-
// TODO(burdon): 'inline' should be orthogonal to the layout (e.g., array of inline static).
|
|
43
|
-
export type Presentation = 'full' | 'compact' | 'inline' | 'static';
|
|
8
|
+
import { Format } from '@dxos/echo';
|
|
9
|
+
import { inputTextLabel, Icon, Input, ThemedClassName, Tooltip } from '@dxos/react-ui';
|
|
10
|
+
import { mx } from '@dxos/ui-theme';
|
|
44
11
|
|
|
45
|
-
|
|
46
|
-
* Props passed to input components.
|
|
47
|
-
*/
|
|
48
|
-
export type FormFieldComponentProps<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
|
-
* Dotted JSON path of this field within the form values (e.g.
|
|
57
|
-
* `runtime.client.storage.persistent`). Forwarded to `FormFieldLabel` so
|
|
58
|
-
* that it can render the path as a debug hint when the enclosing
|
|
59
|
-
* `Form.Root` has `debug` enabled.
|
|
60
|
-
*/
|
|
61
|
-
jsonPath?: string;
|
|
62
|
-
placeholder?: string;
|
|
63
|
-
autoFocus?: boolean;
|
|
64
|
-
layout?: Presentation;
|
|
65
|
-
} & FormFieldStateProps<T>;
|
|
66
|
-
|
|
67
|
-
export type FormFieldComponent = FC<FormFieldComponentProps>;
|
|
68
|
-
|
|
69
|
-
export type FormFieldMap = Record<string, FormFieldComponent>;
|
|
70
|
-
|
|
71
|
-
export type FormFieldProvider = (props: {
|
|
72
|
-
prop: string;
|
|
73
|
-
schema: Schema.Schema<any>;
|
|
74
|
-
fieldProps: FormFieldComponentProps;
|
|
75
|
-
}) => ReactElement | null | undefined;
|
|
12
|
+
import { type FormFieldRendererProps } from '#types';
|
|
76
13
|
|
|
77
14
|
//
|
|
78
15
|
// FormFieldLabel
|
|
@@ -80,15 +17,12 @@ export type FormFieldProvider = (props: {
|
|
|
80
17
|
|
|
81
18
|
export type FormFieldLabelProps = ThemedClassName<
|
|
82
19
|
{
|
|
83
|
-
|
|
20
|
+
/** Render a plain `<span>` instead of an input-associated `Input.Label`, for labels used outside an `Input.Root` (e.g. section/group headers). */
|
|
21
|
+
standalone?: boolean;
|
|
84
22
|
error?: string;
|
|
85
23
|
/**
|
|
86
24
|
* JSON path of the field this label describes (e.g. `runtime.client.storage.persistent`).
|
|
87
|
-
*
|
|
88
|
-
* has `tooltips` enabled (the default) -- useful for spotting which field
|
|
89
|
-
* maps to which path when authoring schemas or filing bugs against a form.
|
|
90
|
-
* Callers can supply the path unconditionally; the label suppresses the
|
|
91
|
-
* tooltip when `Form.Root tooltips={false}`.
|
|
25
|
+
* Field metadata; accepted by callers but not currently rendered.
|
|
92
26
|
*/
|
|
93
27
|
path?: string;
|
|
94
28
|
/**
|
|
@@ -97,7 +31,7 @@ export type FormFieldLabelProps = ThemedClassName<
|
|
|
97
31
|
*/
|
|
98
32
|
button?: ReactNode;
|
|
99
33
|
onClick?: () => void;
|
|
100
|
-
} & Pick<
|
|
34
|
+
} & Pick<FormFieldRendererProps, 'label' | 'readonly'>
|
|
101
35
|
>;
|
|
102
36
|
|
|
103
37
|
export const FormFieldLabel = ({
|
|
@@ -105,13 +39,11 @@ export const FormFieldLabel = ({
|
|
|
105
39
|
label,
|
|
106
40
|
error,
|
|
107
41
|
readonly,
|
|
108
|
-
|
|
109
|
-
path,
|
|
42
|
+
standalone,
|
|
110
43
|
button,
|
|
111
44
|
onClick,
|
|
112
45
|
}: FormFieldLabelProps) => {
|
|
113
|
-
const
|
|
114
|
-
const Label = readonly || asChild ? 'span' : Input.Label;
|
|
46
|
+
const Label = readonly || standalone ? 'span' : Input.Label;
|
|
115
47
|
const labelNode = <Label className={mx(inputTextLabel, 'text-sm')}>{label}</Label>;
|
|
116
48
|
|
|
117
49
|
return (
|
|
@@ -119,13 +51,7 @@ export const FormFieldLabel = ({
|
|
|
119
51
|
className={mx('grid grid-cols-[1fr_auto_auto] items-center select-none', onClick && 'cursor-pointer', classNames)}
|
|
120
52
|
onClick={onClick}
|
|
121
53
|
>
|
|
122
|
-
{
|
|
123
|
-
<Tooltip.Trigger asChild content={path} side='bottom'>
|
|
124
|
-
{labelNode}
|
|
125
|
-
</Tooltip.Trigger>
|
|
126
|
-
) : (
|
|
127
|
-
labelNode
|
|
128
|
-
)}
|
|
54
|
+
{labelNode}
|
|
129
55
|
{error ? (
|
|
130
56
|
<Tooltip.Trigger asChild content={error} side='bottom'>
|
|
131
57
|
<Icon icon='ph--warning--regular' size={4} classNames='text-error-text' />
|
|
@@ -144,13 +70,6 @@ FormFieldLabel.displayName = 'Form.FieldLabel';
|
|
|
144
70
|
// FormFieldWrapper
|
|
145
71
|
//
|
|
146
72
|
|
|
147
|
-
export type FormFieldWrapperProps<T = any> = Pick<
|
|
148
|
-
FormFieldComponentProps,
|
|
149
|
-
'readonly' | 'label' | 'layout' | 'getStatus' | 'getValue' | 'jsonPath' | 'format'
|
|
150
|
-
> & {
|
|
151
|
-
children?: (props: { value: T }) => ReactNode;
|
|
152
|
-
};
|
|
153
|
-
|
|
154
73
|
/**
|
|
155
74
|
* Formats a value for `static` (read-only, plain-DOM) presentation based on its
|
|
156
75
|
* type `format`. Dates/times are rendered human-readable; everything else falls
|
|
@@ -177,6 +96,13 @@ const formatStaticValue = (value: unknown, format?: Format.TypeFormat): string =
|
|
|
177
96
|
}
|
|
178
97
|
};
|
|
179
98
|
|
|
99
|
+
export type FormFieldWrapperProps<T = any> = Pick<
|
|
100
|
+
FormFieldRendererProps,
|
|
101
|
+
'readonly' | 'label' | 'layout' | 'getStatus' | 'getValue' | 'jsonPath' | 'format'
|
|
102
|
+
> & {
|
|
103
|
+
children?: (props: { value: T }) => ReactNode;
|
|
104
|
+
};
|
|
105
|
+
|
|
180
106
|
export const FormFieldWrapper = <T,>(props: FormFieldWrapperProps<T>) => {
|
|
181
107
|
const { children, readonly, layout, label, jsonPath, format, getStatus, getValue } = props;
|
|
182
108
|
const { status, error } = getStatus();
|
|
@@ -237,8 +163,8 @@ export class FormFieldErrorBoundary extends Component<FormFieldErrorBoundaryProp
|
|
|
237
163
|
override render() {
|
|
238
164
|
if (this.state.error) {
|
|
239
165
|
return (
|
|
240
|
-
<div className='flex gap-2 border border-
|
|
241
|
-
<span className='bg-
|
|
166
|
+
<div className='flex gap-2 border border-error-border font-mono text-sm'>
|
|
167
|
+
<span className='bg-error-bg text-base-fg px-1 font-thin'>ERROR</span>
|
|
242
168
|
{String(this.props.path?.join('.'))}
|
|
243
169
|
</div>
|
|
244
170
|
);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import React, { useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { FormLayoutAnnotation, FormOrderedAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
import { trim } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
import { translations } from '#translations';
|
|
14
|
+
|
|
15
|
+
import { TestLayout } from '../../../../../testing';
|
|
16
|
+
import { Form } from '../../../Form';
|
|
17
|
+
|
|
18
|
+
// Mirrors the structure of `Pipeline` (`@dxos/types`): a scalar header plus an
|
|
19
|
+
// array of `Column`-like structs. The array is the field we render as either a
|
|
20
|
+
// static or an ordered (drag-to-reorder) list.
|
|
21
|
+
const Column = Schema.Struct({
|
|
22
|
+
name: Schema.String.annotations({ title: 'Name' }),
|
|
23
|
+
value: Schema.optional(Schema.Number.annotations({ title: 'Value' })),
|
|
24
|
+
}).pipe(
|
|
25
|
+
Schema.mutable,
|
|
26
|
+
LabelAnnotation.set(['name']),
|
|
27
|
+
FormLayoutAnnotation.set({
|
|
28
|
+
default: trim`
|
|
29
|
+
<grid cols="2">
|
|
30
|
+
<field name="name"/>
|
|
31
|
+
<field name="value"/>
|
|
32
|
+
</grid>
|
|
33
|
+
`,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
type Column = Schema.Schema.Type<typeof Column>;
|
|
37
|
+
|
|
38
|
+
const columnsField = Schema.Array(Column).pipe(Schema.mutable, Schema.annotations({ title: 'Columns' }));
|
|
39
|
+
|
|
40
|
+
const headerFields = {
|
|
41
|
+
name: Schema.String.pipe(Schema.annotations({ title: 'Name' }), Schema.optional),
|
|
42
|
+
description: Schema.String.pipe(Schema.annotations({ title: 'Description' }), Schema.optional),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const Pipeline = Schema.Struct({
|
|
46
|
+
...headerFields,
|
|
47
|
+
columns: columnsField,
|
|
48
|
+
}).pipe(Schema.mutable);
|
|
49
|
+
|
|
50
|
+
// `FormOrderedAnnotation` opts the array into a drag-to-reorder list (see `ArrayField`).
|
|
51
|
+
const OrderedPipeline = Schema.Struct({
|
|
52
|
+
...headerFields,
|
|
53
|
+
columns: columnsField.pipe(FormOrderedAnnotation.set(true)),
|
|
54
|
+
}).pipe(Schema.mutable);
|
|
55
|
+
|
|
56
|
+
type Pipeline = Schema.Schema.Type<typeof Pipeline>;
|
|
57
|
+
|
|
58
|
+
const initialValues: Pipeline = {
|
|
59
|
+
name: 'Sales',
|
|
60
|
+
description: 'Lead qualification pipeline.',
|
|
61
|
+
columns: [{ name: 'Contacts' }, { name: 'Organizations' }, { name: 'Tasks' }, { name: 'Messages', value: 100 }],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const StoryComponent = ({ schema }: { schema: typeof Pipeline }) => {
|
|
65
|
+
const [values, setValues] = useState<Pipeline>(initialValues);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<TestLayout json={values}>
|
|
69
|
+
<Form.Root
|
|
70
|
+
schema={schema}
|
|
71
|
+
values={values}
|
|
72
|
+
onValuesChanged={(values) => setValues((prev) => ({ ...prev, ...values }))}
|
|
73
|
+
>
|
|
74
|
+
<Form.Viewport scroll>
|
|
75
|
+
<Form.Content>
|
|
76
|
+
<Form.FieldSet />
|
|
77
|
+
</Form.Content>
|
|
78
|
+
</Form.Viewport>
|
|
79
|
+
</Form.Root>
|
|
80
|
+
</TestLayout>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const meta = {
|
|
85
|
+
title: 'ui/react-ui-form/FormField/ArrayField',
|
|
86
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
87
|
+
parameters: {
|
|
88
|
+
layout: 'fullscreen',
|
|
89
|
+
translations,
|
|
90
|
+
},
|
|
91
|
+
} satisfies Meta<typeof StoryComponent>;
|
|
92
|
+
|
|
93
|
+
export default meta;
|
|
94
|
+
|
|
95
|
+
type Story = StoryObj<typeof meta>;
|
|
96
|
+
|
|
97
|
+
export const Default: Story = {
|
|
98
|
+
render: () => <StoryComponent schema={Pipeline} />,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const Ordered: Story = {
|
|
102
|
+
render: () => <StoryComponent schema={OrderedPipeline} />,
|
|
103
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Option from 'effect/Option';
|
|
6
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
+
import React, { type ReactNode, useCallback, useRef } from 'react';
|
|
8
|
+
|
|
9
|
+
import { Annotation, Ref } from '@dxos/echo';
|
|
10
|
+
import { SchemaEx } from '@dxos/effect';
|
|
11
|
+
import { useTranslation } from '@dxos/react-ui';
|
|
12
|
+
import { OrderedList } from '@dxos/react-ui-list';
|
|
13
|
+
import { arrayMove } from '@dxos/util';
|
|
14
|
+
|
|
15
|
+
import { translationKey } from '#translations';
|
|
16
|
+
import { type FormFieldStateProps } from '#types';
|
|
17
|
+
|
|
18
|
+
import { useFormValues } from '../../../../../hooks';
|
|
19
|
+
import { getFormProperties } from '../../../../../util';
|
|
20
|
+
import { FieldHeader } from '../../FieldHeader';
|
|
21
|
+
import { CompactIconButton, FormField, type FormFieldProps } from '../../FormField';
|
|
22
|
+
|
|
23
|
+
// Synthetic id assigned to each row when rendering an ordered list. Plain form
|
|
24
|
+
// values have no stable identity, so drag-and-drop (which requires a stable key
|
|
25
|
+
// surviving the `onValuesChanged` round-trip) is given one here and kept in
|
|
26
|
+
// lockstep with the array via the add/delete/move handlers.
|
|
27
|
+
const DND_ID = '__dndId';
|
|
28
|
+
|
|
29
|
+
type OrderedItem = {
|
|
30
|
+
[DND_ID]: string;
|
|
31
|
+
value: unknown;
|
|
32
|
+
index: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const isOrderedItem = (item: any): item is OrderedItem =>
|
|
36
|
+
!!item && typeof item === 'object' && typeof item[DND_ID] === 'string';
|
|
37
|
+
|
|
38
|
+
const getOrderedId = (item: OrderedItem): string => item[DND_ID];
|
|
39
|
+
|
|
40
|
+
export type ArrayFieldProps = {
|
|
41
|
+
label: string;
|
|
42
|
+
fieldProps: FormFieldStateProps;
|
|
43
|
+
} & FormFieldProps;
|
|
44
|
+
|
|
45
|
+
export const ArrayField = ({
|
|
46
|
+
type,
|
|
47
|
+
path,
|
|
48
|
+
label,
|
|
49
|
+
readonly,
|
|
50
|
+
layout,
|
|
51
|
+
fieldProps: inputProps,
|
|
52
|
+
...props
|
|
53
|
+
}: ArrayFieldProps) => {
|
|
54
|
+
const { t } = useTranslation(translationKey);
|
|
55
|
+
const elementType = SchemaEx.getArrayElementType(type);
|
|
56
|
+
const { onValueChange } = inputProps;
|
|
57
|
+
const values = useFormValues(ArrayField.displayName, path, () => []);
|
|
58
|
+
|
|
59
|
+
// Order is meaningful and user-controllable -> render as a drag-to-reorder list.
|
|
60
|
+
const ordered = Annotation.FormOrderedAnnotation.getFromAst(type).pipe(Option.getOrElse(() => false));
|
|
61
|
+
|
|
62
|
+
// Synthetic identity map (see `DND_ID`). The counter only advances when the
|
|
63
|
+
// array grows; reconciliation here covers the initial mount and any external
|
|
64
|
+
// length change, while the handlers below keep ids aligned on mutation.
|
|
65
|
+
const idsRef = useRef<string[]>([]);
|
|
66
|
+
const counterRef = useRef(0);
|
|
67
|
+
const nextId = useCallback(() => `${DND_ID}-${counterRef.current++}`, []);
|
|
68
|
+
|
|
69
|
+
const getDefaultObjectValue = (typeNode: SchemaAST.AST): any => {
|
|
70
|
+
const baseNode = SchemaEx.findNode(typeNode, SchemaEx.isDiscriminatedUnion);
|
|
71
|
+
const typeLiteral = baseNode
|
|
72
|
+
? SchemaEx.getDiscriminatedType(baseNode, {})
|
|
73
|
+
: SchemaEx.findNode(typeNode, SchemaAST.isTypeLiteral);
|
|
74
|
+
if (!typeLiteral) {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Object.fromEntries(
|
|
79
|
+
getFormProperties(typeLiteral).map((prop) => {
|
|
80
|
+
const defaultValue = SchemaAST.getDefaultAnnotation(prop.type).pipe((annotation) =>
|
|
81
|
+
Option.getOrUndefined(annotation),
|
|
82
|
+
);
|
|
83
|
+
return [prop.name, defaultValue];
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleAdd = useCallback(() => {
|
|
89
|
+
const defaultValue =
|
|
90
|
+
elementType && SchemaEx.isNestedType(elementType)
|
|
91
|
+
? getDefaultObjectValue(elementType)
|
|
92
|
+
: getDefaultValue(elementType);
|
|
93
|
+
idsRef.current.push(nextId());
|
|
94
|
+
// `values` is `undefined` on first render for arrays whose parent path
|
|
95
|
+
// hasn't been materialised in the form values yet (e.g. `package.repos`
|
|
96
|
+
// when the form starts with no `package`). `useFormValues`'s default-
|
|
97
|
+
// value effect would eventually backfill it to `[]`, but until then the
|
|
98
|
+
// old `values && ...` guard silently swallowed the click. Treat a
|
|
99
|
+
// missing array as empty so Add always produces `[defaultValue]`.
|
|
100
|
+
onValueChange(type, [...(values ?? []), defaultValue]);
|
|
101
|
+
}, [onValueChange, type, elementType, values, nextId]);
|
|
102
|
+
|
|
103
|
+
const handleDelete = useCallback(
|
|
104
|
+
(idx: number) => {
|
|
105
|
+
idsRef.current.splice(idx, 1);
|
|
106
|
+
onValueChange(
|
|
107
|
+
type,
|
|
108
|
+
values?.filter((_, i) => i !== idx),
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
[onValueChange, type, values],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const handleMove = useCallback(
|
|
115
|
+
(fromIndex: number, toIndex: number) => {
|
|
116
|
+
const next = [...(values ?? [])];
|
|
117
|
+
arrayMove(next, fromIndex, toIndex);
|
|
118
|
+
arrayMove(idsRef.current, fromIndex, toIndex);
|
|
119
|
+
onValueChange(type, next);
|
|
120
|
+
},
|
|
121
|
+
[onValueChange, type, values],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (!elementType || ((readonly || layout === 'static') && values && values.length < 1)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const renderItemAsObject = elementType && SchemaEx.isNestedType(elementType) && !Ref.isRefType(elementType);
|
|
129
|
+
|
|
130
|
+
// Object items: a recursively-rendered FormField (multiple sub-rows for the
|
|
131
|
+
// object's fields). Scalar items (refs, primitives): a single inline FormField.
|
|
132
|
+
const renderField = (index: number, isLast: boolean): ReactNode => (
|
|
133
|
+
<FormField
|
|
134
|
+
{...props}
|
|
135
|
+
autoFocus={isLast}
|
|
136
|
+
type={elementType}
|
|
137
|
+
// Suppress the per-item header for object items only — the recursive
|
|
138
|
+
// form already renders labels for each sub-field. Scalar items
|
|
139
|
+
// (refs, primitives) keep the parent name so inline-layout children
|
|
140
|
+
// (e.g. RefField) have a real label to use as a fallback placeholder.
|
|
141
|
+
{...(renderItemAsObject && { name: null })}
|
|
142
|
+
path={[...(path ?? []), index]}
|
|
143
|
+
readonly={readonly || layout === 'static'}
|
|
144
|
+
layout={renderItemAsObject ? (layout === 'static' ? 'static' : undefined) : 'inline'}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const header = (layout !== 'static' || (values && values.length > 0)) && (
|
|
149
|
+
<FieldHeader
|
|
150
|
+
label={label}
|
|
151
|
+
path={SchemaEx.createJsonPath(path ?? [])}
|
|
152
|
+
readonly={readonly}
|
|
153
|
+
add={layout !== 'static' ? { label: t('add-item.button'), onClick: handleAdd } : undefined}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
//
|
|
158
|
+
// Ordered (drag-to-reorder) list.
|
|
159
|
+
//
|
|
160
|
+
|
|
161
|
+
if (ordered && !readonly && layout !== 'static') {
|
|
162
|
+
// Reconcile synthetic ids with the current values (initial mount / external change).
|
|
163
|
+
const ids = idsRef.current;
|
|
164
|
+
const count = values?.length ?? 0;
|
|
165
|
+
while (ids.length < count) {
|
|
166
|
+
ids.push(nextId());
|
|
167
|
+
}
|
|
168
|
+
if (ids.length > count) {
|
|
169
|
+
ids.length = count;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const items: OrderedItem[] = (values ?? []).map((value, index) => ({ value, index, [DND_ID]: ids[index] }));
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<>
|
|
176
|
+
{header}
|
|
177
|
+
<OrderedList.Root<OrderedItem> items={items} isItem={isOrderedItem} getId={getOrderedId} onMove={handleMove}>
|
|
178
|
+
{({ items }) => (
|
|
179
|
+
<OrderedList.Content>
|
|
180
|
+
{items.map((item) => (
|
|
181
|
+
<OrderedList.Item<OrderedItem>
|
|
182
|
+
key={getOrderedId(item)}
|
|
183
|
+
id={getOrderedId(item)}
|
|
184
|
+
item={item}
|
|
185
|
+
classNames='grid grid-cols-[min-content_1fr_min-content] items-start gap-1 p-0 pb-1'
|
|
186
|
+
>
|
|
187
|
+
<OrderedList.DragHandle />
|
|
188
|
+
{renderField(item.index, item.index === items.length - 1)}
|
|
189
|
+
<CompactIconButton
|
|
190
|
+
icon='ph--x--regular'
|
|
191
|
+
label={t('remove-item.button')}
|
|
192
|
+
onClick={() => handleDelete(item.index)}
|
|
193
|
+
/>
|
|
194
|
+
</OrderedList.Item>
|
|
195
|
+
))}
|
|
196
|
+
</OrderedList.Content>
|
|
197
|
+
)}
|
|
198
|
+
</OrderedList.Root>
|
|
199
|
+
</>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//
|
|
204
|
+
// Static list.
|
|
205
|
+
//
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<>
|
|
209
|
+
{header}
|
|
210
|
+
<div className='flex flex-col'>
|
|
211
|
+
{values?.map((_, index) => {
|
|
212
|
+
const isLast = index === values.length - 1;
|
|
213
|
+
return (
|
|
214
|
+
<div key={index} className='grid grid-cols-[1fr_min-content] items-center mb-1 last:mb-form-gap'>
|
|
215
|
+
{renderField(index, isLast)}
|
|
216
|
+
{!readonly && layout !== 'static' && (
|
|
217
|
+
<CompactIconButton
|
|
218
|
+
icon='ph--x--regular'
|
|
219
|
+
label={t('remove-item.button')}
|
|
220
|
+
onClick={() => handleDelete(index)}
|
|
221
|
+
/>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
})}
|
|
226
|
+
</div>
|
|
227
|
+
</>
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
ArrayField.displayName = 'Form.ArrayField';
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Returns the default empty value for a given AST.
|
|
235
|
+
* Used for initializing new array values etc.
|
|
236
|
+
*/
|
|
237
|
+
// TODO(wittjosiah): Factor out?
|
|
238
|
+
export const getDefaultValue = (ast?: SchemaAST.AST): any => {
|
|
239
|
+
switch (ast?._tag) {
|
|
240
|
+
case 'StringKeyword': {
|
|
241
|
+
return '';
|
|
242
|
+
}
|
|
243
|
+
case 'NumberKeyword': {
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
case 'BooleanKeyword': {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
case 'Suspend': {
|
|
250
|
+
return getDefaultValue(ast.f());
|
|
251
|
+
}
|
|
252
|
+
case 'Refinement': {
|
|
253
|
+
// Use minimum from JSON schema annotation (e.g. Schema.between(1, 31)) as the default
|
|
254
|
+
// so new array items start within the valid range.
|
|
255
|
+
const jsonSchema = Option.getOrUndefined(SchemaAST.getJSONSchemaAnnotation(ast));
|
|
256
|
+
if (jsonSchema != null && 'minimum' in jsonSchema && typeof jsonSchema.minimum === 'number') {
|
|
257
|
+
return jsonSchema.minimum;
|
|
258
|
+
}
|
|
259
|
+
return getDefaultValue(ast.from);
|
|
260
|
+
}
|
|
261
|
+
default: {
|
|
262
|
+
if (ast && SchemaEx.isNestedType(ast)) {
|
|
263
|
+
return {};
|
|
264
|
+
} else {
|
|
265
|
+
throw new Error(`Unsupported type: ${ast?._tag}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import React, { useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
|
|
11
|
+
import { translations } from '#translations';
|
|
12
|
+
|
|
13
|
+
import { TestLayout } from '../../../../../testing';
|
|
14
|
+
import { Form } from '../../../Form';
|
|
15
|
+
|
|
16
|
+
const schema = Schema.Struct({
|
|
17
|
+
active: Schema.Boolean.annotations({ title: 'Active' }),
|
|
18
|
+
}).pipe(Schema.mutable);
|
|
19
|
+
|
|
20
|
+
type Values = Schema.Schema.Type<typeof schema>;
|
|
21
|
+
|
|
22
|
+
const DefaultStory = () => {
|
|
23
|
+
const [values, setValues] = useState<Values>({ active: true });
|
|
24
|
+
return (
|
|
25
|
+
<TestLayout json={values}>
|
|
26
|
+
<Form.Root
|
|
27
|
+
schema={schema}
|
|
28
|
+
values={values}
|
|
29
|
+
onValuesChanged={(values) => setValues((prev) => ({ ...prev, ...values }))}
|
|
30
|
+
>
|
|
31
|
+
<Form.Viewport>
|
|
32
|
+
<Form.Content>
|
|
33
|
+
<Form.FieldSet />
|
|
34
|
+
</Form.Content>
|
|
35
|
+
</Form.Viewport>
|
|
36
|
+
</Form.Root>
|
|
37
|
+
</TestLayout>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const meta = {
|
|
42
|
+
title: 'ui/react-ui-form/FormField/BooleanField',
|
|
43
|
+
render: DefaultStory,
|
|
44
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
45
|
+
parameters: { layout: 'fullscreen', translations },
|
|
46
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
47
|
+
|
|
48
|
+
export default meta;
|
|
49
|
+
|
|
50
|
+
type Story = StoryObj<typeof meta>;
|
|
51
|
+
|
|
52
|
+
export const Default: Story = {};
|
|
@@ -6,9 +6,11 @@ import React, { useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { Input, type SwitchProps } from '@dxos/react-ui';
|
|
8
8
|
|
|
9
|
-
import { type
|
|
9
|
+
import { type FormFieldRendererProps } from '#types';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import { FormFieldWrapper } from '../../FormFieldWrapper';
|
|
12
|
+
|
|
13
|
+
export const BooleanField = ({ type, readonly, onValueChange, ...props }: FormFieldRendererProps<boolean>) => {
|
|
12
14
|
const handleChange = useCallback<NonNullable<SwitchProps['onCheckedChange']>>(
|
|
13
15
|
(value) => onValueChange?.(type, value),
|
|
14
16
|
[type, onValueChange],
|
|
@@ -16,7 +18,12 @@ export const BooleanField = ({ type, readonly, onValueChange, ...props }: FormFi
|
|
|
16
18
|
|
|
17
19
|
return (
|
|
18
20
|
<FormFieldWrapper<boolean> readonly={readonly} {...props}>
|
|
19
|
-
{({ value }) =>
|
|
21
|
+
{({ value }) => (
|
|
22
|
+
// TODO(burdon) Push down to react-ui components (e.g., Input.Root).
|
|
23
|
+
<div className='flex items-center px-0.5 h-8'>
|
|
24
|
+
<Input.Switch disabled={!!readonly} checked={value} onCheckedChange={handleChange} />
|
|
25
|
+
</div>
|
|
26
|
+
)}
|
|
20
27
|
</FormFieldWrapper>
|
|
21
28
|
);
|
|
22
29
|
};
|