From d67f53aa5b05c052224f2ad8c991ab1f6f32128a Mon Sep 17 00:00:00 2001 From: Janry Date: Thu, 24 Oct 2019 12:41:02 +0800 Subject: [PATCH] [V1 UMBRELLA] (#346) --- .eslintrc | 2 +- docs/Examples/antd/List.md | 68 +- docs/Examples/antd/Relations.md | 3 +- docs/Examples/antd/Sample.md | 3 +- docs/Examples/next/List.md | 7 +- docs/Examples/next/Relations.md | 5 +- package.json | 11 +- packages/.eslintrc | 24 +- packages/antd/build.ts | 12 +- packages/antd/package.json | 12 +- packages/antd/src/compat/Form.tsx | 22 + packages/antd/src/compat/FormItem.tsx | 99 + packages/antd/src/compat/context.tsx | 35 + packages/antd/src/compat/index.ts | 10 + packages/antd/src/components/FormBlock.tsx | 22 + packages/antd/src/components/FormCard.tsx | 18 + packages/antd/src/components/FormItemGrid.tsx | 87 + packages/antd/src/components/FormLayout.tsx | 27 + packages/antd/src/components/FormStep.tsx | 98 + packages/antd/src/components/FormTextBox.tsx | 104 + packages/antd/src/components/Select.tsx | 39 + packages/antd/src/components/button.tsx | 81 +- .../antd/src/components/formButtonGroup.tsx | 164 +- packages/antd/src/components/grid.tsx | 175 - packages/antd/src/components/index.ts | 8 + packages/antd/src/components/layout.tsx | 326 -- packages/antd/src/fields/array.tsx | 177 - packages/antd/src/fields/boolean.tsx | 4 +- packages/antd/src/fields/cards.tsx | 267 +- packages/antd/src/fields/checkbox.tsx | 4 +- packages/antd/src/fields/date.tsx | 54 +- packages/antd/src/fields/index.ts | 15 + packages/antd/src/fields/number.tsx | 4 +- packages/antd/src/fields/password.tsx | 243 +- packages/antd/src/fields/radio.tsx | 4 +- packages/antd/src/fields/range.tsx | 4 +- packages/antd/src/fields/rating.tsx | 4 +- packages/antd/src/fields/string.tsx | 4 +- packages/antd/src/fields/table.tsx | 484 +- packages/antd/src/fields/textarea.tsx | 4 +- packages/antd/src/fields/time.tsx | 6 +- packages/antd/src/fields/transfer.tsx | 4 +- packages/antd/src/fields/upload.tsx | 4 +- packages/antd/src/form.tsx | 481 -- packages/antd/src/index.tsx | 57 +- packages/antd/src/locale.ts | 7 - packages/antd/src/shared.ts | 75 + packages/antd/src/type.tsx | 186 - packages/antd/src/types.ts | 90 + packages/antd/src/utils.tsx | 253 - packages/builder-next/README.md | 2 - packages/builder-next/src/index.js | 138 - packages/builder-next/tsconfig.json | 11 - packages/builder/.npmignore | 8 - packages/builder/LISENCE.md | 20 - packages/builder/README.md | 2 - packages/builder/package.json | 62 - packages/builder/src/App.js | 366 -- packages/builder/src/actions/index.js | 154 - .../builder/src/components/editor/index.js | 137 - .../builder/src/components/editor/style.js | 33 - .../builder/src/components/fields/field.js | 103 - .../builder/src/components/fields/index.js | 58 - .../builder/src/components/fields/layout.js | 63 - .../src/components/fields/layoutField.js | 85 - .../builder/src/components/fields/style.js | 87 - .../src/components/globalBtnList/index.js | 115 - packages/builder/src/components/index.js | 8 - .../builder/src/components/preview/card.js | 191 - .../src/components/preview/fieldMiddleware.js | 45 - .../builder/src/components/preview/index.js | 63 - .../builder/src/components/preview/mainBox.js | 133 - .../builder/src/components/preview/style.js | 161 - .../src/components/props/colsDetail.js | 53 - .../props/dataSourceEditor/index.js | 42 - .../props/defaultValueCascader/index.js | 36 - .../fieldAttrEditors/dataSourceEditor.js | 310 - .../fieldAttrEditors/dataSourceEnum.js | 126 - .../defaultValueEditor/arrayDefaultEditor.js | 41 - .../defaultValueEditor/boolDefaultEditor.js | 29 - .../defaultValueEditor/dateDefaultEditor.js | 79 - .../dateRangeDefaultEditor.js | 47 - .../dateTimeDefaultEditor.js | 52 - .../dateTimeRangeDefaultEditor.js | 35 - .../defaultValueGenerator.js | 110 - .../defaultValueEditor/index.js | 118 - .../defaultValueEditor/monthDefaultEditor.js | 80 - .../defaultValueEditor/stringDefaultEditor.js | 39 - .../src/components/props/fileSetting.js | 196 - .../src/components/props/propsSetting.js | 263 - .../builder/src/components/props/style.js | 60 - packages/builder/src/configs/index.js | 7 - .../builder/src/configs/supportConfigList.js | 284 - .../builder/src/configs/supportFieldList.js | 246 - .../src/configs/supportGlobalCfgList.js | 80 - .../builder/src/configs/supportLayoutList.js | 44 - packages/builder/src/configs/theme.js | 57 - packages/builder/src/constants/context.js | 5 - packages/builder/src/constants/itemType.js | 5 - packages/builder/src/demo/index-1-x.js | 147 - packages/builder/src/demo/index.js | 113 - packages/builder/src/index.js | 87 - packages/builder/src/reducers/codemode.js | 12 - packages/builder/src/reducers/componentId.js | 12 - .../builder/src/reducers/componentProps.js | 50 - packages/builder/src/reducers/gbConfig.js | 17 - packages/builder/src/reducers/index.js | 17 - .../builder/src/reducers/initSchemaData.js | 193 - packages/builder/src/reducers/preview.js | 12 - packages/builder/src/style.js | 174 - packages/builder/src/utils/arg.js | 313 - packages/builder/src/utils/baseForm.js | 4 - packages/builder/src/utils/comp.js | 58 - packages/builder/src/utils/lang.js | 143 - packages/builder/src/utils/util.js | 267 - packages/builder/tsconfig.json | 11 - packages/core/README.md | 30 +- packages/core/package.json | 13 +- packages/core/src/__test__/form.spec.js | 46 - .../__snapshots__/index.spec.ts.snap | 5106 +++++++++++++++++ .../core/src/__tests__/field.state.spec.ts | 32 + .../core/src/__tests__/form.state.spec.ts | 32 + packages/core/src/__tests__/graph.spec.ts | 49 + packages/core/src/__tests__/index.spec.ts | 1130 ++++ packages/core/src/__tests__/lifecycle.spec.ts | 13 + packages/core/src/__tests__/model.spec.ts | 71 + .../core/src/__tests__/vfield.state.spec.ts | 32 + packages/core/src/field.ts | 586 -- packages/core/src/form.ts | 847 --- packages/core/src/index.ts | 1050 +++- packages/core/src/path.ts | 81 - packages/core/src/shared/graph.ts | 270 + packages/core/src/shared/lifecycle.ts | 113 + packages/core/src/shared/model.ts | 180 + packages/core/src/shared/subscrible.ts | 29 + packages/core/src/state/field.ts | 187 + packages/core/src/state/form.ts | 68 + packages/core/src/state/virtual-field.ts | 56 + packages/core/src/types.ts | 319 + packages/core/src/utils.ts | 164 - packages/next/README.md | 77 +- packages/next/build.ts | 12 +- packages/next/package.json | 21 +- packages/next/src/compat/Form.tsx | 21 + packages/next/src/compat/FormItem.tsx | 109 + packages/next/src/compat/context.tsx | 35 + packages/next/src/compat/index.ts | 10 + packages/next/src/components/FormBlock.tsx | 26 + packages/next/src/components/FormCard.tsx | 22 + packages/next/src/components/FormItemGrid.tsx | 89 + packages/next/src/components/FormLayout.tsx | 27 + packages/next/src/components/FormStep.tsx | 98 + packages/next/src/components/FormTextBox.tsx | 104 + packages/next/src/components/button.tsx | 90 +- .../next/src/components/formButtonGroup.tsx | 123 +- packages/next/src/components/index.ts | 8 + packages/next/src/components/layout.tsx | 312 - packages/next/src/fields/array.tsx | 180 - packages/next/src/fields/boolean.ts | 4 +- packages/next/src/fields/cards.tsx | 244 +- packages/next/src/fields/checkbox.ts | 4 +- .../next/src/fields/{date.tsx => date.ts} | 4 +- packages/next/src/fields/index.ts | 15 + packages/next/src/fields/number.ts | 4 +- packages/next/src/fields/password.tsx | 188 +- packages/next/src/fields/radio.ts | 4 +- packages/next/src/fields/range.ts | 4 +- packages/next/src/fields/rating.ts | 4 +- packages/next/src/fields/string.ts | 4 +- packages/next/src/fields/table.tsx | 505 +- packages/next/src/fields/textarea.ts | 4 +- packages/next/src/fields/time.ts | 4 +- packages/next/src/fields/transfer.ts | 4 +- packages/next/src/fields/upload.tsx | 4 +- packages/next/src/form.tsx | 434 -- packages/next/src/index.tsx | 58 +- packages/next/src/locale.ts | 7 - packages/next/src/shared.ts | 66 + packages/next/src/type.tsx | 160 - packages/next/src/types.ts | 95 + packages/next/src/utils.tsx | 113 - packages/next/tsconfig.json | 2 +- packages/printer/package.json | 4 +- packages/printer/src/index.js | 2 +- .../.npmignore | 0 .../LESENCE.md | 0 packages/react-schema-renderer/README.md | 2 + .../jest.config.js | 0 .../package.json | 27 +- .../src/__old_tests__}/actions.spec.js | 0 .../src/__old_tests__}/context.spec.js | 0 .../src/__old_tests__}/destruct.spec.js | 2 +- .../src/__old_tests__}/display.spec.js | 0 .../src/__old_tests__}/dynamic.spec.js | 2 +- .../src/__old_tests__}/editable.spec.js | 52 +- .../src/__old_tests__}/effects.spec.js | 0 .../src/__old_tests__}/mutators.spec.js | 0 .../src/__old_tests__}/schema_form.spec.js | 0 .../src/__old_tests__}/traverse.spec.js | 0 .../src/__old_tests__}/utils.spec.js | 0 .../src/__old_tests__}/validate.spec.js | 23 - .../__old_tests__}/validate_relations.spec.js | 0 .../src/__old_tests__}/value.spec.js | 34 +- .../src/__old_tests__}/virtualbox.spec.js | 0 .../src/__old_tests__}/visible.spec.js | 0 .../src/__old_tests__}/x-component.spec.js | 0 .../__snapshots__/markup.spec.tsx.snap | 218 + .../__snapshots__/register.spec.tsx.snap | 259 + .../src/__tests__/field.spec.tsx | 12 + .../src/__tests__/form.spec.tsx | 12 + .../src/__tests__/json-schema.spec.tsx | 12 + .../src/__tests__/markup.spec.tsx | 105 + .../src/__tests__/register.spec.tsx | 166 + .../src/components/SchemaField.tsx | 144 + .../src/components/SchemaForm.tsx | 49 + .../src/components/SchemaMarkup.tsx | 131 + .../src/hooks/useSchemaForm.ts | 71 + packages/react-schema-renderer/src/index.tsx | 16 + .../src/shared/actions.ts | 15 + .../src/shared/connect.ts | 123 + .../src/shared/context.ts | 7 + .../src/shared/registry.ts | 134 + .../src/shared/schema.ts | 392 ++ .../src/shared/virtual-render.tsx | 16 + packages/react-schema-renderer/src/types.ts | 180 + packages/react-schema-renderer/tsconfig.json | 8 + .../.npmignore | 0 .../LICENSE.md | 0 packages/react-shared-components/README.md | 2 + .../jest.config.js | 0 .../package.json | 8 +- .../react-shared-components/src/ArrayList.tsx | 232 + .../src/PasswordStrength.tsx | 156 + .../src/PreviewText.tsx | 31 + packages/react-shared-components/src/index.ts | 4 + packages/react-shared-components/src/types.ts | 88 + .../react-shared-components/tsconfig.json | 8 + packages/{types => react}/LICENSE.md | 0 packages/react/README.md | 299 +- packages/react/package.json | 22 +- packages/react/src/__tests__/actions.spec.tsx | 12 + .../react/src/__tests__/consumer.spec.tsx | 12 + packages/react/src/__tests__/effects.spec.tsx | 12 + packages/react/src/__tests__/field.spec.tsx | 12 + packages/react/src/__tests__/form.spec.tsx | 71 + .../react/src/__tests__/provider.spec.tsx | 12 + packages/react/src/__tests__/spy.spec.tsx | 12 + .../react/src/__tests__/useDirty.spec.tsx | 12 + .../react/src/__tests__/useField.spec.tsx | 12 + .../src/__tests__/useForceUpdate.spec.tsx | 12 + packages/react/src/__tests__/useForm.spec.tsx | 12 + packages/react/src/__tests__/virtual.spec.tsx | 12 + packages/react/src/components/Field.tsx | 53 + packages/react/src/components/Form.tsx | 18 + .../react/src/components/FormConsumer.tsx | 46 + .../react/src/components/FormProvider.tsx | 12 + packages/react/src/components/FormSpy.tsx | 73 + .../react/src/components/VirtualField.tsx | 26 + packages/react/src/context.ts | 7 + packages/react/src/decorators/connect.ts | 203 - packages/react/src/decorators/markup.tsx | 95 - packages/react/src/hooks/useDirty.ts | 17 + packages/react/src/hooks/useField.ts | 76 + packages/react/src/hooks/useForceUpdate.ts | 18 + packages/react/src/hooks/useForm.ts | 128 + packages/react/src/hooks/useVirtualField.ts | 71 + packages/react/src/index.ts | 24 + packages/react/src/index.tsx | 92 - packages/react/src/initialize/index.ts | 13 - packages/react/src/initialize/object.tsx | 25 - packages/react/src/initialize/render.tsx | 17 - packages/react/src/initialize/virtualbox.tsx | 18 - packages/react/src/shared.ts | 270 + packages/react/src/shared/array.tsx | 309 - packages/react/src/shared/broadcast.tsx | 167 - packages/react/src/shared/context.tsx | 16 - packages/react/src/shared/core.tsx | 133 - packages/react/src/shared/mutators.ts | 69 - packages/react/src/shared/virtualbox.tsx | 92 - packages/react/src/state/field.tsx | 225 - packages/react/src/state/form.tsx | 317 - packages/react/src/state/index.tsx | 6 - packages/react/src/type.ts | 67 - packages/react/src/types.ts | 181 + packages/react/src/utils.tsx | 37 - packages/{utils => shared}/.npmignore | 0 packages/{utils => shared}/LICENSE.md | 0 packages/{utils => shared}/README.md | 2 +- packages/shared/jest.config.js | 1 + packages/{types => shared}/package.json | 22 +- packages/shared/src/__tests__/index.spec.ts | 116 + packages/{utils => shared}/src/array.ts | 99 +- packages/{utils => shared}/src/broadcast.ts | 37 +- packages/{utils => shared}/src/case.ts | 0 packages/{utils => shared}/src/clone.ts | 13 +- packages/{utils => shared}/src/compare.ts | 75 +- packages/shared/src/deprecate.ts | 22 + packages/shared/src/global.ts | 19 + packages/shared/src/index.ts | 12 + packages/{utils => shared}/src/isEmpty.ts | 2 + packages/shared/src/merge.ts | 2 + packages/shared/src/path.ts | 3 + .../stringLength.ts => shared/src/string.ts} | 0 packages/{types => shared}/src/types.ts | 0 packages/shared/tsconfig.json | 8 + packages/types/README.md | 2 - packages/types/src/effects.ts | 9 - packages/types/src/field.ts | 68 - packages/types/src/form.ts | 193 - packages/types/src/index.ts | 8 - packages/types/src/path.ts | 3 - packages/types/src/rule.ts | 36 - packages/types/src/schema.ts | 28 - packages/types/src/validator.ts | 11 - packages/types/tsconfig.json | 13 - packages/utils/src/__tests__/index.spec.js | 325 -- packages/utils/src/accessor.ts | 594 -- packages/utils/src/defer.ts | 13 - packages/utils/src/globalThis.ts | 13 - packages/utils/src/index.ts | 13 - packages/utils/src/lru.ts | 310 - packages/utils/src/schema.ts | 135 - packages/utils/tsconfig.json | 13 - packages/validator/package.json | 6 +- .../validator/src/__tests__/index.spec.js | 46 - .../validator/src/__tests__/index.spec.ts | 185 + .../src/{validators/regexp.ts => formats.ts} | 0 packages/validator/src/index.ts | 140 +- packages/validator/src/locale.ts | 54 + packages/validator/src/locale/index.ts | 38 - packages/validator/src/message.ts | 32 +- packages/validator/src/rules.ts | 87 + packages/validator/src/types.ts | 94 + packages/validator/src/utils.ts | 38 - packages/validator/src/validator.ts | 277 + packages/validator/src/validators/custom.ts | 12 - packages/validator/src/validators/format.ts | 33 - packages/validator/src/validators/index.ts | 50 - packages/validator/src/validators/pattern.ts | 37 - packages/validator/src/validators/required.ts | 29 - scripts/jest.base.js | 1 + 341 files changed, 16623 insertions(+), 16767 deletions(-) create mode 100644 packages/antd/src/compat/Form.tsx create mode 100644 packages/antd/src/compat/FormItem.tsx create mode 100644 packages/antd/src/compat/context.tsx create mode 100644 packages/antd/src/compat/index.ts create mode 100644 packages/antd/src/components/FormBlock.tsx create mode 100644 packages/antd/src/components/FormCard.tsx create mode 100644 packages/antd/src/components/FormItemGrid.tsx create mode 100644 packages/antd/src/components/FormLayout.tsx create mode 100644 packages/antd/src/components/FormStep.tsx create mode 100644 packages/antd/src/components/FormTextBox.tsx create mode 100644 packages/antd/src/components/Select.tsx delete mode 100644 packages/antd/src/components/grid.tsx create mode 100644 packages/antd/src/components/index.ts delete mode 100644 packages/antd/src/components/layout.tsx delete mode 100644 packages/antd/src/fields/array.tsx create mode 100644 packages/antd/src/fields/index.ts delete mode 100644 packages/antd/src/form.tsx delete mode 100644 packages/antd/src/locale.ts create mode 100644 packages/antd/src/shared.ts delete mode 100644 packages/antd/src/type.tsx create mode 100644 packages/antd/src/types.ts delete mode 100644 packages/antd/src/utils.tsx delete mode 100644 packages/builder-next/README.md delete mode 100644 packages/builder-next/src/index.js delete mode 100644 packages/builder-next/tsconfig.json delete mode 100644 packages/builder/.npmignore delete mode 100644 packages/builder/LISENCE.md delete mode 100644 packages/builder/README.md delete mode 100644 packages/builder/package.json delete mode 100644 packages/builder/src/App.js delete mode 100644 packages/builder/src/actions/index.js delete mode 100644 packages/builder/src/components/editor/index.js delete mode 100644 packages/builder/src/components/editor/style.js delete mode 100644 packages/builder/src/components/fields/field.js delete mode 100644 packages/builder/src/components/fields/index.js delete mode 100644 packages/builder/src/components/fields/layout.js delete mode 100644 packages/builder/src/components/fields/layoutField.js delete mode 100644 packages/builder/src/components/fields/style.js delete mode 100644 packages/builder/src/components/globalBtnList/index.js delete mode 100644 packages/builder/src/components/index.js delete mode 100644 packages/builder/src/components/preview/card.js delete mode 100644 packages/builder/src/components/preview/fieldMiddleware.js delete mode 100644 packages/builder/src/components/preview/index.js delete mode 100644 packages/builder/src/components/preview/mainBox.js delete mode 100644 packages/builder/src/components/preview/style.js delete mode 100644 packages/builder/src/components/props/colsDetail.js delete mode 100644 packages/builder/src/components/props/dataSourceEditor/index.js delete mode 100644 packages/builder/src/components/props/defaultValueCascader/index.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEditor.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEnum.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/arrayDefaultEditor.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/boolDefaultEditor.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateDefaultEditor.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateRangeDefaultEditor.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateTimeDefaultEditor.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateTimeRangeDefaultEditor.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/defaultValueGenerator.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/index.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/monthDefaultEditor.js delete mode 100644 packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/stringDefaultEditor.js delete mode 100644 packages/builder/src/components/props/fileSetting.js delete mode 100644 packages/builder/src/components/props/propsSetting.js delete mode 100644 packages/builder/src/components/props/style.js delete mode 100644 packages/builder/src/configs/index.js delete mode 100644 packages/builder/src/configs/supportConfigList.js delete mode 100644 packages/builder/src/configs/supportFieldList.js delete mode 100644 packages/builder/src/configs/supportGlobalCfgList.js delete mode 100644 packages/builder/src/configs/supportLayoutList.js delete mode 100644 packages/builder/src/configs/theme.js delete mode 100644 packages/builder/src/constants/context.js delete mode 100644 packages/builder/src/constants/itemType.js delete mode 100644 packages/builder/src/demo/index-1-x.js delete mode 100644 packages/builder/src/demo/index.js delete mode 100644 packages/builder/src/index.js delete mode 100644 packages/builder/src/reducers/codemode.js delete mode 100644 packages/builder/src/reducers/componentId.js delete mode 100644 packages/builder/src/reducers/componentProps.js delete mode 100644 packages/builder/src/reducers/gbConfig.js delete mode 100644 packages/builder/src/reducers/index.js delete mode 100644 packages/builder/src/reducers/initSchemaData.js delete mode 100644 packages/builder/src/reducers/preview.js delete mode 100644 packages/builder/src/style.js delete mode 100644 packages/builder/src/utils/arg.js delete mode 100644 packages/builder/src/utils/baseForm.js delete mode 100644 packages/builder/src/utils/comp.js delete mode 100644 packages/builder/src/utils/lang.js delete mode 100644 packages/builder/src/utils/util.js delete mode 100644 packages/builder/tsconfig.json delete mode 100644 packages/core/src/__test__/form.spec.js create mode 100644 packages/core/src/__tests__/__snapshots__/index.spec.ts.snap create mode 100644 packages/core/src/__tests__/field.state.spec.ts create mode 100644 packages/core/src/__tests__/form.state.spec.ts create mode 100644 packages/core/src/__tests__/graph.spec.ts create mode 100644 packages/core/src/__tests__/index.spec.ts create mode 100644 packages/core/src/__tests__/lifecycle.spec.ts create mode 100644 packages/core/src/__tests__/model.spec.ts create mode 100644 packages/core/src/__tests__/vfield.state.spec.ts delete mode 100644 packages/core/src/field.ts delete mode 100644 packages/core/src/form.ts delete mode 100644 packages/core/src/path.ts create mode 100644 packages/core/src/shared/graph.ts create mode 100644 packages/core/src/shared/lifecycle.ts create mode 100644 packages/core/src/shared/model.ts create mode 100644 packages/core/src/shared/subscrible.ts create mode 100644 packages/core/src/state/field.ts create mode 100644 packages/core/src/state/form.ts create mode 100644 packages/core/src/state/virtual-field.ts create mode 100644 packages/core/src/types.ts delete mode 100644 packages/core/src/utils.ts create mode 100644 packages/next/src/compat/Form.tsx create mode 100644 packages/next/src/compat/FormItem.tsx create mode 100644 packages/next/src/compat/context.tsx create mode 100644 packages/next/src/compat/index.ts create mode 100644 packages/next/src/components/FormBlock.tsx create mode 100644 packages/next/src/components/FormCard.tsx create mode 100644 packages/next/src/components/FormItemGrid.tsx create mode 100644 packages/next/src/components/FormLayout.tsx create mode 100644 packages/next/src/components/FormStep.tsx create mode 100644 packages/next/src/components/FormTextBox.tsx create mode 100644 packages/next/src/components/index.ts delete mode 100644 packages/next/src/components/layout.tsx delete mode 100644 packages/next/src/fields/array.tsx rename packages/next/src/fields/{date.tsx => date.ts} (90%) create mode 100644 packages/next/src/fields/index.ts delete mode 100644 packages/next/src/form.tsx delete mode 100644 packages/next/src/locale.ts create mode 100644 packages/next/src/shared.ts delete mode 100644 packages/next/src/type.tsx create mode 100644 packages/next/src/types.ts delete mode 100644 packages/next/src/utils.tsx rename packages/{builder-next => react-schema-renderer}/.npmignore (100%) rename packages/{react => react-schema-renderer}/LESENCE.md (100%) create mode 100644 packages/react-schema-renderer/README.md rename packages/{react => react-schema-renderer}/jest.config.js (100%) rename packages/{builder-next => react-schema-renderer}/package.json (59%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/actions.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/context.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/destruct.spec.js (97%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/display.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/dynamic.spec.js (99%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/editable.spec.js (89%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/effects.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/mutators.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/schema_form.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/traverse.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/utils.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/validate.spec.js (95%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/validate_relations.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/value.spec.js (97%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/virtualbox.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/visible.spec.js (100%) rename packages/{react/src/__tests__ => react-schema-renderer/src/__old_tests__}/x-component.spec.js (100%) create mode 100644 packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap create mode 100644 packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap create mode 100644 packages/react-schema-renderer/src/__tests__/field.spec.tsx create mode 100644 packages/react-schema-renderer/src/__tests__/form.spec.tsx create mode 100644 packages/react-schema-renderer/src/__tests__/json-schema.spec.tsx create mode 100644 packages/react-schema-renderer/src/__tests__/markup.spec.tsx create mode 100644 packages/react-schema-renderer/src/__tests__/register.spec.tsx create mode 100644 packages/react-schema-renderer/src/components/SchemaField.tsx create mode 100644 packages/react-schema-renderer/src/components/SchemaForm.tsx create mode 100644 packages/react-schema-renderer/src/components/SchemaMarkup.tsx create mode 100644 packages/react-schema-renderer/src/hooks/useSchemaForm.ts create mode 100644 packages/react-schema-renderer/src/index.tsx create mode 100644 packages/react-schema-renderer/src/shared/actions.ts create mode 100644 packages/react-schema-renderer/src/shared/connect.ts create mode 100644 packages/react-schema-renderer/src/shared/context.ts create mode 100644 packages/react-schema-renderer/src/shared/registry.ts create mode 100644 packages/react-schema-renderer/src/shared/schema.ts create mode 100644 packages/react-schema-renderer/src/shared/virtual-render.tsx create mode 100644 packages/react-schema-renderer/src/types.ts create mode 100644 packages/react-schema-renderer/tsconfig.json rename packages/{types => react-shared-components}/.npmignore (100%) rename packages/{builder-next => react-shared-components}/LICENSE.md (100%) create mode 100644 packages/react-shared-components/README.md rename packages/{utils => react-shared-components}/jest.config.js (100%) rename packages/{utils => react-shared-components}/package.json (83%) create mode 100644 packages/react-shared-components/src/ArrayList.tsx create mode 100644 packages/react-shared-components/src/PasswordStrength.tsx create mode 100644 packages/react-shared-components/src/PreviewText.tsx create mode 100644 packages/react-shared-components/src/index.ts create mode 100644 packages/react-shared-components/src/types.ts create mode 100644 packages/react-shared-components/tsconfig.json rename packages/{types => react}/LICENSE.md (100%) create mode 100644 packages/react/src/__tests__/actions.spec.tsx create mode 100644 packages/react/src/__tests__/consumer.spec.tsx create mode 100644 packages/react/src/__tests__/effects.spec.tsx create mode 100644 packages/react/src/__tests__/field.spec.tsx create mode 100644 packages/react/src/__tests__/form.spec.tsx create mode 100644 packages/react/src/__tests__/provider.spec.tsx create mode 100644 packages/react/src/__tests__/spy.spec.tsx create mode 100644 packages/react/src/__tests__/useDirty.spec.tsx create mode 100644 packages/react/src/__tests__/useField.spec.tsx create mode 100644 packages/react/src/__tests__/useForceUpdate.spec.tsx create mode 100644 packages/react/src/__tests__/useForm.spec.tsx create mode 100644 packages/react/src/__tests__/virtual.spec.tsx create mode 100644 packages/react/src/components/Field.tsx create mode 100644 packages/react/src/components/Form.tsx create mode 100644 packages/react/src/components/FormConsumer.tsx create mode 100644 packages/react/src/components/FormProvider.tsx create mode 100644 packages/react/src/components/FormSpy.tsx create mode 100644 packages/react/src/components/VirtualField.tsx create mode 100644 packages/react/src/context.ts delete mode 100644 packages/react/src/decorators/connect.ts delete mode 100644 packages/react/src/decorators/markup.tsx create mode 100644 packages/react/src/hooks/useDirty.ts create mode 100644 packages/react/src/hooks/useField.ts create mode 100644 packages/react/src/hooks/useForceUpdate.ts create mode 100644 packages/react/src/hooks/useForm.ts create mode 100644 packages/react/src/hooks/useVirtualField.ts create mode 100644 packages/react/src/index.ts delete mode 100644 packages/react/src/index.tsx delete mode 100644 packages/react/src/initialize/index.ts delete mode 100644 packages/react/src/initialize/object.tsx delete mode 100644 packages/react/src/initialize/render.tsx delete mode 100644 packages/react/src/initialize/virtualbox.tsx create mode 100644 packages/react/src/shared.ts delete mode 100644 packages/react/src/shared/array.tsx delete mode 100644 packages/react/src/shared/broadcast.tsx delete mode 100644 packages/react/src/shared/context.tsx delete mode 100644 packages/react/src/shared/core.tsx delete mode 100644 packages/react/src/shared/mutators.ts delete mode 100644 packages/react/src/shared/virtualbox.tsx delete mode 100644 packages/react/src/state/field.tsx delete mode 100644 packages/react/src/state/form.tsx delete mode 100644 packages/react/src/state/index.tsx delete mode 100644 packages/react/src/type.ts create mode 100644 packages/react/src/types.ts delete mode 100644 packages/react/src/utils.tsx rename packages/{utils => shared}/.npmignore (100%) rename packages/{utils => shared}/LICENSE.md (100%) rename packages/{utils => shared}/README.md (57%) create mode 100644 packages/shared/jest.config.js rename packages/{types => shared}/package.json (68%) create mode 100644 packages/shared/src/__tests__/index.spec.ts rename packages/{utils => shared}/src/array.ts (71%) rename packages/{utils => shared}/src/broadcast.ts (58%) rename packages/{utils => shared}/src/case.ts (100%) rename packages/{utils => shared}/src/clone.ts (88%) rename packages/{utils => shared}/src/compare.ts (60%) create mode 100644 packages/shared/src/deprecate.ts create mode 100644 packages/shared/src/global.ts create mode 100644 packages/shared/src/index.ts rename packages/{utils => shared}/src/isEmpty.ts (96%) create mode 100644 packages/shared/src/merge.ts create mode 100644 packages/shared/src/path.ts rename packages/{utils/src/stringLength.ts => shared/src/string.ts} (100%) rename packages/{types => shared}/src/types.ts (100%) create mode 100644 packages/shared/tsconfig.json delete mode 100644 packages/types/README.md delete mode 100644 packages/types/src/effects.ts delete mode 100644 packages/types/src/field.ts delete mode 100644 packages/types/src/form.ts delete mode 100644 packages/types/src/index.ts delete mode 100644 packages/types/src/path.ts delete mode 100644 packages/types/src/rule.ts delete mode 100644 packages/types/src/schema.ts delete mode 100644 packages/types/src/validator.ts delete mode 100644 packages/types/tsconfig.json delete mode 100644 packages/utils/src/__tests__/index.spec.js delete mode 100644 packages/utils/src/accessor.ts delete mode 100644 packages/utils/src/defer.ts delete mode 100644 packages/utils/src/globalThis.ts delete mode 100644 packages/utils/src/index.ts delete mode 100644 packages/utils/src/lru.ts delete mode 100644 packages/utils/src/schema.ts delete mode 100644 packages/utils/tsconfig.json delete mode 100644 packages/validator/src/__tests__/index.spec.js create mode 100644 packages/validator/src/__tests__/index.spec.ts rename packages/validator/src/{validators/regexp.ts => formats.ts} (100%) create mode 100644 packages/validator/src/locale.ts delete mode 100644 packages/validator/src/locale/index.ts create mode 100644 packages/validator/src/rules.ts create mode 100644 packages/validator/src/types.ts delete mode 100644 packages/validator/src/utils.ts create mode 100644 packages/validator/src/validator.ts delete mode 100644 packages/validator/src/validators/custom.ts delete mode 100644 packages/validator/src/validators/format.ts delete mode 100644 packages/validator/src/validators/index.ts delete mode 100644 packages/validator/src/validators/pattern.ts delete mode 100644 packages/validator/src/validators/required.ts diff --git a/.eslintrc b/.eslintrc index 572c8635275..82a84fcae93 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,5 +12,5 @@ "ecmaFeatures": { "jsx": true } - } + }, } \ No newline at end of file diff --git a/docs/Examples/antd/List.md b/docs/Examples/antd/List.md index 10ca72e9fde..452896f3f97 100644 --- a/docs/Examples/antd/List.md +++ b/docs/Examples/antd/List.md @@ -3,10 +3,11 @@ > 数组场景,区块型数组,能解决大量字段的聚合输入,但是对于数据的对比化展示,区分 > 度不够明显 -下面对于List场景我们主要封装了 -- Array类型组件 -- Table类型组件 -- Card类型组件 +下面对于 List 场景我们主要封装了 + +- Array 类型组件 +- Table 类型组件 +- Card 类型组件 这些组件你都可以对其做简单的定制来适应你当前的业务需求,比如 @@ -66,8 +67,8 @@ const App = () => { maxItems={3} type="array" x-props={{ - renderAddition:'这是定制的添加文案', - renderRemove:'这是定制的删除文案' + renderAddition: '这是定制的添加文案', + renderRemove: '这是定制的删除文案' }} > @@ -82,11 +83,7 @@ const App = () => { - + @@ -149,11 +146,23 @@ const App = () => ( maxItems={3} type="array" x-component="table" - x-props={{ - renderExtraOperations(){ - return
Hello worldasdasdasdasd
+ x-props={{ + scroll: { x: '200%' }, + renderExtraOperations() { + return ( +
+ Hello worldasdasdasdasd +
+ ) }, - operationsWidth:400 + operationsWidth: 400 }} > @@ -169,7 +178,7 @@ const App = () => ( - + @@ -207,12 +216,17 @@ import 'antd/dist/antd.css' const App = () => ( - + ( - + - + { }) }) $('onFieldChange', 'aa').subscribe(fieldState => { + console.log(fieldState.value) setFieldState('bb', state => { state.visible = !fieldState.value }) @@ -135,7 +136,7 @@ const App = () => { }) }} labelCol={6} - wrapperCol={4} + wrapperCol={12} onSubmit={v => console.log(v)} > diff --git a/docs/Examples/antd/Sample.md b/docs/Examples/antd/Sample.md index eb0ddeca04c..8320628a3a1 100644 --- a/docs/Examples/antd/Sample.md +++ b/docs/Examples/antd/Sample.md @@ -34,7 +34,6 @@ ReactDOM.render( actions={actions} labelCol={7} initialValues={{ - date:'2019-08-01', upload3:[{ downloadURL: "//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png", @@ -82,7 +81,7 @@ ReactDOM.render( /> - + ( - + diff --git a/docs/Examples/next/Relations.md b/docs/Examples/next/Relations.md index 24d95d4ba22..8703d9adf56 100644 --- a/docs/Examples/next/Relations.md +++ b/docs/Examples/next/Relations.md @@ -51,6 +51,7 @@ const App = () => { return ( { $('onFormInit').subscribe(() => { setFieldState(FormPath.match('*(gg,hh)'), state => { @@ -131,7 +132,7 @@ const App = () => { wrapperCol={4} onSubmit={v => console.log(v)} > - + {
- + =16.8.0", - "react-dom": ">=16.8.0" + "react-dom": ">=16.8.0", + "@types/styled-components": "^4.1.19" }, "dependencies": { - "@uform/react": "^0.4.3", - "@uform/types": "^0.4.3", - "@uform/utils": "^0.4.3", + "@uform/react-schema-renderer": "^0.4.0", + "@uform/react-shared-components":"^0.4.0", + "@uform/types": "^0.4.0", + "@uform/shared": "^0.4.0", "classnames": "^2.2.6", "moveto": "^1.7.4", "react-stikky": "^0.1.15", diff --git a/packages/antd/src/compat/Form.tsx b/packages/antd/src/compat/Form.tsx new file mode 100644 index 00000000000..521739893c5 --- /dev/null +++ b/packages/antd/src/compat/Form.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import { Form } from 'antd' +import { FormProps } from 'antd/lib/form' +import { IFormItemTopProps } from '../types' +import { FormItemProvider } from './context' +import { normalizeCol } from '../shared' + +export const CompatNextForm: React.FC< + FormProps & IFormItemTopProps +> = props => { + return ( + +
+ + ) +} diff --git a/packages/antd/src/compat/FormItem.tsx b/packages/antd/src/compat/FormItem.tsx new file mode 100644 index 00000000000..67f19610e7f --- /dev/null +++ b/packages/antd/src/compat/FormItem.tsx @@ -0,0 +1,99 @@ +import React, { createContext, useContext } from 'react' +import { Form } from 'antd' +import { useFormItem } from './context' +import { IFormItemTopProps, ICompatItemProps } from '../types' +import { normalizeCol } from '../shared' + +const computeStatus = (props: ICompatItemProps) => { + if (props.loading) { + return 'validating' + } + if (props.invalid) { + return 'error' + } + if (props.warnings && props.warnings.length) { + return 'warning' + } + return '' +} + +const computeHelp = (props: ICompatItemProps) => { + if (props.help) return props.help + const messages = [].concat(props.errors || [], props.warnings || []) + return messages.length ? messages : props.schema && props.schema.description +} + +const computeLabel = (props: ICompatItemProps) => { + if (props.label) return props.label + if (props.schema && props.schema.title) { + return props.schema.title + } +} + +const computeExtra = (props: ICompatItemProps) => { + if (props.extra) return props.extra +} + +function pickProps(obj: T, ...keys: (keyof T)[]): Pick { + const result: Pick = {} as any + for (let i = 0; i < keys.length; i++) { + if (obj[keys[i]] !== undefined) { + result[keys[i]] = obj[keys[i]] + } + } + return result +} + +const computeSchemaExtendProps = ( + props: ICompatItemProps +): IFormItemTopProps => { + if (props.schema) { + return pickProps( + { + ...props.schema.getExtendsItemProps(), + ...props.schema.getExtendsProps() + }, + 'prefix', + 'labelAlign', + 'labelTextAlign', + 'size', + 'labelCol', + 'wrapperCol' + ) + } +} + +const FormItemPropsContext = createContext({}) + +export const FormItemProps = ({ children, ...props }) => ( + + {children} + +) + +export const CompatNextFormItem: React.FC = props => { + const { prefixCls, labelAlign, labelCol, wrapperCol } = useFormItem() + const help = computeHelp(props) + const label = computeLabel(props) + const status = computeStatus(props) + const extra = computeExtra(props) + const itemProps = computeSchemaExtendProps(props) + const outerFormItemProps = useContext(FormItemPropsContext) + return ( + {extra}

: undefined} + {...itemProps} + {...outerFormItemProps} + > + {props.children} +
+ ) +} diff --git a/packages/antd/src/compat/context.tsx b/packages/antd/src/compat/context.tsx new file mode 100644 index 00000000000..e3f38383c73 --- /dev/null +++ b/packages/antd/src/compat/context.tsx @@ -0,0 +1,35 @@ +import React, { createContext, useContext } from 'react' +import { IFormItemTopProps } from '../types' + +const FormItemContext = createContext({}) + +export const FormItemProvider: React.FC = ({ + children, + prefix, + size, + labelAlign, + labelCol, + inline, + labelTextAlign, + wrapperCol +}) => ( + + {children} + +) + +FormItemProvider.displayName = 'FormItemProvider' + +export const useFormItem = () => { + return useContext(FormItemContext) +} diff --git a/packages/antd/src/compat/index.ts b/packages/antd/src/compat/index.ts new file mode 100644 index 00000000000..1eca2fb65ed --- /dev/null +++ b/packages/antd/src/compat/index.ts @@ -0,0 +1,10 @@ +import { + registerFormComponent, + registerFormItemComponent +} from '@uform/react-schema-renderer' +import { CompatNextForm } from './Form' +import { CompatNextFormItem } from './FormItem' + +registerFormComponent(CompatNextForm) + +registerFormItemComponent(CompatNextFormItem) diff --git a/packages/antd/src/components/FormBlock.tsx b/packages/antd/src/components/FormBlock.tsx new file mode 100644 index 00000000000..ab870a2a4cd --- /dev/null +++ b/packages/antd/src/components/FormBlock.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import { createVirtualBox } from '@uform/react-schema-renderer' +import { Card } from 'antd' +import { CardProps } from 'antd/lib/card' +import styled from 'styled-components' + +export const FormBlock = createVirtualBox( + 'block', + styled(({ children, className, ...props }) => { + return ( + + {children} + + ) + })` + margin-bottom: 10px !important; + &.ant-card { + border: none; + box-shadow: none; + } + ` +) diff --git a/packages/antd/src/components/FormCard.tsx b/packages/antd/src/components/FormCard.tsx new file mode 100644 index 00000000000..69c6b071c16 --- /dev/null +++ b/packages/antd/src/components/FormCard.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { createVirtualBox } from '@uform/react-schema-renderer' +import { Card } from 'antd' +import { CardProps } from 'antd/lib/card' +import styled from 'styled-components' + +export const FormCard = createVirtualBox( + 'card', + styled(({ children, className, ...props }) => { + return ( + + {children} + + ) + })` + margin-bottom: 10px !important; + ` +) diff --git a/packages/antd/src/components/FormItemGrid.tsx b/packages/antd/src/components/FormItemGrid.tsx new file mode 100644 index 00000000000..6f6e7fcd662 --- /dev/null +++ b/packages/antd/src/components/FormItemGrid.tsx @@ -0,0 +1,87 @@ +import React, { Fragment } from 'react' +import { CompatNextFormItem } from '../compat/FormItem' +import { createVirtualBox } from '@uform/react-schema-renderer' +import { toArr } from '@uform/shared' +import { Row, Col } from 'antd' +import { RowProps, ColProps } from 'antd/lib/grid' +import { FormItemProps as ItemProps } from 'antd/lib/form' +import { IFormItemGridProps, IItemProps } from '../types' +import { normalizeCol } from '../shared' + +export const FormItemGrid = createVirtualBox< + React.PropsWithChildren +>('grid', props => { + const { + cols: rawCols, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + title, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + description, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + help, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + extra, + ...selfProps + } = props + const children = toArr(props.children) + const cols = toArr(rawCols).map(col => normalizeCol(col)) + const childNum = children.length + + if (cols.length < childNum) { + let offset: number = childNum - cols.length + let lastSpan: number = + 24 - + cols.reduce((buf, col) => { + return ( + buf + + Number(col.span ? col.span : 0) + + Number(col.offset ? col.offset : 0) + ) + }, 0) + for (let i = 0; i < offset; i++) { + cols.push({ span: Math.floor(lastSpan / offset) }) + } + } + const grids = ( + + {children.reduce((buf, child, key) => { + return child + ? buf.concat( + + {child} + + ) + : buf + }, [])} + + ) + + if (title) { + return ( + + {grids} + + ) + } + return {grids} +}) + +export const FormGridRow = createVirtualBox( + 'grid-row', + props => { + const { title, description, extra } = props + const grids = {props.children} + if (title) { + return ( + + {grids} + + ) + } + return grids + } +) + +export const FormGridCol = createVirtualBox('grid-col', props => { + return {props.children} +}) diff --git a/packages/antd/src/components/FormLayout.tsx b/packages/antd/src/components/FormLayout.tsx new file mode 100644 index 00000000000..35f242caf7e --- /dev/null +++ b/packages/antd/src/components/FormLayout.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { FormItemProvider, useFormItem } from '../compat/context' +import { createVirtualBox } from '@uform/react-schema-renderer' +import cls from 'classnames' +import { IFormItemTopProps } from '../types' + +export const FormLayout = createVirtualBox( + 'layout', + props => { + const { inline } = useFormItem() + const isInline = props.inline || inline + const children = + isInline || props.className || props.style ? ( +
+ {props.children} +
+ ) : ( + props.children + ) + return {children} + } +) diff --git a/packages/antd/src/components/FormStep.tsx b/packages/antd/src/components/FormStep.tsx new file mode 100644 index 00000000000..3f5ee35121a --- /dev/null +++ b/packages/antd/src/components/FormStep.tsx @@ -0,0 +1,98 @@ +import React, { useState, useMemo, useRef } from 'react' +import { + createControllerBox, + ISchemaVirtualFieldComponentProps, + FormPathPattern, + createEffectHook, + createFormActions +} from '@uform/react-schema-renderer' +import { toArr } from '@uform/shared' +import { Observable } from 'rxjs/internal/Observable' +import { Steps } from 'antd' +import { IFormStep } from '../types' + +enum StateMap { + ON_FORM_STEP_NEXT = 'onFormStepNext', + ON_FORM_STEP_PREVIOUS = 'onFormStepPrevious', + ON_FORM_STEP_GO_TO = 'onFormStepGoto', + ON_FORM_STEP_CURRENT_CHANGE = 'onFormStepCurrentChange' +} +const EffectHooks = { + onStepNext$: createEffectHook(StateMap.ON_FORM_STEP_NEXT), + onStepPrevious$: createEffectHook(StateMap.ON_FORM_STEP_PREVIOUS), + onStepGoto$: createEffectHook(StateMap.ON_FORM_STEP_GO_TO), + onStepCurrentChange$: createEffectHook<{ + value: number + preValue: number + }>(StateMap.ON_FORM_STEP_CURRENT_CHANGE) +} + +const effects = (relations: FormPathPattern[]) => { + const actions = createFormActions() + return EffectHooks.onStepCurrentChange$().subscribe(({ value }) => { + relations.forEach((pattern, index) => { + actions.setFieldState(pattern, (state: any) => { + state.display = index === value + }) + }) + }) +} + +type StepComponentExtendsProps = StateMap & { + getEffects: ( + relations: FormPathPattern[] + ) => Observable<{ + value: number + preValue: number + }> +} + +export const FormStep: React.FC & + StepComponentExtendsProps = createControllerBox( + 'step', + ({ props, form }: ISchemaVirtualFieldComponentProps) => { + const [current, setCurrent] = useState(0) + const ref = useRef(current) + const { dataSource, ...stepProps } = props['x-component-props'] || {} + const items = toArr(dataSource) + const update = (cur: number) => { + form.notify(StateMap.ON_FORM_STEP_CURRENT_CHANGE, { + value: cur, + preValue: current + }) + setCurrent(cur) + } + useMemo(() => { + update(ref.current) + form.subscribe(({ type, payload }) => { + switch (type) { + case StateMap.ON_FORM_STEP_NEXT: + update( + ref.current + 1 > items.length - 1 ? ref.current : ref.current + 1 + ) + break + case StateMap.ON_FORM_STEP_PREVIOUS: + update(ref.current - 1 < 0 ? ref.current : ref.current - 1) + break + case StateMap.ON_FORM_STEP_GO_TO: + if (!(payload < 0 || payload > items.length)) { + update(payload) + } + break + } + }) + }, []) + ref.current = current + return ( + + {items.map((props, key) => { + return + })} + + ) + } +) as any + +Object.assign(FormStep, StateMap, EffectHooks, { + effects +}) diff --git a/packages/antd/src/components/FormTextBox.tsx b/packages/antd/src/components/FormTextBox.tsx new file mode 100644 index 00000000000..45cf18233ee --- /dev/null +++ b/packages/antd/src/components/FormTextBox.tsx @@ -0,0 +1,104 @@ +import React, { useRef, useEffect } from 'react' +import { createControllerBox } from '@uform/react-schema-renderer' +import { IFormTextBox } from '../types' +import { toArr } from '@uform/shared' +import { CompatNextFormItem } from '../compat/FormItem' +import styled from 'styled-components' + +export const FormTextBox = createControllerBox( + 'text-box', + styled(({ props, className, children }) => { + const { + title, + help, + text, + name, + extra, + gutter, + style, + ...componentProps + } = Object.assign( + { + gutter: 5 + }, + props['x-component-props'] + ) + const ref: React.RefObject = useRef() + const arrChildren = toArr(children) + const split = text.split('%s') + let index = 0 + useEffect(() => { + if (ref.current) { + const eles = ref.current.querySelectorAll('.text-box-field') + eles.forEach((el: HTMLElement) => { + const ctrl = el.querySelector('.next-form-item-control:first-child') + if (ctrl) { + el.style.width = getComputedStyle(ctrl).width + } + }) + } + }, []) + const newChildren = split.reduce((buf, item, key) => { + return buf.concat( + item ? ( +

+ {item} +

+ ) : null, + arrChildren[key] ? ( +
+ {arrChildren[key]} +
+ ) : null + ) + }, []) + + const textChildren = ( +
+ {newChildren} +
+ ) + + if (!title) return textChildren + + return ( + + {textChildren} + + ) + })` + display: flex; + .text-box-words:nth-child(1) { + margin-left: 0; + } + .text-box-field { + display: inline-block; + } + .next-form-item { + margin-bottom: 0 !important; + } + .preview-text { + text-align: center !important; + } + ` +) diff --git a/packages/antd/src/components/Select.tsx b/packages/antd/src/components/Select.tsx new file mode 100644 index 00000000000..969410d5ec8 --- /dev/null +++ b/packages/antd/src/components/Select.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { Select as AntSelect } from 'antd' +import { SelectProps as AntSelectProps } from 'antd/lib/select' +import styled from 'styled-components' + +type SelectOption = { + label: React.ReactText + value: any + [key: string]: any +} + +type SelectProps = AntSelectProps & { + dataSource?: SelectOption[] +} + +export const Select: React.FC = styled((props: SelectProps) => { + const { dataSource = [], ...others } = props + const children = dataSource.map((item, index) => { + const { label, value, ...others } = item + return ( + + {label} + + ) + }) + return ( + + {children} + + ) +})` + min-width: 100px; + width: 100%; +` diff --git a/packages/antd/src/components/button.tsx b/packages/antd/src/components/button.tsx index 966e4d6c90e..5d07468e830 100644 --- a/packages/antd/src/components/button.tsx +++ b/packages/antd/src/components/button.tsx @@ -1,38 +1,87 @@ import React from 'react' -import { FormConsumer } from '@uform/react' +import { FormSpy, LifeCycleTypes } from '@uform/react-schema-renderer' import { Button } from 'antd' -import { ISubmitProps } from '../type' +import { ButtonProps } from 'antd/lib/button' +import { ISubmitProps, IResetProps } from '../types' -export const Submit = ({ showLoading, ...props }: ISubmitProps) => { +export const TextButton: React.FC = props => ( + ) }} - + ) } -export const Reset: React.FC> = props => { +Submit.defaultProps = { + showLoading: true +} + +export const Reset: React.FC = ({ + children, + forceClear, + validate, + ...props +}) => { return ( - - {({ reset }) => { + + {({ form }) => { return ( - ) }} - + ) } diff --git a/packages/antd/src/components/formButtonGroup.tsx b/packages/antd/src/components/formButtonGroup.tsx index 2ddcc01dda7..7899c572d23 100644 --- a/packages/antd/src/components/formButtonGroup.tsx +++ b/packages/antd/src/components/formButtonGroup.tsx @@ -1,24 +1,10 @@ -import React, { Component } from 'react' -import ReactDOM from 'react-dom' -import { Row, Col } from './grid' -import { FormLayoutConsumer } from '../form' -import { IFormButtonGroupProps } from '../type' +import React, { useRef } from 'react' +import { Row, Col } from 'antd' import Sticky from 'react-stikky' import cls from 'classnames' import styled from 'styled-components' - -const getAlign = align => { - if (align === 'start' || align === 'end') { - return align - } - if (align === 'left' || align === 'top') { - return 'flex-start' - } - if (align === 'right' || align === 'bottom') { - return 'flex-end' - } - return align -} +import { useFormItem } from '../compat/context' +import { IFormButtonGroupProps } from '../types' export interface IOffset { top: number | string @@ -27,6 +13,13 @@ export interface IOffset { left: number | string } +const getAlign = align => { + if (align === 'start' || align === 'end') return align + if (align === 'left' || align === 'top') return 'flex-start' + if (align === 'right' || align === 'bottom') return 'flex-end' + return align +} + const isElementInViewport = ( rect: ClientRect, { @@ -36,7 +29,7 @@ const isElementInViewport = ( offset?: IOffset | number threshold?: number } = {} -) => { +): boolean => { const { top, right, bottom, left, width, height } = rect const intersection = { t: bottom, @@ -62,74 +55,28 @@ const isElementInViewport = ( ) } -export const FormButtonGroup: React.FC = styled( - class FormButtonGroup extends Component { - public static defaultProps = { - span: 24 - } - - private formNode: HTMLElement - - public render() { - const { sticky, style, className } = this.props - - const content = ( - - {({ inline } = {}) => ( -
- {this.renderChildren()} -
- )} -
- ) - - if (sticky) { - return ( -
- - {({ FormRef } = {}) => { - if (!FormRef) { - return - } - return ( - -
- {content} -
-
- ) - }} -
-
- ) - } - - return content - } - - private renderChildren() { - const { children, itemStyle, offset, span } = this.props +export const FormButtonGroup = styled( + (props: React.PropsWithChildren) => { + const { + span, + zIndex, + sticky, + style, + offset, + className, + children, + triggerDistance, + itemStyle + } = props + const { inline } = useFormItem() + const selfRef = useRef() + const renderChildren = () => { return ( -
+
- -
+ +
{children}
@@ -138,20 +85,53 @@ export const FormButtonGroup: React.FC = styled(
) } - - private getStickyBoundaryHandler(ref) { + const getStickyBoundaryHandler = () => { return () => { - // eslint-disable-next-line react/no-find-dom-node - this.formNode = this.formNode || ReactDOM.findDOMNode(ref.current) - if (this.formNode) { - return isElementInViewport(this.formNode.getBoundingClientRect()) + if (selfRef.current && selfRef.current.parentElement) { + const container = selfRef.current.parentElement + return isElementInViewport(container.getBoundingClientRect()) } return true } } + + const content = ( +
+ {renderChildren()} +
+ ) + + if (sticky) { + return ( +
+ +
+ {content} +
+
+
+ ) + } + + return content } -)` - ${props => +)` + ${(props: IFormButtonGroupProps) => props.align ? `display:flex;justify-content: ${getAlign(props.align)}` : ''} &.is-inline { display: inline-block; diff --git a/packages/antd/src/components/grid.tsx b/packages/antd/src/components/grid.tsx deleted file mode 100644 index e9773c591a1..00000000000 --- a/packages/antd/src/components/grid.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React, { Component, Children, cloneElement } from 'react' -import cx from 'classnames' -import { toArr } from '@uform/utils' -import { IColProps, IRowProps } from '../type' - -export class Row extends Component { - public static defaultProps = { - prefix: 'ant-', - pure: false, - fixed: false, - gutter: 0, - wrap: false, - component: 'div' - } - - public render() { - /* eslint-disable @typescript-eslint/no-unused-vars */ - const { - prefix, - pure, - wrap, - fixed, - gutter, - fixedWidth, - align, - justify, - hidden, - className, - component: Tag, - children, - ...others - } = this.props - /* eslint-enable @typescript-eslint/no-unused-vars */ - - let hiddenClassObj - if (hidden === true) { - hiddenClassObj = { [`${prefix}row-hidden`]: true } - } else if (typeof hidden === 'string') { - hiddenClassObj = { [`${prefix}row-${hidden}-hidden`]: !!hidden } - } else if (Array.isArray(hidden)) { - hiddenClassObj = hidden.reduce((ret, point) => { - ret[`${prefix}row-${point}-hidden`] = !!point - return ret - }, {}) - } - - const newClassName = cx({ - [`${prefix}row`]: true, - [`${prefix}row-wrap`]: wrap, - [`${prefix}row-fixed`]: fixed, - [`${prefix}row-fixed-${fixedWidth}`]: !!fixedWidth, - [`${prefix}row-justify-${justify}`]: !!justify, - [`${prefix}row-align-${align}`]: !!align, - ...hiddenClassObj, - [className]: !!className - }) - - let newChildren = toArr(children) - const gutterNumber = parseInt(gutter, 10) - - if (gutterNumber !== 0) { - const halfGutterString = `${gutterNumber / 2}px` - others.style = { - marginLeft: `-${halfGutterString}`, - marginRight: `-${halfGutterString}`, - ...(others.style || {}) - } - newChildren = Children.map(children, (child: React.ReactElement) => { - if ( - child && - child.type && - typeof child.type === 'function' && - (child.type as any).isNextCol - ) { - const newChild = cloneElement(child, { - style: { - paddingLeft: halfGutterString, - paddingRight: halfGutterString, - ...(child.props.style || {}) - } - }) - return newChild - } - - return child - }) - } - - return ( - - {newChildren} - - ) - } -} - -const breakPoints = ['xxs', 'xs', 's', 'm', 'l', 'xl'] - -export class Col extends Component { - public static isNextCol = true - - public static defaultProps = { - prefix: 'ant-', - pure: false, - component: 'div' - } - - public render() { - const { - prefix, - pure, - span, - offset, - fixedSpan, - fixedOffset, - hidden, - align, - xxs, - xs, - s, - m, - l, - xl, - component: Tag, - className, - children, - ...others - } = this.props - - const pointClassObj = breakPoints.reduce((ret, point) => { - let pointProps: { span?: string; offset?: string } = {} - if (typeof this.props[point] === 'object') { - pointProps = this.props[point] - } else { - pointProps.span = this.props[point] - } - - ret[`${prefix}col-${point}-${pointProps.span}`] = !!pointProps.span - ret[ - `${prefix}col-${point}-offset-${pointProps.offset}` - ] = !!pointProps.offset - return ret - }, {}) - - let hiddenClassObj - if (hidden === true) { - hiddenClassObj = { [`${prefix}col-hidden`]: true } - } else if (typeof hidden === 'string') { - hiddenClassObj = { [`${prefix}col-${hidden}-hidden`]: !!hidden } - } else if (Array.isArray(hidden)) { - hiddenClassObj = hidden.reduce((ret, point) => { - ret[`${prefix}col-${point}-hidden`] = !!point - return ret - }, {}) - } - - const classes = cx({ - [`${prefix}col`]: true, - [`${prefix}col-${span}`]: !!span, - [`${prefix}col-fixed-${fixedSpan}`]: !!fixedSpan, - [`${prefix}col-offset-${offset}`]: !!offset, - [`${prefix}col-offset-fixed-${fixedOffset}`]: !!fixedOffset, - [`${prefix}col-${align}`]: !!align, - ...pointClassObj, - ...hiddenClassObj, - [className]: className - }) - - return ( - - {children} - - ) - } -} diff --git a/packages/antd/src/components/index.ts b/packages/antd/src/components/index.ts new file mode 100644 index 00000000000..1015143d9c7 --- /dev/null +++ b/packages/antd/src/components/index.ts @@ -0,0 +1,8 @@ +export * from './Button' +export * from './FormButtonGroup' +export * from './FormLayout' +export * from './FormItemGrid' +export * from './FormCard' +export * from './FormBlock' +export * from './FormTextBox' +export * from './FormStep' diff --git a/packages/antd/src/components/layout.tsx b/packages/antd/src/components/layout.tsx deleted file mode 100644 index f87cf4bd801..00000000000 --- a/packages/antd/src/components/layout.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import React, { Component, useEffect, useRef } from 'react' -import { createVirtualBox, createControllerBox } from '@uform/react' -import { toArr } from '@uform/utils' -import { IFormItemGridProps, IFormItemProps } from '@uform/types' -import { Card, Row, Col } from 'antd' -import styled from 'styled-components' -import cls from 'classnames' - -import { FormLayoutConsumer, FormItem, FormLayoutProvider } from '../form' -import { - IFormTextBox, - IFormCardProps, - IFormBlockProps, - IFormLayoutProps, - TFormCardOrFormBlockProps, - IFormItemGridProps as IFormItemGridPropsAlias -} from '../type' - -const normalizeCol = ( - col: { span: number; offset?: number } | number, - defaultValue: { span: number } = { span: 0 } -): { span: number; offset?: number } => { - if (!col) { - return defaultValue - } else { - return typeof col === 'object' ? col : { span: col } - } -} - -export const FormLayoutItem: React.FC = function(props) { - return React.createElement( - FormLayoutConsumer, - {}, - ({ - labelAlign, - labelTextAlign, - labelCol, - wrapperCol, - size, - autoAddColon - }) => { - return React.createElement( - FormItem, - { - labelAlign, - labelTextAlign, - labelCol, - wrapperCol, - autoAddColon, - size, - ...props - }, - props.children - ) - } - ) -} - -export const FormLayout = createVirtualBox( - 'layout', - ({ children, ...props }) => { - return ( - - {value => { - const newValue = { ...value, ...props } - const child = - newValue.inline || newValue.className || newValue.style ? ( -
- {children} -
- ) : ( - children - ) - return ( - {child} - ) - }} -
- ) - } -) - -export const FormItemGrid = createVirtualBox( - 'grid', - class extends Component { - public render() { - const { title } = this.props - if (title) { - return this.renderFormItem(this.renderGrid()) - } else { - return this.renderGrid() - } - } - - private renderFormItem(children) { - const { title, help, name, extra, ...props } = this.props - return React.createElement( - FormLayoutItem, - { - label: title, - noMinHeight: true, - id: name, - extra, - help, - ...props - } as IFormItemGridProps, - children - ) - } - - private renderGrid() { - const { - children: rawChildren, - cols: rawCols, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - title, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - description, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - help, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - extra, - ...props - } = this.props - - const children = toArr(rawChildren) - const childNum = children.length - const cols = toArr(rawCols).map(col => normalizeCol(col)) - - if (cols.length < childNum) { - const offset: number = childNum - cols.length - const lastSpan: number = - 24 - - cols.reduce((buf, col) => { - return ( - buf + - Number(col.span ? col.span : 0) + - Number(col.offset ? col.offset : 0) - ) - }, 0) - - for (let i = 0; i < offset; i++) { - cols.push({ span: Math.floor(lastSpan / offset) }) - } - } - - // cols = toArr(cols).map(col => normalizeCol(col)) - - return ( - - {children.reduce((buf, child, key) => { - return child - ? buf.concat( - - {child} - - ) - : buf - }, [])} - - ) - } - } -) - -export const FormCard = createVirtualBox( - 'card', - styled( - class extends Component { - public static defaultProps = { - // bodyHeight: 'auto' - } - public render() { - const { children, className, ...props } = this.props - return ( - - {children} - - ) - } - } - )` - margin-bottom: 30px; - .ant-card-body { - padding-top: 30px; - padding-bottom: 0 !important; - } - &.ant-card { - display: block; - margin-bottom: 30px; - } - ` -) - -export const FormBlock = createVirtualBox( - 'block', - styled( - class extends Component { - public static defaultProps = { - // bodyHeight: 'auto' - } - - public render() { - const { children, className, ...props } = this.props - return ( - - {children} - - ) - } - } - )` - margin-bottom: 0px; - .ant-card-body { - padding-top: 20px; - padding-bottom: 0 !important; - } - &.ant-card { - border: none; - padding: 0 15px; - padding-bottom: 15px; - display: block; - box-shadow: none; - } - .ant-card-head { - padding: 0 !important; - min-height: 24px; - font-weight: normal; - } - .ant-card-head-title { - padding: 0; - } - ` -) - -export const FormTextBox = createControllerBox( - 'text-box', - styled(({ children, schema, className }) => { - const { title, help, text, name, extra, ...props } = schema['x-props'] - const ref: React.RefObject = useRef() - const arrChildren = toArr(children) - const split = String(text).split('%s') - useEffect(() => { - if (ref.current) { - const eles = ref.current.querySelectorAll('.text-box-field') - eles.forEach((el: HTMLElement) => { - const ctrl = el.querySelector( - '.ant-form-item-control>*:not(.ant-form-item-space)' - ) - if (ctrl) { - el.style.width = getComputedStyle(ctrl).width - } - }) - } - }, []) - - let index = 0 - const newChildren = split.reduce((buf, item, key) => { - return buf.concat( - item ? ( - - {item} - - ) : null, - arrChildren[key] ? ( -
- {arrChildren[key]} -
- ) : null - ) - }, []) - - if (!title) { - return ( -
- {newChildren} -
- ) - } - - return React.createElement( - FormLayoutItem, - { - label: title, - noMinHeight: true, - id: name, - extra, - help, - ...props - }, -
- {newChildren} -
- ) - })` - display: flex; - .text-box-words { - font-size: 14px; - line-height: 34px; - color: #333; - ${props => { - const { editable, schema } = props - const { gutter } = schema['x-props'] - if (!editable) { - return { - margin: 0 - } - } - return { - margin: `0 ${gutter === 0 || gutter ? gutter : 10}px` - } - }} - } - .text-box-words:nth-child(1) { - margin-left: 0; - } - .text-box-field { - display: inline-block; - } - ` -) diff --git a/packages/antd/src/fields/array.tsx b/packages/antd/src/fields/array.tsx deleted file mode 100644 index 52feca6fd41..00000000000 --- a/packages/antd/src/fields/array.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import React from 'react' -import { registerFormField, createArrayField } from '@uform/react' -import { Icon } from 'antd' -import styled, { css } from 'styled-components' - -export const CircleButton = styled['div'].attrs({ className: 'cricle-btn' })` - ${props => (!props.hasText ? 'width:30px; height:30px;' : '')} - margin-right:10px; - border-radius: ${props => (!props.hasText ? '100px' : 'none')}; - border: ${props => (!props.hasText ? '1px solid #eee' : 'none')}; - margin-bottom: 20px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - ${props => - !props.hasText - ? `&:hover{ - background:#f7f4f4; - }` - : ''} - .op-name { - margin-left: 3px; - } -` - -export const TextButton = styled['div'].attrs({ - className: 'ant-btn-text' -})` - display: inline-block; - height: 20px; - line-height: 20px; - cursor: pointer; - .op-name { - margin-left: 4px; - } - ${props => - props.inline && - css` - display: inline-block; - width: auto; - `} -` - -export const ArrayField = createArrayField({ - CircleButton, - TextButton, - AddIcon: () => , - RemoveIcon: () => , - MoveDownIcon: () => , - MoveUpIcon: () => -}) - -registerFormField( - 'array', - styled( - class extends ArrayField { - public render() { - const { className, name, value, renderField } = this.props - const cls = this.getProps('className') - const style = this.getProps('style') - return ( -
- {value.map((item, index) => { - return ( -
-
- {index + 1} -
-
{renderField(index)}
-
- {this.renderRemove(index, item)} - {this.renderMoveDown(index, item)} - {this.renderMoveUp(index)} - {this.renderExtraOperations(index)} -
-
- ) - })} - {this.renderEmpty()} - {value.length > 0 && this.renderAddition()} -
- ) - } - } - )` - border: 1px solid #eee; - min-width: 400px; - .array-item { - padding: 20px; - padding-bottom: 0; - padding-top: 30px; - border-bottom: 1px solid #eee; - position: relative; - &:nth-child(even) { - background: #fafafa; - } - .array-index { - position: absolute; - top: 0; - left: 0; - display: block; - span { - position: absolute; - color: rgb(255, 255, 255); - z-index: 1; - font-size: 12px; - top: 3px; - left: 3px; - line-height: initial; - } - &::after { - content: ''; - display: block; - border-top: 20px solid transparent; - border-left: 20px solid transparent; - border-bottom: 20px solid transparent; - border-right: 20px solid #888; - transform: rotate(45deg); - position: absolute; - z-index: 0; - top: -20px; - left: -20px; - } - } - .array-item-operator { - display: flex; - border-top: 1px solid #eee; - padding-top: 20px; - } - } - .array-empty-wrapper { - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - &.disabled { - cursor: default; - } - .array-empty { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - margin: 20px; - img { - display: block; - height: 80px; - } - .ant-btn-text { - color: #999; - i { - margin-right: 3px; - } - } - } - } - .array-item-wrapper { - margin: 0 -20px; - } - .array-item-addition { - padding: 10px 20px; - line-height: normal !important; - background: #fbfbfb; - .ant-btn-text { - color: #888; - i { - margin-right: 3px; - } - } - } - ` -) diff --git a/packages/antd/src/fields/boolean.tsx b/packages/antd/src/fields/boolean.tsx index eb28ca25b26..cfea566fe57 100644 --- a/packages/antd/src/fields/boolean.tsx +++ b/packages/antd/src/fields/boolean.tsx @@ -1,6 +1,6 @@ import { Switch } from 'antd' -import { connect, registerFormField } from '@uform/react' -import { acceptEnum, mapStyledProps } from '../utils' +import { connect, registerFormField } from '@uform/react-schema-renderer' +import { acceptEnum, mapStyledProps } from '../shared' registerFormField( 'boolean', diff --git a/packages/antd/src/fields/cards.tsx b/packages/antd/src/fields/cards.tsx index a48422b0f22..1b13bb6ba83 100644 --- a/packages/antd/src/fields/cards.tsx +++ b/packages/antd/src/fields/cards.tsx @@ -1,132 +1,132 @@ -import React, { Fragment, ReactElement } from 'react' +import React, { Fragment } from 'react' +import { + registerFormField, + ISchemaFieldComponentProps, + SchemaField +} from '@uform/react-schema-renderer' +import { toArr, isFn } from '@uform/shared' +import { ArrayList } from '@uform/react-shared-components' +import { CircleButton, TextButton } from '../components/Button' +import { Card, Icon } from 'antd' import styled from 'styled-components' -import { registerFormField } from '@uform/react' -import { Card } from 'antd' -import { toArr } from '@uform/utils' -import { ArrayField } from './array' -const FormCardsField = styled( - class extends ArrayField { - public renderOperations(item, index) { - return ( - - {this.renderRemove(index, item)} - {this.renderMoveDown(index, item)} - {this.renderMoveUp(index)} - {this.renderExtraOperations(index)} - - ) - } +const ArrayComponents = { + CircleButton, + TextButton, + AdditionIcon: () => , + RemoveIcon: () => , + MoveDownIcon: () => , + MoveUpIcon: () => +} - public renderCardEmpty = (title: string | ReactElement) => { - return ( - - {this.renderEmpty()} - - ) +const FormCardsField = styled( + (props: ISchemaFieldComponentProps & { className: string }) => { + const { value, schema, className, editable, path, mutators } = props + const { + renderAddition, + renderRemove, + renderMoveDown, + renderMoveUp, + renderEmpty, + renderExtraOperations, + ...componentProps + } = schema.getExtendsComponentProps() || {} + const onAdd = () => { + const items = Array.isArray(schema.items) + ? schema.items[schema.items.length - 1] + : schema.items + mutators.push(items.getEmptyValue()) } - - public render() { - const { value, className, schema, renderField } = this.props - const { - title, - style, - className: cls, - renderAddition, - renderRemove, - renderEmpty, - renderMoveDown, - renderMoveUp, - renderOperations, - ...others - } = this.getProps() || ({} as any) - - return ( -
+ {toArr(value).map((item, index) => { return ( - {index + 1}. {title || schema.title} + {index + 1}. {componentProps.title || schema.title} } - className="card-list" - key={index} - extra={this.renderOperations(item, index)} + extra={ + + mutators.remove(index)} + /> + mutators.moveDown(index)} + /> + mutators.moveUp(index)} + /> + {isFn(renderExtraOperations) + ? renderExtraOperations(index) + : renderExtraOperations} + + } > - {renderField(index)} + ) })} - {value.length === 0 && this.renderCardEmpty(title)} -
- {value.length > 0 && this.renderAddition()} -
-
- ) - } - } -)` - .ant-card-body { - padding-top: 30px; - padding-bottom: 0 !important; - } - .ant-card-head-main { - display: flex; - justify-content: space-between; - align-items: center; + + {({ children }) => { + return ( + +
{children}
+
+ ) + }} +
+ + {({ children, isEmpty }) => { + if (!isEmpty) { + return ( +
+ {children} +
+ ) + } + }} +
+ +
+ ) } +)` .ant-card { - display: block; - margin-bottom: 0px; - background: #fff; - .array-empty-wrapper { - display: flex; - justify-content: center; - cursor: pointer; - margin-bottom: 0px; - &.disabled { - cursor: default; - } - .array-empty { - display: flex; - flex-direction: column; - margin-bottom: 20px; - align-items: center; - img { - margin-bottom: 16px; - height: 85px; - } - .next-btn-text { - color: #888; - } - .next-icon:before { - width: 16px !important; - font-size: 16px !important; - margin-right: 5px; - } - } - } - - .next-card { + .ant-card { box-shadow: none; } - .card-list { - box-shadow: none; - border: 1px solid #eee; + .ant-card-body{ + padding:20px 10px 0 10px; } - - .array-item-addition { + .array-cards-addition { box-shadow: none; border: 1px solid #eee; transition: all 0.35s ease-in-out; @@ -134,40 +134,47 @@ const FormCardsField = styled( border: 1px solid #ccc; } } + .empty-wrapper { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom:10px; + img { + height: 85px; + } + .ant-btn { + color: #888; + } + } } - .ant-card.card-list { - margin-top: 20px; + .card-list-empty.card-list-item { + cursor: pointer; } - .addition-wrapper .array-item-addition { - margin-top: 20px; + .array-cards-addition { + margin-top: 10px; margin-bottom: 3px; - } - .cricle-btn { - margin-bottom: 0; - } - .ant-card-extra { - display: flex; - } - .array-item-addition { background: #fff; display: flex; cursor: pointer; - padding: 10px 0; + padding: 5px 0; justify-content: center; box-shadow: 1px 1px 4px 0 rgba(0, 0, 0, 0.1); - .next-btn-text { - color: #888; - } - .next-icon:before { - width: 16px !important; - font-size: 16px !important; - margin-right: 5px; - } } - .card-list:first-child { + .card-list-item { + margin-top: 10px; + border: 1px solid #eee; + } + .card-list-item:first-child { margin-top: 0 !important; } + .ant-card-extra { + display: flex; + button { + margin-right: 8px; + } + } ` registerFormField('cards', FormCardsField) +registerFormField('array', FormCardsField) diff --git a/packages/antd/src/fields/checkbox.tsx b/packages/antd/src/fields/checkbox.tsx index de04757933e..e24d5874411 100644 --- a/packages/antd/src/fields/checkbox.tsx +++ b/packages/antd/src/fields/checkbox.tsx @@ -1,10 +1,10 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Checkbox } from 'antd' import { transformDataSourceKey, mapStyledProps, mapTextComponent -} from '../utils' +} from '../shared' const { Group: CheckboxGroup } = Checkbox diff --git a/packages/antd/src/fields/date.tsx b/packages/antd/src/fields/date.tsx index 8d109485368..d258487c464 100644 --- a/packages/antd/src/fields/date.tsx +++ b/packages/antd/src/fields/date.tsx @@ -1,54 +1,44 @@ import React from 'react' -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import moment from 'moment' -import { DatePicker as AntDatePicker } from 'antd' +import { DatePicker } from 'antd' import { mapStyledProps, mapTextComponent, - StateLoading, compose, isStr, isArr -} from '../utils' +} from '../shared' -const { - RangePicker: AntRangePicker, - WeekPicker: AntWeekPicker, - MonthPicker: AntMonthPicker -} = AntDatePicker +const { RangePicker, WeekPicker, MonthPicker } = DatePicker -class AntYearPicker extends React.Component { +class YearPicker extends React.Component { public render() { - return + return } } -const DatePicker = StateLoading(AntDatePicker) -const RangePicker = StateLoading(AntRangePicker) -const MonthPicker = StateLoading(AntMonthPicker) -const WeekPicker = StateLoading(AntWeekPicker) -const YearPicker = StateLoading(AntYearPicker) - const transformMoment = (value, format = 'YYYY-MM-DD HH:mm:ss') => { return value && value.format ? value.format(format) : value } -const mapMomentValue = (props, fieldProps) => { - const { value, showTime = false } = props +const mapMomentValue = props => { + const { value, showTime = false, disabled = false } = props try { - if (!fieldProps.editable) return props - if (isStr(value) && value) { - props.value = moment( - value, - showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD' - ) - } else if (isArr(value) && value.length) { - props.value = value.map( - item => - (item && - moment(item, showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')) || - '' - ) + if (!disabled) { + if (isStr(value) && value) { + props.value = moment( + value, + showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD' + ) + } else if (isArr(value) && value.length) { + props.value = value.map( + item => + (item && + moment(item, showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')) || + '' + ) + } } } catch (e) { throw new Error(e) diff --git a/packages/antd/src/fields/index.ts b/packages/antd/src/fields/index.ts new file mode 100644 index 00000000000..59c23266c82 --- /dev/null +++ b/packages/antd/src/fields/index.ts @@ -0,0 +1,15 @@ +import './string' +import './number' +import './boolean' +import './date' +import './time' +import './range' +import './upload' +import './checkbox' +import './radio' +import './rating' +import './transfer' +import './cards' +import './table' +import './textarea' +import './password' \ No newline at end of file diff --git a/packages/antd/src/fields/number.tsx b/packages/antd/src/fields/number.tsx index 2a36d250716..e85d868452a 100644 --- a/packages/antd/src/fields/number.tsx +++ b/packages/antd/src/fields/number.tsx @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { InputNumber } from 'antd' -import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils' +import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' registerFormField( 'number', diff --git a/packages/antd/src/fields/password.tsx b/packages/antd/src/fields/password.tsx index 926ae0e1ffe..02e057f15da 100644 --- a/packages/antd/src/fields/password.tsx +++ b/packages/antd/src/fields/password.tsx @@ -1,179 +1,53 @@ import React, { useState } from 'react' -import styled from 'styled-components' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Input } from 'antd' -import { InputProps } from 'antd/es/input' -import { connect, registerFormField } from '@uform/react' -import { mapStyledProps } from '../utils' +import { PasswordProps } from 'antd/lib/input' +import { PasswordStrength } from '@uform/react-shared-components' +import styled from 'styled-components' +import { mapStyledProps } from '../shared' -var isNum = function(c) { - return c >= 48 && c <= 57 -} -var isLower = function(c) { - return c >= 97 && c <= 122 -} -var isUpper = function(c) { - return c >= 65 && c <= 90 -} -var isSymbol = function(c) { - return !(isLower(c) || isUpper(c) || isNum(c)) -} -var isLetter = function(c) { - return isLower(c) || isUpper(c) +export interface IPasswordProps extends PasswordProps { + checkStrength: boolean } -const getStrength = val => { - if (!val) { - return 0 - } - let num = 0 - let lower = 0 - let upper = 0 - let symbol = 0 - let MNS = 0 - let rep = 0 - let repC = 0 - let consecutive = 0 - let sequential = 0 - const len = () => num + lower + upper + symbol - const require = () => { - var re = num > 0 ? 1 : 0 - re += lower > 0 ? 1 : 0 - re += upper > 0 ? 1 : 0 - re += symbol > 0 ? 1 : 0 - if (re > 2 && len() >= 8) { - return re + 1 - } else { - return 0 - } - } - for (var i = 0; i < val.length; i++) { - var c = val.charCodeAt(i) - if (isNum(c)) { - num++ - if (i !== 0 && i !== val.length - 1) { - MNS++ - } - if (i > 0 && isNum(val.charCodeAt(i - 1))) { - consecutive++ - } - } else if (isLower(c)) { - lower++ - if (i > 0 && isLower(val.charCodeAt(i - 1))) { - consecutive++ - } - } else if (isUpper(c)) { - upper++ - if (i > 0 && isUpper(val.charCodeAt(i - 1))) { - consecutive++ - } - } else { - symbol++ - if (i !== 0 && i !== val.length - 1) { - MNS++ - } - } - var exists = false - for (var j = 0; j < val.length; j++) { - if (val[i] === val[j] && i !== j) { - exists = true - repC += Math.abs(val.length / (j - i)) - } - } - if (exists) { - rep++ - var unique = val.length - rep - repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC) - } - if (i > 1) { - var last1 = val.charCodeAt(i - 1) - var last2 = val.charCodeAt(i - 2) - if (isLetter(c)) { - if (isLetter(last1) && isLetter(last2)) { - var v = val.toLowerCase() - var vi = v.charCodeAt(i) - var vi1 = v.charCodeAt(i - 1) - var vi2 = v.charCodeAt(i - 2) - if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) { - sequential++ - } - } - } else if (isNum(c)) { - if (isNum(last1) && isNum(last2)) { - if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { - sequential++ - } - } - } else { - if (isSymbol(last1) && isSymbol(last2)) { - if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { - sequential++ +const Password: React.FC = styled((props: IPasswordProps) => { + const { className, checkStrength, onChange, ...others } = props + const [value, setValue] = useState(props.value || props.defaultValue) + return ( +
+ { + setValue(event.target.value) + if (onChange) { + onChange(event) } - } - } - } - } - let sum = 0 - let length = len() - sum += 4 * length - if (lower > 0) { - sum += 2 * (length - lower) - } - if (upper > 0) { - sum += 2 * (length - upper) - } - if (num !== length) { - sum += 4 * num - } - sum += 6 * symbol - sum += 2 * MNS - sum += 2 * require() - if (length === lower + upper) { - sum -= length - } - if (length === num) { - sum -= num - } - sum -= repC - sum -= 2 * consecutive - sum -= 3 * sequential - sum = sum < 0 ? 0 : sum - sum = sum > 100 ? 100 : sum - - if (sum >= 80) { - return 100 - } else if (sum >= 60) { - return 80 - } else if (sum >= 40) { - return 60 - } else if (sum >= 20) { - return 40 - } else { - return 20 - } -} - -interface IStrengthProps { - strength: number - className?: string -} - -// 校验强度 UI -const StrengthFC = styled(({ strength, className }: IStrengthProps) => ( -
-
-
-
-
-
-
+ {checkStrength && ( + + {score => { + return ( +
+
+
+
+
+
+
+ ) + }} + + )}
-
-))` + ) +})` .password-strength-wrapper { background: #e0e0e0; margin-bottom: 3px; @@ -210,42 +84,9 @@ const StrengthFC = styled(({ strength, className }: IStrengthProps) => ( } ` -export interface IPasswordProps extends Omit { - checkStrength: boolean // 是否启用密码强度校验 - onChange: (value: InputProps['value']) => void -} - -const PasswordFC = (props: Partial) => { - const [strength, setStrength] = useState(0) - - const { checkStrength, ...others } = props - - const onChangeHandler = (e: React.ChangeEvent) => { - const value = e.target.value - - // 开启才计算 - checkStrength && setStrength(getStrength(value)) - - // 回调 - props.onChange && props.onChange(value) - } - - return ( - - - - {checkStrength && } - - ) -} - registerFormField( 'password', connect({ getProps: mapStyledProps - })(PasswordFC) + })(Password) ) diff --git a/packages/antd/src/fields/radio.tsx b/packages/antd/src/fields/radio.tsx index f21dc2c8dee..3c510b49489 100644 --- a/packages/antd/src/fields/radio.tsx +++ b/packages/antd/src/fields/radio.tsx @@ -1,10 +1,10 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Radio } from 'antd' import { transformDataSourceKey, mapStyledProps, mapTextComponent -} from '../utils' +} from '../shared' const { Group: RadioGroup } = Radio diff --git a/packages/antd/src/fields/range.tsx b/packages/antd/src/fields/range.tsx index edab597790a..03c5fa1f730 100644 --- a/packages/antd/src/fields/range.tsx +++ b/packages/antd/src/fields/range.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Slider } from 'antd' -import { connect, registerFormField } from '@uform/react' -import { mapStyledProps } from '../utils' +import { connect, registerFormField } from '@uform/react-schema-renderer' +import { mapStyledProps } from '../shared' export interface ISliderMarks { [key: number]: diff --git a/packages/antd/src/fields/rating.tsx b/packages/antd/src/fields/rating.tsx index 5b3c8cac7df..044cd5fbc9e 100644 --- a/packages/antd/src/fields/rating.tsx +++ b/packages/antd/src/fields/rating.tsx @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Rate } from 'antd' -import { mapStyledProps } from '../utils' +import { mapStyledProps } from '../shared' registerFormField( 'rating', diff --git a/packages/antd/src/fields/string.tsx b/packages/antd/src/fields/string.tsx index 6e6ba694e33..ffd041c0d9d 100644 --- a/packages/antd/src/fields/string.tsx +++ b/packages/antd/src/fields/string.tsx @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Input } from 'antd' -import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils' +import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' registerFormField( 'string', diff --git a/packages/antd/src/fields/table.tsx b/packages/antd/src/fields/table.tsx index 0f5e7950ed2..01be7668509 100644 --- a/packages/antd/src/fields/table.tsx +++ b/packages/antd/src/fields/table.tsx @@ -1,355 +1,165 @@ -import React, { Component } from 'react' +import React from 'react' +import { + registerFormField, + ISchemaFieldComponentProps, + SchemaField, + Schema +} from '@uform/react-schema-renderer' +import { toArr, isFn, isArr } from '@uform/shared' +import { ArrayList } from '@uform/react-shared-components' +import { CircleButton, TextButton } from '../components/Button' +import { Table, Form, Icon } from 'antd' +import { FormItemProps } from '../compat/FormItem' import styled from 'styled-components' -import { registerFormField } from '@uform/react' -import { isFn, toArr } from '@uform/utils' -import { ArrayField } from './array' -export interface IColumnProps { - title?: string - dataIndex?: string - width?: string | number - cell: (item?: any, index?: number) => React.ReactElement +const ArrayComponents = { + CircleButton, + TextButton, + AdditionIcon: () => , + RemoveIcon: () => , + MoveDownIcon: () => , + MoveUpIcon: () => } -class Column extends Component { - public static displayName = '@schema-table-column' - public render() { - return this.props.children - } -} - -registerFormField( - 'table', - styled( - class extends ArrayField { - public createFilter(key, payload) { - const { schema } = this.props - const columnFilter: (key: string, payload: any) => boolean = - schema['x-props'] && schema['x-props'].columnFilter - - return (render, otherwise) => { - if (isFn(columnFilter)) { - return columnFilter(key, payload) - ? isFn(render) - ? render() - : render - : isFn(otherwise) - ? otherwise() - : otherwise - } else { - return render() - } - } - } - - public render() { - const { - value, - schema, - locale, - className, - renderField, - getOrderProperties - } = this.props - const cls = this.getProps('className') - const style = this.getProps('style') - const operationsWidth = this.getProps('operationsWidth') - return ( -
-
- - {getOrderProperties(schema.items).reduce( - (buf, { key, schema }) => { - const filter = this.createFilter(key, schema) - const res = filter( - () => { - return buf.concat( - { - return renderField([index, key]) - }} - /> - ) - }, - () => { - return buf - } - ) - return res - }, - [] - )} - - { - return ( -
- {this.renderRemove(index, item)} - {this.renderMoveDown(index, item)} - {this.renderMoveUp(index)} - {this.renderExtraOperations(index)} -
- ) - }} - /> -
- {this.renderAddition()} -
-
- ) - } +const FormTableField = styled( + (props: ISchemaFieldComponentProps & { className: string }) => { + const { value, schema, className, editable, path, mutators } = props + const { + renderAddition, + renderRemove, + renderMoveDown, + renderMoveUp, + renderEmpty, + renderExtraOperations, + operations, + ...componentProps + } = schema.getExtendsComponentProps() || {} + const onAdd = () => { + const items = Array.isArray(schema.items) + ? schema.items[schema.items.length - 1] + : schema.items + mutators.push(items.getEmptyValue()) } - )` - display: inline-block; - .array-item-addition { - line-height: normal !important; - padding: 10px; - background: #fbfbfb; - border-left: 1px solid #dcdee3; - border-right: 1px solid #dcdee3; - border-bottom: 1px solid #dcdee3; - .ant-btn-text { - color: #888; - i { - margin-right: 3px; + const renderColumns = (items: Schema) => { + return items.mapProperties((props, key) => { + const itemProps = { + ...props.getExtendsItemProps(), + ...props.getExtendsProps() } - } - } - .ant-table-cell-wrapper > .ant-form-item { - margin-bottom: 0; - } - .array-item-operator { - display: flex; - } - ` -) - -export interface ITableProps { - className?: string - dataSource: any -} - -/** - * 轻量级Table - */ -const Table = styled( - class Table extends Component { - public renderCell({ record, col, rowIndex }) { - return ( -
- {isFn(col.cell) - ? col.cell( - record ? record[col.dataIndex] : undefined, - rowIndex, - record - ) - : record - ? record[col.dataIndex] - : undefined} -
- ) - } - - public renderTable(columns, dataSource) { - return ( -
- - - - {columns.map((col, index) => { - return ( - - ) - })} - - - - {dataSource.map((record, rowIndex) => { - return ( - - {columns.map((col, colIndex) => { - return ( - - ) - })} - - ) - })} - {this.renderPlacehodler(dataSource, columns)} - -
-
- {col.title} -
-
- {this.renderCell({ - record, - col, - rowIndex - })} -
-
- ) + return { + title: props.title, + ...itemProps, + key, + dataIndex: key, + render: (value: any, record: any, index: number) => { + return ( + + + + ) + } + } + }) } - - public renderPlacehodler(dataSource, columns) { - if (dataSource.length === 0) { - return ( - - -
- { + return buf.concat(renderColumns(items)) + }, []) + : renderColumns(schema.items) + if (editable) { + columns.push({ + ...operations, + key: 'operations', + dataIndex: 'operations', + render: (value: any, record: any, index: number) => { + return ( + +
+ mutators.remove(index)} + /> + mutators.moveDown(index)} + /> + mutators.moveUp(index)} /> + {isFn(renderExtraOperations) + ? renderExtraOperations(index) + : renderExtraOperations}
- - - ) - } - } - - public getColumns(children) { - const columns: IColumnProps[] = [] - React.Children.forEach>( - children, - child => { - if (React.isValidElement(child)) { - if ( - child.type === Column || - child.type.displayName === '@schema-table-column' - ) { - columns.push(child.props) - } - } +
+ ) } - ) - - return columns - } - - public render() { - const columns = this.getColumns(this.props.children) - const dataSource = toArr(this.props.dataSource) - return ( -
-
-
- {this.renderTable(columns, dataSource)} -
-
-
- ) + }) } + return ( +
+ +
+ + {({ children }) => { + return ( +
+ {children} +
+ ) + }} +
+
+
+ ) } )` - .ant-table { - position: relative; - } - - .ant-table, - .ant-table *, - .ant-table :after, - .ant-table :before { - -webkit-box-sizing: border-box; - box-sizing: border-box; - } - - .ant-table table { - border-collapse: collapse; - border-spacing: 0; - width: 100%; - background: #fff; - display: table !important; - margin: 0 !important; - } - - .ant-table table tr:first-child td { - border-top-width: 0; - } - - .ant-table th { - padding: 0; - background: #ebecf0; - color: #333; - text-align: left; - font-weight: 400; - min-width: 200px; - border: 1px solid #dcdee3; + min-width: 600px; + margin-bottom: 10px; + table { + margin-bottom: 0 !important; + } + .array-table-addition { + background: #fbfbfb; + cursor: pointer; + margin-top: 3px; + border-radius: 3px; + .next-btn-text { + color: #888; + } + .next-icon:before { + width: 16px !important; + font-size: 16px !important; + margin-right: 5px; + } } - - .ant-table th .ant-table-cell-wrapper { - padding: 12px 16px; - overflow: hidden; - text-overflow: ellipsis; - word-break: break-all; + .ant-btn { + color: #888; } - - .ant-table td { - padding: 0; - border: 1px solid #dcdee3; - } - - .ant-table td .ant-table-cell-wrapper { - padding: 12px 16px; - overflow: hidden; - text-overflow: ellipsis; - word-break: break-all; + .array-item-operator { display: flex; - } - - .ant-table.zebra tr:nth-child(odd) td { - background: #fff; - } - - .ant-table.zebra tr:nth-child(2n) td { - background: #f7f8fa; - } - - .ant-table-empty { - color: #a0a2ad; - padding: 32px 0; - text-align: center; - } - - .ant-table-row { - -webkit-transition: all 0.3s ease; - transition: all 0.3s ease; - background: #fff; - color: #333; - border: none !important; - } - - .ant-table-row.hidden { - display: none; - } - - .ant-table-row.hovered, - .ant-table-row.selected { - background: #f2f3f7; - color: #333; - } - - .ant-table-body, - .ant-table-header { - overflow: auto; + button { + margin-right: 8px; + } } ` + +registerFormField('table', FormTableField) diff --git a/packages/antd/src/fields/textarea.tsx b/packages/antd/src/fields/textarea.tsx index b8b8d1cbe8a..97eb7bbc996 100644 --- a/packages/antd/src/fields/textarea.tsx +++ b/packages/antd/src/fields/textarea.tsx @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Input } from 'antd' -import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils' +import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' const { TextArea } = Input diff --git a/packages/antd/src/fields/time.tsx b/packages/antd/src/fields/time.tsx index cd83a2c3ba2..fa314bf049f 100644 --- a/packages/antd/src/fields/time.tsx +++ b/packages/antd/src/fields/time.tsx @@ -1,7 +1,7 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import moment from 'moment' import { TimePicker } from 'antd' -import { mapStyledProps, mapTextComponent } from '../utils' +import { mapStyledProps, mapTextComponent } from '../shared' registerFormField( 'time', @@ -9,7 +9,7 @@ registerFormField( getValueFromEvent(_, value) { return value }, - getProps: (props, fieldProps) => { + getProps: (props: any, fieldProps) => { const { value, disabled = false } = props try { if (!disabled && value) { diff --git a/packages/antd/src/fields/transfer.tsx b/packages/antd/src/fields/transfer.tsx index 2c30691b498..b7761ccb845 100644 --- a/packages/antd/src/fields/transfer.tsx +++ b/packages/antd/src/fields/transfer.tsx @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Transfer } from 'antd' -import { mapStyledProps } from '../utils' +import { mapStyledProps } from '../shared' registerFormField( 'transfer', diff --git a/packages/antd/src/fields/upload.tsx b/packages/antd/src/fields/upload.tsx index d3ec524d0b7..896e59ed4e8 100644 --- a/packages/antd/src/fields/upload.tsx +++ b/packages/antd/src/fields/upload.tsx @@ -1,8 +1,8 @@ import React from 'react' -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Button, Upload, Icon } from 'antd' import styled from 'styled-components' -import { toArr, isArr, isEqual, mapStyledProps } from '../utils' +import { toArr, isArr, isEqual, mapStyledProps } from '../shared' const { Dragger: UploadDragger } = Upload diff --git a/packages/antd/src/form.tsx b/packages/antd/src/form.tsx deleted file mode 100644 index 13857383c1f..00000000000 --- a/packages/antd/src/form.tsx +++ /dev/null @@ -1,481 +0,0 @@ -import React from 'react' -import classNames from 'classnames' -import { Row, Col, Popover, Icon } from 'antd' -import styled from 'styled-components' -import { registerFormWrapper, registerFieldMiddleware } from '@uform/react' -import { IFormItemProps, IFormProps } from '@uform/types' - -import LOCALE from './locale' -import { isFn, moveTo, isStr, stringLength } from './utils' - -/** - * 轻量级 Form,不包含任何数据管理能力 - */ - -export const { - Provider: FormLayoutProvider, - Consumer: FormLayoutConsumer -} = React.createContext(undefined) - -const normalizeCol = col => { - return typeof col === 'object' ? col : { span: col } -} - -const getParentNode = (node, selector) => { - if (!node || (node && !node.matches)) { - return - } - if (node.matches(selector)) { - return node - } else { - return getParentNode(node.parentNode || node.parentElement, selector) - } -} - -const isPopDescription = (description, maxTipsNum = 30) => { - if (isStr(description)) { - return stringLength(description) > maxTipsNum - } else { - return React.isValidElement(description) - } -} - -export const FormItem = styled( - class FormItem extends React.Component { - public static defaultProps = { - prefix: 'ant-' - } - - public render() { - /* eslint-disable @typescript-eslint/no-unused-vars */ - const { - className, - labelAlign, - labelTextAlign, - style, - prefix, - wrapperCol, - labelCol, - size, - help, - extra, - noMinHeight, - isTableColItem, - validateState, - autoAddColon, - maxTipsNum, - required, - type, - schema, - ...others - } = this.props - /* eslint-enable @typescript-eslint/no-unused-vars */ - const itemClassName = classNames({ - [`${prefix}form-item`]: true, - [`${prefix}${labelAlign}`]: labelAlign, - [`has-${validateState}`]: !!validateState, - [`${prefix}${size}`]: !!size, - [`${className}`]: !!className, - [`field-${type}`]: !!type - }) - - // 垂直模式并且左对齐才用到 - const Tag = (wrapperCol || labelCol) && labelAlign !== 'top' ? Row : 'div' - const label = labelAlign === 'inset' ? null : this.getItemLabel() - return ( - - {label} - {this.getItemWrapper()} - - ) - } - - private getItemLabel() { - const { - id, - required, - label, - labelCol, - wrapperCol, - prefix, - extra, - labelAlign, - labelTextAlign, - autoAddColon, - isTableColItem, - maxTipsNum - } = this.props - - if (!label || isTableColItem) { - return null - } - - const ele = ( - // @ts-ignore - - ) - - const cls = classNames({ - [`${prefix}form-item-label`]: true, - [`${prefix}${labelTextAlign}`]: !!labelTextAlign - }) - - if ((wrapperCol || labelCol) && labelAlign !== 'top') { - return ( - - {ele} - {isPopDescription(extra, maxTipsNum) && this.renderHelper()} - - ) - } - - return ( -
- {ele} - {isPopDescription(extra, maxTipsNum) && this.renderHelper()} -
- ) - } - - private getItemWrapper() { - const { - labelCol, - wrapperCol, - children, - extra, - label, - labelAlign, - help, - prefix, - noMinHeight, - size, - isTableColItem, - maxTipsNum - } = this.props - - const message = ( -
- {help &&
{help}
} - {!help && !isPopDescription(extra, maxTipsNum) && ( -
{extra}
- )} -
- ) - const ele = ( -
- {React.cloneElement(children, { size })} - {message} -
- ) - if ( - (wrapperCol || labelCol) && - labelAlign !== 'top' && - !isTableColItem && - label - ) { - return ( - - {ele} - - ) - } - - return {ele} - } - - private renderHelper() { - return ( - - {/* TODO antd 没有 size 属性 */} - - - ) - } - } -)` - margin-bottom: 0 !important; - .ant-form-item-control-wrapper { - line-height: 32px; - } - .ant-form-item-control { - line-height: 32px; - } - &.field-table { - .ant-form-item-control { - overflow: auto; - } - } - .antd-uploader { - display: block; - } - .ant-form-item-msg { - &.ant-form-item-space { - min-height: 18px; - margin-bottom: 2px; - .ant-form-item-help, - .ant-form-item-extra { - margin-top: 0; - line-height: 1.5; - } - } - } - .ant-form-tips { - margin-left: -5px; - margin-right: 10px; - transform: translateY(1px); - } - .ant-form-item-extra { - color: #888; - font-size: 12px; - line-height: 1.7; - } - .ant-col { - padding-right: 0; - } - .ant-card-head { - background: none; - } - .ant-form-item-label label { - color: #666; - font-size: 14px; - &.no-colon:after { - content: ''; - } - } - ul { - padding: 0; - li { - margin: 0; - & + li { - margin: 0; - } - } - } -` - -const toArr = val => (Array.isArray(val) ? val : val ? [val] : []) - -registerFormWrapper(OriginForm => { - OriginForm = styled(OriginForm)` - &.ant-form-inline, - .ant-form-inline { - display: flex; - .rs-uform-content { - margin-right: 15px; - } - .ant-form-item { - display: inline-block; - vertical-align: top; - } - .ant-form-item:not(:last-child) { - margin-right: 20px; - } - - .ant-form-item.ant-left .ant-form-item-control { - display: inline-block; - display: table-cell\0; - vertical-align: top; - line-height: 0; - } - } - .ant-form-item-label { - line-height: 32px; - } - .ant-form-item-label label[required]:before { - margin-right: 4px; - content: '*'; - color: #ff3000; - } - .ant-form-item-help { - margin-top: 4px; - font-size: 12px; - line-height: 1.5; - color: #999; - } - .ant-form-item.has-error .ant-form-item-help { - color: #ff3000; - } - - .ant-table { - table { - table-layout: auto; - } - } - ` - - class Form extends React.Component { - public static defaultProps = { - component: 'form', - prefix: 'ant-', - size: 'default', - labelAlign: 'left', - layout: 'horizontal', - locale: LOCALE, - autoAddColon: true - } - - public static displayName = 'SchemaForm' - public static LOCALE = LOCALE - private FormRef = React.createRef() - - public render() { - const { - className, - inline, - size, - labelAlign, - labelTextAlign, - autoAddColon, - children, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - labelCol, - layout, - wrapperCol, - maxTipsNum, - style, - prefix, - ...others - } = this.props - - const isInline = inline || layout === 'line' - const formClassName = classNames({ - [`${prefix}form`]: true, - [`${prefix}form-${isInline ? 'inline' : layout}`]: true, - [`${prefix}${size}`]: size, - [`${prefix}form-${labelAlign}`]: !!labelAlign, - [className]: !!className - }) - return ( - - - {children} - - - ) - } - - private validateFailedHandler(onValidateFailed) { - return (...args) => { - if (isFn(onValidateFailed)) { - onValidateFailed(...args) - } - const container = this.FormRef.current as HTMLElement - if (container) { - const errors = container.querySelectorAll('.ant-form-item-help') - if (errors && errors.length) { - const node = getParentNode(errors[0], '.ant-form-item') - if (node) { - moveTo(node) - } - } - } - } - } - } - - Form.LOCALE = LOCALE - - return Form -}) - -const isTableColItem = (path, getSchema) => { - const schema = getSchema(path) - return schema && schema.type === 'array' && schema['x-component'] === 'table' -} - -registerFieldMiddleware(Field => { - return props => { - const { - name, - errors, - editable, - path, - required, - schema, - schemaPath, - getSchema - } = props - if (path.length === 0) { - // 根节点是不需要包FormItem的 - return React.createElement(Field, props) - } - return React.createElement( - FormLayoutConsumer, - {}, - ({ - labelAlign, - labelTextAlign, - labelCol, - maxTipsNum, - wrapperCol, - size, - autoAddColon - }) => { - return React.createElement( - FormItem, - { - labelAlign, - labelTextAlign, - labelCol, - maxTipsNum, - wrapperCol, - autoAddColon, - size, - required: editable === false ? false : required, - ...schema['x-item-props'], - label: schema.title, - noMinHeight: schema.type === 'object' && !schema['x-component'], - isTableColItem: isTableColItem( - schemaPath.slice(0, schemaPath.length - 2), - getSchema - ), - type: schema['x-component'] || schema.type, - id: name, - validateState: toArr(errors).length ? 'error' : undefined, - extra: schema.description, - help: - toArr(errors).join(' , ') || - (schema['x-item-props'] && schema['x-item-props'].help) - }, - React.createElement(Field, props) - ) - } - ) - } -}) diff --git a/packages/antd/src/index.tsx b/packages/antd/src/index.tsx index 8534b7ef68b..85d9569b902 100644 --- a/packages/antd/src/index.tsx +++ b/packages/antd/src/index.tsx @@ -1,44 +1,17 @@ -import './form' -import './fields/string' -import './fields/number' -import './fields/boolean' -import './fields/date' -import './fields/time' -import './fields/range' -import './fields/upload' -import './fields/checkbox' -import './fields/radio' -import './fields/rating' -import './fields/transfer' -import './fields/array' -import './fields/table' -import './fields/textarea' -import './fields/password' -import './fields/cards' - -export * from '@uform/react' -export * from './components/formButtonGroup' -export * from './components/button' -export * from './components/layout' import React from 'react' import { - SchemaForm as InternalSchemaForm, - Field as InternalField -} from '@uform/react' -import { SchemaFormProps, FieldProps } from './type' - -export { mapStyledProps, mapTextComponent } from './utils' - -export default class SchemaForm extends React.Component> { - render() { - return - } -} - -export class Field extends React.Component< - FieldProps -> { - render() { - return - } -} + SchemaMarkupForm, + SchemaMarkupField +} from '@uform/react-schema-renderer' +import { INextSchemaFormProps, INextSchemaFieldProps } from './types' +import './fields' +import './compat' +export * from '@uform/react-schema-renderer' +export * from './components' +export * from './types' +export { mapStyledProps, mapTextComponent } from './shared' +export const SchemaForm: React.FC< + INextSchemaFormProps +> = SchemaMarkupForm as any +export const Field: React.FC = SchemaMarkupField +export default SchemaForm diff --git a/packages/antd/src/locale.ts b/packages/antd/src/locale.ts deleted file mode 100644 index aa28ff56c6e..00000000000 --- a/packages/antd/src/locale.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -export default { - addItem: '添加', - array_invalid_minItems: '条目数不允许小于%s条', - array_invalid_maxItems: '条目数不允许大于%s条', - operations: '操作' -} diff --git a/packages/antd/src/shared.ts b/packages/antd/src/shared.ts new file mode 100644 index 00000000000..64e0363713a --- /dev/null +++ b/packages/antd/src/shared.ts @@ -0,0 +1,75 @@ +import React from 'react' +import { PreviewText } from '@uform/react-shared-components' +import { + IConnectProps, + MergedFieldComponentProps +} from '@uform/react-schema-renderer' +import { Select } from './components/Select' +export * from '@uform/shared' + +export const mapTextComponent = ( + Target: React.JSXElementConstructor, + props: any = {}, + fieldProps: any = {} +): React.JSXElementConstructor => { + const { editable } = fieldProps + if (editable !== undefined) { + if (editable === false) { + return PreviewText + } + } + if (Array.isArray(props.dataSource)) { + return Select + } + return Target +} + +export const acceptEnum = (component: React.JSXElementConstructor) => { + return ({ dataSource, ...others }) => { + if (dataSource) { + return React.createElement(Select, { dataSource, ...others }) + } else { + return React.createElement(component, others) + } + } +} + +export const transformDataSourceKey = (component, dataSourceKey) => { + return ({ dataSource, ...others }) => { + return React.createElement(component, { + [dataSourceKey]: dataSource, + ...others + }) + } +} + +export const normalizeCol = ( + col: { span: number; offset?: number } | number, + defaultValue: { span: number } +): { span: number; offset?: number } => { + if (!col) { + return defaultValue + } else { + return typeof col === 'object' ? col : { span: Number(col) } + } +} + +export const mapStyledProps = ( + props: IConnectProps, + fieldProps: MergedFieldComponentProps +) => { + const { loading, errors } = fieldProps + if (loading) { + props.state = props.state || 'loading' + } else if (errors && errors.length) { + props.state = 'error' + } +} + +export const compose = (...args: any[]) => { + return (payload: any, ...extra: any[]) => { + return args.reduce((buf, fn) => { + return buf !== undefined ? fn(buf, ...extra) : fn(payload, ...extra) + }, payload) + } +} diff --git a/packages/antd/src/type.tsx b/packages/antd/src/type.tsx deleted file mode 100644 index 18b6114d416..00000000000 --- a/packages/antd/src/type.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { ColProps } from 'antd/es/col' -import { CardProps } from 'antd/es/card' -import { BaseButtonProps } from 'antd/es/button/button' -import { - IFormActions, - ISchema, - IEffects, - IFieldError, - TextAlign, - Size, - Layout, - TextEl, - LabelAlign, - IAsyncFormActions -} from '@uform/types' -import { SwitchProps } from 'antd/lib/switch' -import { CheckboxGroupProps } from 'antd/lib/checkbox' -import { - DatePickerProps, - RangePickerProps, - MonthPickerProps, - WeekPickerProps -} from 'antd/lib/date-picker/interface' -import { InputNumberProps } from 'antd/lib/input-number' -import { IPasswordProps } from './fields/password' -import { RadioGroupProps } from 'antd/lib/radio' -import { ISliderProps } from './fields/range' -import { RateProps } from 'antd/lib/rate' -import { InputProps } from 'antd/lib/input' -import { TextAreaProps } from 'antd/es/input' -import { TimePickerProps } from 'antd/lib/time-picker' -import { TransferProps } from 'antd/lib/transfer' -import { IUploaderProps } from './fields/upload' -import { SelectProps } from 'antd/lib/select' - -type ColSpanType = number | string - -export interface ColSize { - span?: ColSpanType - offset?: ColSpanType -} - -export interface ILocaleMessages { - [key: string]: string | ILocaleMessages -} - -export interface IFormLayoutProps { - className?: string - inline?: boolean - labelAlign?: LabelAlign - wrapperCol?: ColProps | number - labelCol?: ColProps | number - labelTextAlign?: TextAlign - size?: Size - style?: React.CSSProperties -} - -export interface IFormItemGridProps { - cols?: Array - description?: TextEl - gutter?: number - title?: TextEl -} - -export interface IRowProps { - prefix?: string - pure?: boolean - wrap?: boolean - fixed?: boolean - hidden?: boolean - className?: string - fixedWidth?: string | number - style?: React.CSSProperties - component?: keyof JSX.IntrinsicElements | React.ComponentType - gutter?: string - align?: string | number - justify?: string | number - children: React.ReactNode -} - -export interface IColProps extends ColProps { - prefix?: string - pure?: boolean - className?: string - fixedSpan?: string | number - fixedOffset?: string | number - hidden?: boolean - align?: any - component?: keyof JSX.IntrinsicElements | React.ComponentType - children?: React.ReactNode - xxs?: ColSpanType | ColSize - xs?: ColSpanType | ColSize - s?: ColSpanType | ColSize - m?: ColSpanType | ColSize - l?: ColSpanType | ColSize - xl?: ColSpanType | ColSize -} - -export interface IFormCardProps extends CardProps { - className?: string -} - -export interface IFormBlockProps extends CardProps { - className?: string -} - -export type TFormCardOrFormBlockProps = Omit - -export interface IFormTextBox { - text?: string - name?: string - title?: TextEl - description?: TextEl - gutter?: number - required?: boolean -} - -export interface IFormButtonGroupProps { - sticky?: boolean - style?: React.CSSProperties - itemStyle?: React.CSSProperties - className?: string - align?: 'left' | 'right' | 'start' | 'end' | 'top' | 'bottom' | 'center' - triggerDistance?: number - zIndex?: number - span?: ColSpanType - offset?: ColSpanType -} - -export interface ISubmitProps extends Omit { - showLoading?: boolean -} - -export interface SchemaFormProps { - actions?: IFormActions | IAsyncFormActions - initialValues?: V - defaultValue?: V - value?: V - editable?: boolean | ((name: string) => boolean) - effects?: IEffects - locale?: ILocaleMessages - schema?: ISchema - onChange?: (values: V) => void - onReset?: (values: V) => void - onSubmit?: (values: V) => void - onValidateFailed?: (fieldErrors: IFieldError[]) => void - autoAddColon?: boolean - className?: string - inline?: boolean - layout?: Layout - maxTipsNum?: number - labelAlign?: LabelAlign - labelTextAlign?: TextAlign - labelCol?: ColSize | number - wrapperCol?: ColSize | number - size?: Size - style?: React.CSSProperties - prefix?: string -} - -interface InternalFieldTypes { - boolean: SwitchProps | SelectProps - checkbox: CheckboxGroupProps - date: DatePickerProps - daterange: RangePickerProps - month: MonthPickerProps - week: WeekPickerProps - year: DatePickerProps - number: InputNumberProps | SelectProps - password: IPasswordProps - radio: RadioGroupProps - range: ISliderProps - rating: RateProps - string: InputProps | SelectProps - textarea: TextAreaProps | SelectProps - time: TimePickerProps - transfer: TransferProps - upload: IUploaderProps -} - -export interface FieldProps extends ISchema { - type?: T - name?: string - editable?: boolean - ['x-props']?: T extends keyof InternalFieldTypes ? InternalFieldTypes[T] : any -} diff --git a/packages/antd/src/types.ts b/packages/antd/src/types.ts new file mode 100644 index 00000000000..879cfba78ae --- /dev/null +++ b/packages/antd/src/types.ts @@ -0,0 +1,90 @@ +import { ButtonProps } from 'antd/lib/button' +import { FormProps, FormItemProps as ItemProps } from 'antd/lib/form' +import { + StepsProps as StepProps, + StepProps as StepItemProps +} from 'antd/lib/steps' +import { + ISchemaFormProps, + IMarkupSchemaFieldProps, + ISchemaFieldComponentProps +} from '@uform/react-schema-renderer' +import { StyledComponent } from 'styled-components' + +type ColSpanType = number | string + +export type INextSchemaFormProps = ISchemaFormProps & + FormProps & + IFormItemTopProps + +export type INextSchemaFieldProps = IMarkupSchemaFieldProps + +export interface ISubmitProps extends ButtonProps { + onSubmit?: ISchemaFormProps['onSubmit'] + showLoading?: boolean +} + +export interface IResetProps extends ButtonProps { + forceClear?: boolean + validate?: boolean +} + +export type IFormItemTopProps = React.PropsWithChildren< + Exclude< + Pick, + 'labelCol' | 'wrapperCol' + > & { + inline?: boolean + className?: string + style?: React.CSSProperties + labelCol?: number | { span: number; offset?: number } + wrapperCol?: number | { span: number; offset?: number } + } +> + +export interface ICompatItemProps + extends Exclude, + Partial { + labelCol?: number | { span: number; offset?: number } + wrapperCol?: number | { span: number; offset?: number } +} + +export type StyledCP

= StyledComponent< + (props: React.PropsWithChildren

) => React.ReactElement, + any, + {}, + never +> + +export type StyledCC = StyledCP & Statics + +export interface IFormButtonGroupProps { + sticky?: boolean + style?: React.CSSProperties + itemStyle?: React.CSSProperties + className?: string + align?: 'left' | 'right' | 'start' | 'end' | 'top' | 'bottom' | 'center' + triggerDistance?: number + zIndex?: number + span?: ColSpanType + offset?: ColSpanType +} + +export interface IItemProps { + title?: React.ReactText + description?: React.ReactText +} + +export interface IFormItemGridProps extends IItemProps { + cols?: Array + gutter?: number +} + +export interface IFormTextBox extends IItemProps { + text?: string + gutter?: number +} + +export interface IFormStep extends StepProps { + dataSource: StepItemProps[] +} diff --git a/packages/antd/src/utils.tsx b/packages/antd/src/utils.tsx deleted file mode 100644 index d449d403cd1..00000000000 --- a/packages/antd/src/utils.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import React from 'react' -import { Select as AntSelect, Icon } from 'antd' -import ReactDOM from 'react-dom' -import styled from 'styled-components' -import { isFn } from '@uform/utils' -import { IConnectProps, IFieldProps } from '@uform/react' -export * from '@uform/utils' - -export interface ISelectProps { - dataSource: any[] - className: string -} - -export interface IElement extends Element { - oldHTML?: string -} -const MoveTo = typeof window !== 'undefined' ? require('moveto') : null -const WrapSelect = styled( - class extends React.Component { - public render() { - const { dataSource = [], ...others } = this.props - const children = dataSource.map(item => { - const { label, value, ...others } = item - return ( - - {label} - - ) - }) - return ( - - {children} - - ) - } - } -)` - min-width: 100px; - width: 100%; -` - -const Text = styled(props => { - let value - if (props.dataSource && props.dataSource.length) { - const find = props.dataSource.filter(({ value }) => - Array.isArray(props.value) - ? props.value.some(val => val == value) - : props.value == value - ) - value = find.map(item => item.label).join(' , ') - } else { - value = Array.isArray(props.value) - ? props.value.join(' ~ ') - : String( - props.value === undefined || props.value === null ? '' : props.value - ) - } - return ( -

- {value || 'N/A'} - {props.innerAfter ? ' ' + props.innerAfter : ''} - {props.addonAfter ? ' ' + props.addonAfter : ''} -
- ) -})` - height: 32px; - line-height: 32px; - vertical-align: middle; - font-size: 13px; - color: #333; - &.small { - height: 24px; - line-height: 24px; - } - &.large { - height: 40px; - line-height: 40px; - } -` - -export interface IStateLoadingProps { - state?: string - dataSource: any[] -} - -const loadingSvg = - '' - -export const StateLoading = (Target: React.ComponentClass) => { - return class Select extends React.Component { - public wrapper: React.ReactInstance - public wrapperDOM: HTMLElement - public classList: string[] - - public componentDidMount() { - if (this.wrapper) { - this.wrapperDOM = ReactDOM.findDOMNode(this.wrapper) - this.mapState() - } - } - - public componentDidUpdate() { - this.mapState() - } - - public render() { - return ( - { - if (inst) { - this.wrapper = inst - } - }} - {...this.props} - /> - ) - } - - public mapState() { - const { state } = this.props - const loadingName = 'anticon-spin' - const iconSizeClassNames = [ - 'xxs', - 'xs', - 'small', - 'medium', - 'large', - 'xl', - 'xxl', - 'xxxl' - ] - this.classList = this.classList || [] - - if (this.wrapperDOM) { - const icon: IElement = this.wrapperDOM.querySelector('.anticon') - if (!icon || !icon.classList) { - return - } - - if (state === 'loading') { - icon.classList.forEach(className => { - if (className.indexOf('anticon-') > -1) { - if ( - className !== loadingName && - iconSizeClassNames.every(val => `anticon-${val}` !== className) - ) { - icon.classList.remove(className) - this.classList.push(className) - } - } - }) - if (icon.innerHTML) { - icon.oldHTML = icon.innerHTML - icon.innerHTML = loadingSvg - } - if (!icon.classList.contains(loadingName)) { - icon.classList.add(loadingName) - } - } else { - icon.classList.remove(loadingName) - this.classList.forEach(className => { - icon.classList.add(className) - }) - if (icon.oldHTML) { - icon.innerHTML = icon.oldHTML - } - this.classList = [] - } - } - } - } -} - -const Select = StateLoading(WrapSelect) - -export const acceptEnum = component => { - return ({ dataSource, ...others }) => { - if (dataSource || others.showSearch) { - return React.createElement(Select, { dataSource, ...others }) - } else { - return React.createElement(component, others) - } - } -} - -export const mapStyledProps = ( - props: IConnectProps, - { loading, size }: IFieldProps -) => { - if (loading) { - props.state = props.state || 'loading' - props.suffix = props.suffix || ( - - ) - } else { - props.suffix = props.suffix || - } - if (size) { - props.size = size - } -} - -export const mapTextComponent = ( - Target: React.ComponentClass, - props, - { - editable, - name - }: { editable: boolean | ((name: string) => boolean); name: string } -): React.ComponentClass => { - if (editable !== undefined) { - if (isFn(editable)) { - if (!editable(name)) { - return Text - } - } else if (editable === false) { - return Text - } - } - return Target -} - -export const compose = (...args) => { - return (payload, ...extra) => { - return args.reduce((buf, fn) => { - return buf !== undefined ? fn(buf, ...extra) : fn(payload, ...extra) - }, payload) - } -} - -export const transformDataSourceKey = (component, dataSourceKey) => { - return ({ dataSource, ...others }) => { - return React.createElement(component, { - [dataSourceKey]: dataSource, - ...others - }) - } -} - -export const moveTo = element => { - if (!element || !MoveTo) { - return - } - if (element.scrollIntoView) { - element.scrollIntoView({ - behavior: 'smooth', - inline: 'start', - block: 'start' - }) - } else { - new MoveTo().move(element.getBoundingClientRect().top) - } -} diff --git a/packages/builder-next/README.md b/packages/builder-next/README.md deleted file mode 100644 index 9d2412db39e..00000000000 --- a/packages/builder-next/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# @uform/builder-next -> UForm 可视化配置 next实现 \ No newline at end of file diff --git a/packages/builder-next/src/index.js b/packages/builder-next/src/index.js deleted file mode 100644 index 2957247e88f..00000000000 --- a/packages/builder-next/src/index.js +++ /dev/null @@ -1,138 +0,0 @@ -import React from 'react' -import SchemaForm, { FormButtonGroup, Submit, Reset } from '@uform/next' -import Builder from '@uform/builder' - -import { - Button, - Collapse, - Message, - Upload, - Input, - Select, - DatePicker, - Icon, - Checkbox, - NumberPicker, - TimePicker, - Radio, - Form, - Tab -} from '@alifd/next' - -// style -import '@alifd/next/dist/next.css' - -SchemaForm.FormButtonGroup = FormButtonGroup -SchemaForm.Submit = Submit -SchemaForm.Reset = Reset - -const renderSchema = {} - -const props = { - UI: { - version: '1.x', - Button, - Accordion: Collapse, - Toast: Message, - Upload, - Input, - Select, - Icon, - DatePicker, - TimePicker, - Checkbox, - NumberPicker, - Radio, - RadioGroup: Radio.Group, - TabPane: Tab.Item, - Form, - Tab - }, - // 主题: dark/light,默认dark - themeStyle: 'dark', - // 是否展示布局组件,默认为false - showLayoutField: true, - // 是否展示预览按钮,默认为true - showPreviewBtn: true, - // 是否展示源码按钮 - showSourceCodeBtn: true, - // 控制返回按钮点击事件 - onBackBtnClick: () => { - alert('点击了返回') - }, - // 额外全局按钮 - globalButtonList: [ - // { - // key: 'submit', - // title: '自定义保存', - // render: (props) => { - // return {props.children} - // }, - // props: { - // // loading: true, - // }, - // }, { - // key: 'cancel', - // title: '取消', - // props: { - // onClick: () => { - // alert('点击取消'); - // } - // }, - // } - ], - // 是否展示全局配置 - showGlobalCfg: true, - // 全局配置额外项 - extraGlobalCfgList: [ - { - name: 'labelCol', - title: 'label宽度占比', - type: 'string' - }, - { - name: 'wrapperCol', - title: 'wrapper宽度占比', - type: 'string' - } - ], - globalCfg: {}, - supportFieldList: [], - includeFieldListKeyList: [ - 'input', - 'multipleInput', - 'number', - 'radio', - 'checkbox', - 'date', - 'month', - 'daterange', - 'time' - ], - - // 渲染引擎 - renderEngine: SchemaForm, - - schema: renderSchema, - // onChange: (data) => { - // console.info('index onChange data', data); - // }, - onSubmit: data => { - console.info('index onSubmit data', data) - } -} - -class Component extends React.Component { - constructor(props) { - super(props) - this.state = { - schema: renderSchema - } - } - - render() { - return - } -} - -export default Component diff --git a/packages/builder-next/tsconfig.json b/packages/builder-next/tsconfig.json deleted file mode 100644 index 3f2ad609650..00000000000 --- a/packages/builder-next/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib", - "declaration": false, - "allowJs": true, - "skipLibCheck": true - }, - "include": ["./src/**/*.js", "./src/**/*.ts", "./src/**/*.tsx"], - "exclude": ["./src/__tests__/*"] -} diff --git a/packages/builder/.npmignore b/packages/builder/.npmignore deleted file mode 100644 index 2d6bc728edb..00000000000 --- a/packages/builder/.npmignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules -config -public -scripts -*.log -src/demo -build -__tests__ \ No newline at end of file diff --git a/packages/builder/LISENCE.md b/packages/builder/LISENCE.md deleted file mode 100644 index 2bd9316cd51..00000000000 --- a/packages/builder/LISENCE.md +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015-present, Alibaba Group Holding Limited. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/builder/README.md b/packages/builder/README.md deleted file mode 100644 index c4aaedcacd1..00000000000 --- a/packages/builder/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# @uform/builder -> UForm 可视化搭建实现 \ No newline at end of file diff --git a/packages/builder/package.json b/packages/builder/package.json deleted file mode 100644 index 04021b7ad6a..00000000000 --- a/packages/builder/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "@uform/builder", - "version": "0.4.3", - "license": "MIT", - "main": "lib/index.js", - "repository": { - "type": "git", - "url": "git+https://github.com/alibaba/uform.git" - }, - "bugs": { - "url": "https://github.com/alibaba/uform/issues" - }, - "homepage": "https://github.com/alibaba/uform#readme", - "engines": { - "npm": ">=3.0.0" - }, - "scripts": { - "build": "tsc" - }, - "peerDependencies": { - "@alifd/next": "^1.13.1", - "@babel/runtime": "^7.4.4", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - }, - "resolutions": { - "@types/react": "16.8.23" - }, - "dependencies": { - "@uform/react": "^0.4.3", - "@uform/utils": "^0.4.3", - "@uform/validator": "^0.4.3", - "classnames": "^2.2.5", - "immutability-helper": "^3.0.0", - "lodash.flow": "^3.5.0", - "lodash.isequal": "^4.5.0", - "lodash.merge": "^4.6.1", - "lodash.pick": "^4.4.0", - "lodash.pickby": "^4.6.0", - "lodash.remove": "^4.7.0", - "lodash.uniqby": "^4.7.0", - "moment": "^2.24.0", - "prop-types": "^15.6.1", - "react-dnd": "^7.4.1", - "react-dnd-html5-backend": "^7.4.0", - "react-powerplug": "^1.0.0", - "react-redux": "^5.0.7", - "redux": "^4.0.0", - "redux-logger": "^3.0.6", - "redux-thunk": "^2.2.0", - "styled-components": "^4.1.2", - "uuid": "^3.2.1" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "4d068dad6183e8da294a4c899a158326c0b0b050", - "devDependencies": { - "react-test-renderer": "^16.8.6", - "typescript": "^3.5.2" - } -} diff --git a/packages/builder/src/App.js b/packages/builder/src/App.js deleted file mode 100644 index 7a4c3f81b7a..00000000000 --- a/packages/builder/src/App.js +++ /dev/null @@ -1,366 +0,0 @@ -import React, { Component, createRef } from 'react' -import cls from 'classnames' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { - changePreview, - changeGbConfig, - initSchema, - changeCodeMode, - changeComponent, - editComponent -} from './actions' -import { Divider } from './utils/util' - -import isEqual from 'lodash.isequal' -import AppStyle from './style' - -// components -import { - FieldList, - Preview, - GlobalBtnList, - PropsSetting -} from './components/index' - -import { SchemaForm, Field } from './utils/baseForm' -import defaultGlobalCfgList from './configs/supportGlobalCfgList' - -const noop = () => {} - -class App extends Component { - constructor(props) { - super(props) - this.state = { - systemError: false, - accordionList: [] - } - this.appRef = createRef(null) - this.appHeaderRef = createRef(null) - } - - generateGlobalCfgList = () => { - // Merge custom form global property configuration - const globalCfgList = [ - ...defaultGlobalCfgList, - ...this.props.extraGlobalCfgList - ] - const _globalCfgList = [] - for (let i = globalCfgList.length - 1; i >= 0; i--) { - if ( - !_globalCfgList.find(cfgItem => cfgItem.name === globalCfgList[i].name) - ) { - _globalCfgList.unshift(globalCfgList[i]) - } - } - return _globalCfgList - } - - // global config - renderGlobalConfig = () => { - const globalCfgList = this.generateGlobalCfgList() - - const content = ( - { - this.props.changeGbConfig(value) - }} - defaultValue={this.props.gbConfig} - labelAlign="left" - labelCol={10} - labelTextAlign="right" - > - {globalCfgList.map(props => ( - - ))} - - ) - - return content - } - - getAccordionList = () => { - const list = [ - { - title: '组件配置', - content: ( - - ), - expanded: true - } - ] - - if (this.props.showGlobalCfg) { - list.unshift({ - title: '全局配置', - content: this.renderGlobalConfig(), - expanded: false - }) - } - return list - } - - componentWillMount() { - const { - schema, - globalCfg, - changeGbConfig: _changeGbConfig, - initSchema: _initSchema - } = this.props - - _changeGbConfig(globalCfg) - _initSchema(schema) - } - - componentDidMount() { - this.setState({ - accordionList: this.getAccordionList() - }) - - const appDom = this.appRef.current - const appHeaderDom = this.appHeaderRef.current - - if (appDom.offsetTop !== 0) { - document.querySelector( - '.schamaform-content' - ).style.height = `${window.innerHeight - - appDom.offsetTop - - appHeaderDom.offsetHeight}px` - } - } - - componentWillUnmount() { - // Clear the selected componentId with the selected component - this.props.changeComponent() - this.props.editComponent(null, { - active: false - }) - } - - componentDidUpdate(prevProps) { - const oldProperties = - prevProps.schema && prevProps.schema.properties - ? prevProps.schema.properties - : {} - const { schema = {}, globalCfg = {}, initSchema: _initSchema } = this.props - - const { properties = {} } = schema - - if (!isEqual(properties, oldProperties)) { - _initSchema(schema) - } - - if (!isEqual(globalCfg, this.props.globalCfg)) { - changeGbConfig(globalCfg) - } - } - - componentDidCatch(error, info) { - if (window.location.href.indexOf('av_debug') > -1) { - console.error('Form configurator system error', error, info) - } - this.setState({ - systemError: true - }) - } - - // dynamic import layout component - getLayoutTpl() { - if (!this.props.showLayoutField) return null - - try { - const LayoutList = React.lazy( - () => - new Promise((resolve, reject) => { - import('./components/fields/layout') - .then(result => - resolve(result.default ? result : { default: result }) - ) - .catch(reject) - }) - ) - return ( - Loading...
}> - - - - ) - } catch (e) { - console.error('getEditor function error', e) - return null - } - } - - // dynamic import editor component - getEditorTpl() { - if (!this.props.showSourceCodeBtn) return null - - try { - const Editor = React.lazy( - () => - new Promise((resolve, reject) => { - import('./components/editor/index') - .then(result => - resolve(result.default ? result : { default: result }) - ) - .catch(reject) - }) - ) - return ( - Loading...
}> - - - ) - } catch (e) { - console.error('getEditor function error', e) - return null - } - } - - render() { - const { initSchemaData, renderEngine } = this.props - const { Accordion, version: UIVersion } = this.props.UI - - const contentHeight = window.innerHeight - - return this.state.systemError ? ( -

系统发生异常

- ) : ( - - -
-
- {this.getLayoutTpl()} - -
-
- -
-
- {UIVersion === '1.x' ? ( - - ) : ( - { - this.setState({ - accordionList: list - }) - }} - /> - )} -
-
- {this.getEditorTpl()} -
- ) - } -} - -App.propTypes = { - // 左上角返回按钮事件绑定 - onBackBtnClick: PropTypes.func, - // 是否展示源码编辑按钮 - showSourceCodeBtn: PropTypes.bool, - changeGbConfig: PropTypes.func, - // 当前是否是预览状态 - preview: PropTypes.bool, - changePreview: PropTypes.func, - componentProps: PropTypes.object, - schema: PropTypes.object, - showGlobalCfg: PropTypes.bool, - // 额外注入的全局配置 - extraGlobalCfgList: PropTypes.arrayOf( - PropTypes.objectOf(PropTypes.any, PropTypes.any) - ), - // 全局配置 - globalCfg: PropTypes.object, - // 全局的组件列表 - supportFieldList: PropTypes.array, - // 过滤全局组件列表 - includeFieldListKeyList: PropTypes.arrayOf(PropTypes.string), - supportConfigList: PropTypes.array, - beforeSubmit: PropTypes.func, - onSubmit: PropTypes.func, - onChange: PropTypes.func, - renderEngine: PropTypes.any.isRequired, - formSubmitUrl: PropTypes.string, - showPreviewBtn: PropTypes.bool, - globalButtonList: PropTypes.arrayOf( - PropTypes.objectOf(PropTypes.any, PropTypes.any) - ) -} - -App.defaultProps = { - onBackBtnClick: () => { - window.history.back() - }, - showGlobalCfg: false, - showSourceCodeBtn: false, - extraGlobalCfgList: [], - supportFieldList: [], - includeFieldListKeyList: [], - supportConfigList: [], - globalCfg: {}, - schema: {}, - renderEngine: SchemaForm, - beforeSubmit: noop, - onSubmit: data => {}, - onChange: noop, - formSubmitUrl: '', - showPreviewBtn: true, - globalButtonList: [] -} - -const mapStateToProps = state => state - -const mapDispatchToProps = dispatch => ({ - changePreview: preview => dispatch(changePreview(preview)), - changeGbConfig: data => dispatch(changeGbConfig(data)), - initSchema: data => dispatch(initSchema(data)), - changeCodeMode: codemode => dispatch(changeCodeMode(codemode)), - changeComponent: componentId => dispatch(changeComponent(componentId)), - editComponent: (...args) => dispatch(editComponent(...args)) -}) - -class StyledAppComp extends React.Component { - render() { - return - } -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(StyledAppComp) diff --git a/packages/builder/src/actions/index.js b/packages/builder/src/actions/index.js deleted file mode 100644 index 67377209b00..00000000000 --- a/packages/builder/src/actions/index.js +++ /dev/null @@ -1,154 +0,0 @@ -import uuid from 'uuid' - -/** - * 添加组件 - * @param {Object} component - */ -export const addComponent = (component, existId, id, type, containerId) => ({ - type: 'ADD_COMPONENT', - data: { - id: id || uuid(), - component, - existId, - addType: type, - containerId - } -}) - -/** - * 添加组件并置于编辑状态 - * @param {Object} component - */ -export const addComponentAndEdit = ( - component, - existId, - type, - containerId = [] -) => dispatch => { - const id = component.id || uuid() - - dispatch(addComponent(component, existId, id, type, containerId)) - dispatch(changeComponent(Array.isArray(id) ? id : [...containerId, id])) - dispatch( - showComponentProps(Array.isArray(id) ? id : [...containerId, id], component) - ) - dispatch( - editComponent(Array.isArray(id) ? id : [...containerId, id], { - active: true - }) - ) -} - -/** - * 移动组件 - * @param {Array} sourceId 源ID - * @param {Array} targetId 目标ID - */ -export const moveComponent = (sourceId, targetId) => ({ - type: 'MOVE_COMOPNENT', - data: { - id: sourceId, - targetId - } -}) - -/** - * 改变组件顺序 - */ -export const changeComponentOrder = (sourceId, targetId, containerId) => ({ - type: 'CHANGE_COMPONENT_ORDER', - data: { - id: sourceId, - targetId, - containerId - } -}) - -/** - * 修改组件数据 - */ -export const editComponent = (id, propsData) => ({ - type: 'EDIT_COMPONENT', - data: { - id, - propsData - } -}) - -/** - * 删除组件 - */ -export const deleteComponent = id => ({ - type: 'DELETE_COMPONENT', - data: { - id - } -}) - -/** - * 展示组件属性 - */ -export const showComponentProps = (id, comp) => ({ - type: 'SHOW_COMPONENT_PROPS', - data: { - id, - comp - } -}) - -/** - * 编辑组件属性 - */ -export const editComponentProps = (id, propsData) => ({ - type: 'EDIT_COMPONENT_PROPS', - data: { - id, - propsData - } -}) - -/** - * 改变预览状态 - */ -export const changePreview = preview => ({ - type: 'CHANGE_PREVIEW', - data: { - preview - } -}) - -/** - * 改变源码编辑状态 - */ -export const changeCodeMode = codemode => ({ - type: 'CHANGE_CODEMODE', - data: { - codemode - } -}) - -/** - * 改变当前编辑的组件 - */ -export const changeComponent = componentId => ({ - type: 'CHANGE_COMPONENT', - data: { - componentId - } -}) - -/** - * 修改全局配置 - */ -export const changeGbConfig = data => ({ - type: 'CHANGE_GB_CONFIG', - data -}) - -/** - * 初始化schema - */ -export const initSchema = data => ({ - type: 'INIT_SCHEMA', - data -}) diff --git a/packages/builder/src/components/editor/index.js b/packages/builder/src/components/editor/index.js deleted file mode 100644 index 060f6c7fcb0..00000000000 --- a/packages/builder/src/components/editor/index.js +++ /dev/null @@ -1,137 +0,0 @@ -import React from 'react' -import cls from 'classnames' - -import { connect } from 'react-redux' -import { initSchema, changeGbConfig, changeCodeMode } from '../../actions/index' -import EditorStyle from './style' - -class Component extends React.Component { - componentDidMount() { - /* eslint-disable */ - const defaultValue = this.getValueFromProps() - const theme = this.props.themeStyle === 'dark' ? 'vs-dark' : 'vs' - - if (window.loadedMonaco === true) { - self.monacoInstance = monaco.editor.create( - document.getElementById('J_uformEditor'), - { - language: 'javascript', - theme: theme, - value: defaultValue - } - ) - return false - } - - const script = document.createElement('script') - script.src = - '//g.alicdn.com/ascp-comp/common-monaco-editor/5.0.1/min/vs/loader.js' - document.head.appendChild(script) - - const tpl1 = `data:text/javascript;charset=utf-8,${encodeURIComponent(` - self.MonacoEnvironment = { - baseUrl: "https://g.alicdn.com/ascp-comp/common-monaco-editor/5.0.1/min/" - }; - importScripts( - "https://g.alicdn.com/ascp-comp/common-monaco-editor/5.0.1/min/vs/base/worker/workerMain.js" - )`)}` - - script.onload = () => { - const script2 = document.createElement('script') - const tpl = ` - require.config({ - paths: { - vs: "https://g.alicdn.com/ascp-comp/common-monaco-editor/5.0.1/min/vs" - } - }); - window.MonacoEnvironment = { - getWorkerUrl: function(workerId, label) { - return "${tpl1}" - } - }; - require(["vs/editor/editor.main"], function() { - window.loadedMonaco = true; - self.monacoInstance = self.monacoInstance || monaco.editor.create(document.getElementById("J_uformEditor"), { - language: "javascript", - theme: "${theme}", - value: ${JSON.stringify(defaultValue)} - }) - }) - ` - script2.innerHTML = tpl - document.head.appendChild(script2) - } - /* eslint-enable */ - } - - getValueFromProps() { - const { initSchemaData, gbConfig } = this.props - const val = JSON.stringify( - { - schema: initSchemaData, - gbConfig - }, - null, - '\t' - ) - - return val - } - - componentDidUpdate() { - window.monacoInstance && - window.monacoInstance.setValue(this.getValueFromProps()) - } - - render() { - const { className, codemode } = this.props - - return ( - -
- { - try { - // eslint-disable-next-line - const newValue = new Function( - `return ${window.monacoInstance.getValue()}` - )() - const { schema = {}, gbConfig = {} } = newValue - const { - initSchema: _initSchema, - changeGbConfig: _changeGbConfig - } = this.props - _initSchema(schema) - _changeGbConfig(gbConfig) - } catch (e) { - throw new Error(`格式转换失败,请检查代码: + ${e.message}`) - } - }} - > - 保存源码 - - - ) - } -} - -class StyledEditorComp extends React.Component { - render() { - return - } -} - -const mapStateToProps = state => state - -const mapDispatchToProps = dispatch => ({ - initSchema: data => dispatch(initSchema(data)), - changeGbConfig: data => dispatch(changeGbConfig(data)), - changeCodeMode: codemode => dispatch(changeCodeMode(codemode)) -}) - -export default connect( - mapStateToProps, - mapDispatchToProps -)(StyledEditorComp) diff --git a/packages/builder/src/components/editor/style.js b/packages/builder/src/components/editor/style.js deleted file mode 100644 index 532d4d66120..00000000000 --- a/packages/builder/src/components/editor/style.js +++ /dev/null @@ -1,33 +0,0 @@ -import styled from 'styled-components' - -export default styled.div` - position: absolute; - min-width: 500px; - top: 64px; - right: 0; - bottom: 0; - overflow-y: scroll; - box-shadow: 0 -1px 3px 0 rgba(0, 0, 0, 0.2); - transition: transform 0.2s ease-in; - transform: translate3d(500px, 0, 0); - &.active { - transform: translate3d(0, 0, 0); - } - z-index: 2000; - .editor { - position: absolute; - top: 0; - right: 0; - width: 100%; - height: 100%; - } - .editor-btn { - position: absolute; - top: 10px; - right: 10px; - padding: 4px; - background: rgba(90, 96, 255, 0.95); - color: #fff; - border-radius: 3px; - } -` diff --git a/packages/builder/src/components/fields/field.js b/packages/builder/src/components/fields/field.js deleted file mode 100644 index 62032a84c72..00000000000 --- a/packages/builder/src/components/fields/field.js +++ /dev/null @@ -1,103 +0,0 @@ -import React from 'react' -import { DragSource } from 'react-dnd' -import ItemTypes from '../../constants/itemType' -import uuid from 'uuid' - -const DEFAULT_ICON_URL = - '//gw.alicdn.com/tfs/TB10xa4DbrpK1RjSZTEXXcWAVXa-116-60.png' - -const wrapFieldItem = fieldItem => - !!fieldItem && typeof fieldItem === 'object' - ? { - iconUrl: DEFAULT_ICON_URL, - ...fieldItem - } - : { - type: fieldItem, - icon: '', - iconUrl: DEFAULT_ICON_URL, - width: '58', - height: '30', - title: '自定义组件' - } - -const Box = ({ - addComponentAndEdit, - fieldItem, - isDragging, - connectDragSource -}) => { - const style = { - opacity: isDragging ? 0.4 : 1 - } - - if (isDragging) { - style.filter = 'blur(2px) brightness(.6)' - } - - const newFieldItem = wrapFieldItem(fieldItem) - const { key, iconUrl, width, height, title } = newFieldItem - - return connectDragSource( -
  • { - addComponentAndEdit(newFieldItem) - }} - style={style} - > - - {title} -
  • - ) -} - -export default DragSource( - ItemTypes.FIELD, - { - beginDrag: props => { - const { fieldItem } = props - const id = uuid() - return { fieldItem, id } - }, - endDrag(props, monitor) { - console.info('endDrag') - if (!monitor.didDrop()) { - return - } - console.info('endDrag success') - const item = monitor.getItem() - const dropResult = monitor.getDropResult() - - // @note: 不要直接拿fieldItem,避免下面删掉属性直接影响到原有的fieldItem - const fieldItem = { ...item.fieldItem } - const { addComponentAndEdit } = props - - // 删除多余的跟渲染无关的属性 - try { - ;['height', 'icon', 'iconUrl', 'width'].forEach(key => { - delete fieldItem[key] - }) - } catch (e) {} - - if (dropResult) { - if (dropResult.targetType === 'layout') { - addComponentAndEdit(fieldItem, '', 'layout', dropResult.targetId) - } else { - addComponentAndEdit(fieldItem) - } - } - } - }, - (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging() - }) -)(Box) diff --git a/packages/builder/src/components/fields/index.js b/packages/builder/src/components/fields/index.js deleted file mode 100644 index d0f8a0ccaee..00000000000 --- a/packages/builder/src/components/fields/index.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react' -import cls from 'classnames' -import defaultSupportFieldList from '../../configs/supportFieldList' -import { Header, wrapComp2Class } from '../../utils/util' -import { connect } from 'react-redux' -import { addComponentAndEdit } from '../../actions' -import uniqBy from 'lodash.uniqby' -import { indexStyle as IndexStyle } from './style' -import Field from './field' - -function FieldList(props) { - const { - addComponentAndEdit, - supportFieldList = [], - includeFieldListKeyList = [] - } = props - - let fieldList = defaultSupportFieldList - if (supportFieldList.length) { - fieldList = uniqBy([...supportFieldList, ...defaultSupportFieldList], 'key') - } - if (includeFieldListKeyList.length) { - fieldList = fieldList.filter( - fieldItem => includeFieldListKeyList.indexOf(fieldItem.key) > -1 - ) - } - - return ( - -
    -

    组件

    -

    可将选项拖动到主面板进行编辑

    -
    -
      - {fieldList.map((fieldItem, i) => { - return ( - - ) - })} -
    -
    - ) -} - -const mapStateToProps = state => state - -const mapDispatchToProps = dispatch => ({ - addComponentAndEdit: (...args) => dispatch(addComponentAndEdit(...args)) -}) - -export default connect( - mapStateToProps, - mapDispatchToProps -)(wrapComp2Class(FieldList)) diff --git a/packages/builder/src/components/fields/layout.js b/packages/builder/src/components/fields/layout.js deleted file mode 100644 index a7b6733c067..00000000000 --- a/packages/builder/src/components/fields/layout.js +++ /dev/null @@ -1,63 +0,0 @@ -// 布局 -import React from 'react' -import cls from 'classnames' -import PropTypes from 'prop-types' -import supportLayoutList from '../../configs/supportLayoutList' -import { Header, wrapComp2Class } from '../../utils/util' -import { connect } from 'react-redux' -import { addComponentAndEdit } from '../../actions' -import Field from './layoutField' -import { layoutStyle as LayoutStyle } from './style' - -class Component extends React.Component { - constructor(props) { - super(props) - - this.layoutList = supportLayoutList - } - - renderList() { - return ( -
      - {this.layoutList.map((item, i) => { - return ( - - ) - })} -
    - ) - } - - render() { - return ( - -
    -

    布局

    -

    单击将布局添加入主面板

    -
    - {this.renderList()} -
    - ) - } -} - -Component.propTypes = { - addComponentAndEdit: PropTypes.func -} - -Component.defaultProps = {} - -const mapStateToProps = state => state - -const mapDispatchToProps = dispatch => ({ - addComponentAndEdit: (...args) => dispatch(addComponentAndEdit(...args)) -}) - -export default connect( - mapStateToProps, - mapDispatchToProps -)(wrapComp2Class(Component)) diff --git a/packages/builder/src/components/fields/layoutField.js b/packages/builder/src/components/fields/layoutField.js deleted file mode 100644 index 9a3da8f71ca..00000000000 --- a/packages/builder/src/components/fields/layoutField.js +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react' -import { DragSource } from 'react-dnd' -import ItemTypes from '../../constants/itemType' -import uuid from 'uuid' - -const Box = ({ - addComponentAndEdit, - fieldItem, - isDragging, - connectDragSource -}) => { - const opacity = isDragging ? 0.4 : 1 - const { key, title } = fieldItem - return ( -
  • { - const id = uuid() - const newFieldItem = { - type: 'object', - key: fieldItem.key, - id, - ...fieldItem.__key__data__, - properties: {}, - 'x-props': { - ...fieldItem.__key__data__['x-props'], - _extra: fieldItem - } - } - addComponentAndEdit(newFieldItem) - }} - style={Object.assign({}, { opacity })} - > - {title} -
  • - ) -} - -export default DragSource( - ItemTypes.LAYOUT, - { - beginDrag: props => { - const { fieldItem } = props - const id = uuid() - return { fieldItem, id } - }, - endDrag(props, monitor) { - if (!monitor.didDrop()) { - return - } - - const item = monitor.getItem() - const dropResult = monitor.getDropResult() - const { id } = item - const fieldItem = { ...item.fieldItem } - const { addComponentAndEdit } = props - try { - ;['height', 'icon', 'iconUrl', 'width'].forEach(key => { - delete fieldItem[key] - }) - } catch (e) {} - - if (dropResult) { - const newFieldItem = { - type: 'object', - key: fieldItem.key, - id, - ...fieldItem.__key__data__, - properties: {}, - 'x-props': { - ...fieldItem.__key__data__['x-props'], - _extra: fieldItem - } - } - - addComponentAndEdit(newFieldItem) - } - } - }, - (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging() - }) -)(Box) diff --git a/packages/builder/src/components/fields/style.js b/packages/builder/src/components/fields/style.js deleted file mode 100644 index 986077e5a42..00000000000 --- a/packages/builder/src/components/fields/style.js +++ /dev/null @@ -1,87 +0,0 @@ -import styled from 'styled-components' - -export const indexStyle = styled.div` - .field-list { - font-size: 0; - li { - overflow: hidden; - margin-bottom: 12px; - padding: 0 8px; - display: inline-block; - width: 33.33%; - height: 75px; - font-size: 12px; - text-align: center; - color: ${props => props.theme.whiteColor}; - box-sizing: border-box; - transition: all 0.1s ease; - cursor: pointer; - &:hover { - background: ${props => props.theme.compHoverBgColor}; - } - span { - display: block; - margin: auto; - max-width: 50px; - height: 32px; - word-break: break-all; - } - } - } - .field-icon { - display: block; - margin: 0 auto 12px; - height: 30px; - background-repeat: no-repeat; - background-position: 50% 50%; - } - @media screen and (max-width: 834px) { - .field-list { - li { - width: 100%; - } - } - } -` - -export const layoutStyle = styled.div` - .layout-list { - margin-bottom: 15px; - padding: 0 8px; - font-size: 0; - li { - overflow: hidden; - margin-right: 7px; - width: 70px; - height: 90px; - line-height: 90px; - border-radius: 4px; - display: inline-block; - font-size: 12px; - text-align: center; - background: ${props => props.theme.compHoverBgColor}; - color: ${props => props.theme.whiteColor}; - border: 1px solid ${props => props.theme.compHoverBgColor}; - box-sizing: border-box; - transition: all 0.1s ease; - cursor: pointer; - &:hover { - background: ${props => props.theme.compHoverBgColor}; - border-color: ${props => props.theme.whiteColor}; - } - &:nth-child(3n) { - margin-right: 0; - } - span { - display: block; - margin: auto; - word-break: break-all; - } - } - } -` - -export default { - indexStyle, - layoutStyle -} diff --git a/packages/builder/src/components/globalBtnList/index.js b/packages/builder/src/components/globalBtnList/index.js deleted file mode 100644 index d21eb40fa48..00000000000 --- a/packages/builder/src/components/globalBtnList/index.js +++ /dev/null @@ -1,115 +0,0 @@ -import React from 'react' -import { GLOBAL_BTN_ICON_URL } from '../../configs/theme' -import { wrapSubmitSchema, CustomIcon } from '../../utils/util' -import merge from 'lodash.merge' - -export default props => { - const { - preview, - codemode, - onSubmit, - themeStyle, - changeCodeMode: _changeCodeMode, - globalButtonList, - showPreviewBtn, - showSourceCodeBtn, - UI - } = props - - // 获取主题下的默认icon图片地址 - const globalBtnIconUrlWithTheme = GLOBAL_BTN_ICON_URL[themeStyle] - - // 默认按钮 - const defaultBtnList = [ - { - key: 'preview', - show: showPreviewBtn, - title: preview ? '返回编辑' : '预览', - props: { - onClick: () => { - props.changePreview(!props.preview) - } - }, - iconType: 'eye', - iconUrl: globalBtnIconUrlWithTheme.preview, - iconWidth: 16, - iconHeight: 16 - }, - { - key: 'submit', - title: '保存', - props: { - type: 'primary', - onClick: () => { - onSubmit && - typeof onSubmit === 'function' && - onSubmit({ - schema: wrapSubmitSchema(props.initSchemaData), - globalCfg: props.gbConfig - }) - } - }, - iconType: 'save', - iconUrl: globalBtnIconUrlWithTheme.submit, - iconWidth: 15, - iconHeight: 15 - }, - { - key: 'code', - show: showSourceCodeBtn, - title: codemode ? '关闭源码' : '源码', - props: { - onClick: () => { - _changeCodeMode(!props.codemode) - } - }, - iconUrl: globalBtnIconUrlWithTheme.code, - iconWidth: 21, - iconHeight: 16 - } - ] - - // 合并相同key的数据 - const _globalButtonList = defaultBtnList.map(btnItem => { - const { key } = btnItem - const customBtnItem = globalButtonList.find(btn => btn.key === key) - return customBtnItem ? merge({}, btnItem, customBtnItem) : btnItem - }) - - // 注入额外的数据 - globalButtonList.forEach(btnItem => { - if (['preview', 'submit', 'preview'].indexOf(btnItem.key) === -1) { - _globalButtonList.push(btnItem) - } - }) - - return _globalButtonList.map(btnItem => { - const { - props = {}, - key, - show = true, - title, - iconUrl, - iconWidth, - iconHeight, - render - } = btnItem - - if (!title || !show || !key) return null - - const customIconTpl = iconUrl ? ( - - ) : null - - const originalBtn = ( - - {customIconTpl} - {title} - - ) - - return render - ? React.createElement(render, btnItem, originalBtn) - : originalBtn - }) -} diff --git a/packages/builder/src/components/index.js b/packages/builder/src/components/index.js deleted file mode 100644 index f2a34ea7117..00000000000 --- a/packages/builder/src/components/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import PropsSetting from './props/propsSetting' -import FieldList from './fields/index' -import Preview from './preview/index' -import GlobalBtnList from './globalBtnList/index' - -export { PropsSetting, FieldList, Preview, GlobalBtnList } - -export default {} diff --git a/packages/builder/src/components/preview/card.js b/packages/builder/src/components/preview/card.js deleted file mode 100644 index 051b0d6d575..00000000000 --- a/packages/builder/src/components/preview/card.js +++ /dev/null @@ -1,191 +0,0 @@ -import React, { useImperativeHandle, useRef, forwardRef } from 'react' -import { DragSource, DropTarget } from 'react-dnd' -import cls from 'classnames' -import ItemTypes from '../../constants/itemType' -import { isLayoutWrapper } from '../../utils/util' -import flow from 'lodash.flow' -const style = { - cursor: 'move' -} -const Card = forwardRef( - ( - { - Field, - props, - canDrop, - isOver, - that, - isDragging, - connectDragSource, - connectDropTarget, - id - }, - ref - ) => { - const elementRef = useRef(null) - connectDragSource(elementRef) - connectDropTarget(elementRef) - useImperativeHandle(ref, () => ({ - getNode: () => elementRef.current - })) - - const opacity = isDragging ? 0.2 : 1 - const isActive = canDrop && isOver - let backgroundColor = '#fff' - if (isActive) { - backgroundColor = '#f5f5f5' - } - - const { active = false } = props.schema - const comp = { - ...props.schema - } - - return isLayoutWrapper(comp) ? ( - connectDropTarget( -
    - {!Object.keys(props.schema.properties).length ? ( -

    - 请从左边字段拖拽组件进来这里 -

    - ) : ( - React.createElement(Field, props) - )} -
    { - ev.preventDefault() - that.onMouseClick(id, comp) - }} - /> - -
    - ) - ) : ( -
    - {React.createElement(Field, { ...props })} -
    { - ev.preventDefault() - that.onMouseClick(id, comp) - }} - /> - -
    -
    - ) - } -) - -export default flow( - DragSource( - ItemTypes.CARD, - { - beginDrag: props => { - const { id } = props - return { - source: 'card', - id - } - }, - endDrag(props, monitor) { - if (!monitor.didDrop()) { - return - } - - const dropResult = monitor.getDropResult() - const { id: droppedId } = props - if (dropResult) { - const { targetId, targetType } = dropResult - - if (targetType === 'layout') { - // props.move(droppedId, targetId) - } else { - props.moveCard(droppedId, targetId, targetType) - } - } - } - }, - (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging() - }) - ), - DropTarget( - [ItemTypes.CARD, ItemTypes.FIELD], - { - drop(props, monitor) { - const comp = props.props.schema - - return { - name: 'card', - targetId: props.id, - targetType: isLayoutWrapper(comp) ? 'layout' : '' - } - }, - hover(props, monitor, component) { - const node = component.getNode() - if (!node) { - return null - } - const hoverBoundingRect = node.getBoundingClientRect() - // Get vertical middle - const hoverMiddleY = - (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2 - // Determine mouse position - const clientOffset = monitor.getClientOffset() - // Get pixels to the top - const hoverClientY = clientOffset.y - hoverBoundingRect.top - - const isOverHalf = hoverClientY > hoverMiddleY - monitor.getItem().isOverHalf = isOverHalf - } - }, - (connect, monitor) => ({ - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - isOverCurrent: monitor.isOver({ shallow: true }), - canDrop: monitor.canDrop() - }) - ) -)(Card) diff --git a/packages/builder/src/components/preview/fieldMiddleware.js b/packages/builder/src/components/preview/fieldMiddleware.js deleted file mode 100644 index 279105adcd0..00000000000 --- a/packages/builder/src/components/preview/fieldMiddleware.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react' -import { registerFieldMiddleware } from '../../utils/baseForm' -import Card from './card' -import { FormConsumer } from '../../constants/context' - -export default that => { - // 判断注册过则不再注册 - const hasRegisted = window.__hasRegisted__ || false - if (hasRegisted) { - return false - } - window.__hasRegisted__ = true - - const moveCard = (dragIndex, hoverIndex) => { - that.props.changeComponentOrder(dragIndex, hoverIndex) - } - - const move = (sourceId, targetId) => { - that.props.moveComponent(sourceId, targetId) - } - - registerFieldMiddleware(Field => props => - React.createElement(FormConsumer, {}, (obj = {}) => { - const { type } = obj - - // 根节点或者非预览直接返回 - if (props.path.length === 0 || type !== 'preview') { - return React.createElement(Field, props) - } - - return ( - - ) - }) - ) -} diff --git a/packages/builder/src/components/preview/index.js b/packages/builder/src/components/preview/index.js deleted file mode 100644 index bce0adaf8b0..00000000000 --- a/packages/builder/src/components/preview/index.js +++ /dev/null @@ -1,63 +0,0 @@ -import React, { Component } from 'react' -import cls from 'classnames' - -import { connect } from 'react-redux' -import { - changeComponentOrder, - moveComponent, - deleteComponent, - showComponentProps, - changeComponent -} from '../../actions' - -import { Header, wrapComp2Class } from '../../utils/util' -import registerPreviewFieldMiddleware from './fieldMiddleware' -import MainBox from './mainBox' - -import PreviewStyle from './style' - -class Preview extends Component { - componentDidMount() { - registerPreviewFieldMiddleware(this) - } - - onMouseClick = (id, comp) => { - this.props.changeComponent(id) - this.props.showComponentProps(id, comp) - } - - deleteComponent = id => { - this.props.deleteComponent && this.props.deleteComponent(id) - } - - render() { - return ( - -
    -

    预览区域

    -

    组件过多时可下拉查看更多

    -
    - -
    - ) - } -} - -const mapStateToProps = state => state - -const mapDispatchToProps = dispatch => ({ - changeComponentOrder: (sourceId, targetId, containerId) => - dispatch(changeComponentOrder(sourceId, targetId, containerId)), - moveComponent: (sourceId, targetId) => - dispatch(moveComponent(sourceId, targetId)), - deleteComponent: id => dispatch(deleteComponent(id)), - showComponentProps: (id, comp) => dispatch(showComponentProps(id, comp)), - changeComponent: componentId => dispatch(changeComponent(componentId)) -}) - -export default connect( - mapStateToProps, - mapDispatchToProps -)(wrapComp2Class(Preview)) diff --git a/packages/builder/src/components/preview/mainBox.js b/packages/builder/src/components/preview/mainBox.js deleted file mode 100644 index 10ae6940cb3..00000000000 --- a/packages/builder/src/components/preview/mainBox.js +++ /dev/null @@ -1,133 +0,0 @@ -import React, { useRef, forwardRef } from 'react' -import { DropTarget } from 'react-dnd' -import ItemTypes from '../../constants/itemType' -import { SchemaForm } from '../../utils/baseForm' -import { isEmptyObj } from '../../utils/util' -import pick from 'lodash.pick' -import { normalizeSchema } from '../../utils/lang' -import { FormProvider } from '../../constants/context' - -const RenderPreviewList = ({ props }) => { - const { preview, gbConfig = {}, schema = {}, renderEngine, onChange } = props - const { properties = {} } = schema - const { FormButtonGroup, Submit, Reset } = renderEngine - const { needFormButtonGroup } = gbConfig - - if (isEmptyObj(properties)) { - return

    请从左边字段添加组件进来吧

    - } - - let children = ' ' - try { - children = - needFormButtonGroup === true || needFormButtonGroup === 'true' ? ( - - 提交 - 重置 - - ) : ( - ' ' - ) - } catch (e) { - if (window.location.href.indexOf('av_debug=true') > -1) { - console.error(`RenderPreviewList function error: ${e.message}`) - } - } - - // @see: https://alibaba.github.io/uform/#/aAUeUD/qAI7IVFnsJ - const globalCfg = pick(gbConfig, [ - 'labelCol', - 'wrapperCol', - 'action', - 'labelAlign', - 'labelTextAlign', - 'autoAddColon', - 'inline', - 'size', - 'editable', - 'defaultValue', - 'value', - 'locale', - 'schema', - 'effects', - 'actions', - 'editable', - 'onValidateFailed', - 'onReset', - 'onSubmit', - 'onChange' - ]) - - return ( - - - {children} - - - ) -} - -const MainBox = forwardRef( - ({ props, canDrop, isOver, connectDropTarget }, ref) => { - const elementRef = useRef(null) - connectDropTarget(elementRef) - - const isActive = canDrop && isOver - let backgroundColor = '#fff' - if (isActive) { - backgroundColor = '#f1f1f1' - } - - return ( -
    - -
    -
    - ) - } -) - -export default DropTarget( - [ItemTypes.FIELD, ItemTypes.LAYOUT], - { - drop: (props, monitor, component) => { - // console.info('drop', component) - if (!component) { - return - } - const hasDroppedOnChild = monitor.didDrop() - if (hasDroppedOnChild) { - return - } - // console.info('drop child') - component.onDrop(hasDroppedOnChild) - } - }, - (connect, monitor) => ({ - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - isOverCurrent: monitor.isOver({ shallow: true }), - canDrop: monitor.canDrop() - }) -)(MainBox) diff --git a/packages/builder/src/components/preview/style.js b/packages/builder/src/components/preview/style.js deleted file mode 100644 index ba5ea73264c..00000000000 --- a/packages/builder/src/components/preview/style.js +++ /dev/null @@ -1,161 +0,0 @@ -import styled from 'styled-components' - -export default styled.div` - position: relative; - height: 100%; - .preview-main { - position: absolute; - top: 75px; - left: 20px; - right: 20px; - bottom: 20px; - padding: 20px; - background: #fff; - overflow-y: scroll; - border-radius: 4px; - } - .next-card-head { - margin-bottom: 10px; - } - .next-card-body { - padding-top: 0 !important; - } - .preview-line { - position: relative; - overflow: hidden; - margin-bottom: 0 !important; - padding: 10px !important; - border: 1px solid #e9e9e9; - outline: 1px solid #outline; - border-radius: 2px; - transition: all 0.1s ease; - user-select: none; - .preview-line-del { - cursor: pointer; - z-index: 101; - opacity: 0; - width: 30px; - height: 16px; - font-size: 12px; - color: #333; - text-align: center; - &::before { - content: ''; - display: block; - margin: 0 auto 5px; - background: url('https://gw.alicdn.com/tfs/TB1j5fABkvoK1RjSZFDXXXY3pXa-30-32.png') - no-repeat center center; - background-size: 15px 16px; - height: 16px; - } - } - &:hover { - > .comp-item-layout-tool .preview-line-del { - opacity: 1; - } - } - .preview-line-layer { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - z-index: 100; - cursor: move; - } - .next-form-item-label { - text-align: right; - } - } - .preview-line-bar { - height: 10px; - &.active { - overflow: hidden; - height: 60px; - &::after { - content: ''; - display: block; - border: 1px dashed #ddd; - border-radius: 4px; - margin: 6px 0; - height: 40px; - } - } - &:last-child { - padding-bottom: 999px; - } - } - .preview-line-enter { - border-color: #419bf9; - } - .preview-line-active { - cursor: pointer; - border-color: #5a60ff; - outline-color: #e6e7ff; - } - .preview-tips { - position: absolute; - left: 0; - top: 72px; - width: 100%; - text-align: center; - color: #999; - } - - .comp-item, - .comp-item-layout { - position: relative; - width: 100%; - .next-row { - width: 100%; - } - } - .comp-item { - z-index: 110; - &.is-over-half { - &::after { - content: ''; - display: block; - width: 100%; - height: 10px; - background: #222; - } - } - &.is-not-over-half { - &::before { - content: ''; - display: block; - width: 100%; - height: 10px; - background: #222; - } - } - } - .comp-item-layout { - margin: 10px 0; - padding: 20px 10px; - min-height: 200px; - border: 1px dashed #ccc; - border-radius: 5px; - &.active { - border-color: #3f486b; - } - } - .comp-item-layout-tool { - position: absolute; - top: 5px; - right: 5px; - z-index: 101; - > * { - float: right; - margin-left: 8px; - } - } - .comp-item-layout-empty { - margin-top: 0; - padding-top: 20px; - width: 100%; - text-align: center; - color: #999; - } -` diff --git a/packages/builder/src/components/props/colsDetail.js b/packages/builder/src/components/props/colsDetail.js deleted file mode 100644 index c969bd83f0d..00000000000 --- a/packages/builder/src/components/props/colsDetail.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -class ColsDetail extends React.Component { - static propTypes = { - value: PropTypes.arrayOf(PropTypes.any), - onChange: PropTypes.func - } - - handleChange = (idx, val) => { - const { UI } = this.props - if (!val) { - UI.Toast.error('请确保列宽是有效整数') - return false - } - const { onChange, value } = this.props - let newValue = [...value] - const diff = val - newValue[idx] - - if (diff >= newValue[newValue.length - 1]) { - UI.Toast.error('请确保4列宽度加起来等于24') - return false - } - - newValue = newValue.map((_val, i) => { - if (i === idx) { - return val - } - if (i === newValue.length - 1) { - return _val - diff - } - if (i < idx) { - return _val - } - return _val - }) - - onChange(newValue) - } - - render() { - const { value = [], UI } = this.props - return value.map((item, idx) => ( - this.handleChange(idx, val)} - /> - )) - } -} - -export default ColsDetail diff --git a/packages/builder/src/components/props/dataSourceEditor/index.js b/packages/builder/src/components/props/dataSourceEditor/index.js deleted file mode 100644 index e45afda5d99..00000000000 --- a/packages/builder/src/components/props/dataSourceEditor/index.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import { connect, registerFormFields } from '../../../utils/baseForm' -import DataSourceEditor from '../editors/fieldAttrEditors/dataSourceEditor' - -class Component extends React.Component { - constructor(props) { - super(props) - this.handleFieldAttrChange = this.handleFieldAttrChange.bind(this) - } - - handleFieldAttrChange() { - return v => { - const { onChange } = this.props - onChange(v) - } - } - - render() { - const { fieldStore = {}, value, UI } = this.props - - return ( -
    - -
    - ) - } -} - -registerFormFields({ - dataSourceEditor: connect()(Component) -}) - -export default () => {} diff --git a/packages/builder/src/components/props/defaultValueCascader/index.js b/packages/builder/src/components/props/defaultValueCascader/index.js deleted file mode 100644 index d2044b9438c..00000000000 --- a/packages/builder/src/components/props/defaultValueCascader/index.js +++ /dev/null @@ -1,36 +0,0 @@ -import React, { Component } from 'react' -import { connect, registerFormFields } from '../../../utils/baseForm' -import DefaultValueEditor from '../editors/fieldAttrEditors/defaultValueEditor/index' - -class defaultValueCascader extends Component { - constructor(props) { - super(props) - this.handleFieldAttrChange = this.handleFieldAttrChange.bind(this) - } - - handleFieldAttrChange() { - return v => { - const { onChange } = this.props - onChange(v) - } - } - - render() { - const { fieldStore = {}, value, UI } = this.props - - return ( - - ) - } -} - -registerFormFields({ - defaultValueCascader: connect()(defaultValueCascader) -}) - -export default () => {} diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEditor.js deleted file mode 100644 index b60efbf509d..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEditor.js +++ /dev/null @@ -1,310 +0,0 @@ -import React, { Component } from 'react' -import DataSourceEnum from './dataSourceEnum' -import styled from 'styled-components' - -const LABEL_COL = { fixedSpan: 4 } -const STYLE_W = { width: 220 } -const JSON_EXAMPLE = ` -[{ - "label": "foo", - "value": "bar" -}] -` -const REQ_OPT_EXAMPLE = ` - { - key1: aa, - key2: bb - } -` - -class DataSourceEditor extends Component { - constructor(props) { - super(props) - this.state = { - dataSourceType: this.getDefaultDataSourceType(props), - error: '' - } - } - - componentDidUpdate(prevProps, prevState) { - const _dataSourceType = this.getDefaultDataSourceType( - this.props, - prevState.dataSourceType - ) - if (_dataSourceType !== prevState.dataSourceType) { - this.setState({ - dataSourceType: _dataSourceType - }) - } - } - - getDefaultDataSourceType = (props = this.props, defaultType) => { - const { fieldStore, value } = props - const xProps = fieldStore['x-props'] || {} - const enums = value.enum - - const url = xProps.url || '' - if (enums) return 'local' - if (url) return 'remote' - - return defaultType || 'local' - } - - handleTypeChange = v => { - const { onChange } = this.props - if (v === 'local') { - this.setState({ dataSourceType: 'local' }, () => { - onChange({ - url: '' - }) - }) - } else { - this.setState({ dataSourceType: 'remote' }, () => { - onChange({ - enum: '' - }) - }) - } - } - - handleReqOptValueChange = v => { - const { onChange } = this.props - - if (this.timer1) clearTimeout(this.timer1) - this.timer1 = setTimeout(() => { - if (!v) { - onChange({ - requestOptions: {} - }) - } else { - onChange({ - requestOptions: { - data: this.transformData(v, false) - } - }) - } - }, 10) - } - - handleReqOptChange = ev => { - const { onChange } = this.props - const v = ev.target.value - - try { - JSON.parse(v) - } catch (e) { - this.setState({ error: e.message || '不是合法的json格式!' }) - return - } - - this.setState({ error: '' }) - - ev.preventDefault() - ev.stopPropagation() - - if (this.timer1) clearTimeout(this.timer1) - this.timer1 = setTimeout(() => { - if (!v) { - onChange({ - requestOptions: {} - }) - } else { - onChange({ - requestOptions: { - data: JSON.parse(v) - } - }) - } - }, 10) - } - - handleEnumChange = ev => { - const v = ev.target.value - if (!v) { - this.setState({ error: '' }) - return - } - - try { - JSON.parse(v) - } catch (e) { - this.setState({ error: e.message || '不是合法的json格式!' }) - return - } - - this.setState({ error: '' }) - - ev.preventDefault() - ev.stopPropagation() - - const { onChange } = this.props - if (this.timer) clearTimeout(this.timer) - this.timer = setTimeout(() => { - onChange({ - enum: JSON.parse(v) - }) - }, 10) - } - - handleEnumValueChange = value => { - if (this.timer) clearTimeout(this.timer) - this.timer = setTimeout(() => { - this.props.onChange({ - enum: value - }) - }, 10) - } - - handleUrlChange = v => { - const { onChange } = this.props - onChange({ - url: v - }) - } - - /** - * 对象跟数组互转 - * { a: 1, b: 2 } => [{value: 1, label: a}, {value: 2, label: b}] - */ - transformData = (data, toArr = true) => { - if (toArr) { - return ( - !!data && - typeof data === 'object' && - Object.keys(data).map(key => ({ - value: data[key], - label: key - })) - ) - } else { - const obj = {} - data && - Array.isArray(data) && - data.forEach(item => { - item.label !== undefined && (obj[item.label] = item.value) - }) - return obj - } - } - - render() { - const { fieldStore = {}, UI } = this.props - const { dataSourceType, error } = this.state - const xProps = fieldStore['x-props'] || {} - const { requestOptions = {} } = xProps - const { data = {} } = requestOptions - - const reqDataArr = this.transformData(data, true) - const { Form, Input, Tab, RadioGroup, TabPane, version: UIVersion } = UI - - const dataSource = [ - { - label: '手动填写', - value: 'local' - } - ] - - const defaultSource = xProps.enum || fieldStore.enum || '' - - if (Object.hasOwnProperty.call(xProps, 'url')) { - // xprops带有url的则支持远程拉取数据 - dataSource.push({ - label: '远程接口获取', - value: 'remote' - }) - } - - const tabProps = {} - tabProps[UIVersion === '1.x' ? 'shape' : 'type'] = 'text' - - const formItemProps = {} - const _key = UIVersion === '1.x' ? 'validateState' : 'validateStatus' - formItemProps[_key] = error ? 'error' : 'success' - - return ( - - - {dataSourceType === 'local' ? ( - {error}} - > - - - - - - - - - - ) : null} - {dataSourceType === 'remote' ? ( - {error}} - > - - - -
    - -
    -
    - - - -
    -
    - ) : null} -
    - ) - } -} - -export default styled(DataSourceEditor)` - .next-form-item-control { - width: 100%; - } - .next-tabs-medium .next-tabs-content { - padding: 0; - } -` diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEnum.js b/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEnum.js deleted file mode 100644 index 9869d7abaed..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/dataSourceEnum.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react' -import styled from 'styled-components' - -class Component extends React.Component { - state = {} - - // 新增一行记录 - handleAddNewItem = () => { - const newDataSource = [...this.props.dataSource] - newDataSource.push({ - value: '', - label: '' - }) - this.props.onChange && this.props.onChange(newDataSource) - } - - // 删除某一行 - handleDeleteItem = idx => { - const newDataSource = [...this.props.dataSource] - newDataSource.splice(idx, 1) - this.props.onChange && this.props.onChange(newDataSource) - } - - // 修改某行记录 - handleChangeItemRow = (e, idx, key) => { - const { value } = e.target - const newDataSource = [...this.props.dataSource] - newDataSource[idx][key] = value - this.props.onChange && this.props.onChange(newDataSource) - } - - renderItemRow = (item = {}, idx) => { - const { value = '', label = '' } = item - const { UI } = this.props - const btnProps = {} - if (UI.version === '1.x') { - btnProps.text = true - } else { - btnProps.shape = 'text' - } - return ( -
    -
      -
    • - - this.handleChangeItemRow(e, idx, 'label')} - /> -
    • -
    • - - this.handleChangeItemRow(e, idx, 'value')} - /> -
    • -
    - this.handleDeleteItem(idx)} - > - - -
    - ) - } - - renderNewBtn() { - const { UI } = this.props - return ( -
    - - - 添加 - -
    - ) - } - - render() { - const { dataSource = [], className } = this.props - - return ( -
    - {dataSource && dataSource.map(this.renderItemRow)} - {this.renderNewBtn()} -
    - ) - } -} - -export default styled(Component)` - .source-row { - position: relative; - margin-bottom: 4px; - li { - display: inline-block; - width: 50%; - > label, - .next-input { - display: inline-block; - } - > label { - color: ${props => props.theme.whiteColor}; - text-align: right; - } - .next-input { - width: 50px; - } - } - .ashbin-btn { - position: absolute; - top: 50%; - right: 5px; - transform: translate3d(0, -50%, 0); - opacity: 0.5; - } - &:hover { - .ashbin-btn { - opacity: 1; - } - } - } -` diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/arrayDefaultEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/arrayDefaultEditor.js deleted file mode 100644 index 94e577f3651..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/arrayDefaultEditor.js +++ /dev/null @@ -1,41 +0,0 @@ -import React, { Component } from 'react' -import DefaultValueGenerator from './defaultValueGenerator' - -const ds = [ - { - label: '手动输入', - value: 'specify' - }, - { - label: '从url获取', - value: 'url' - } -] -class Editor extends Component { - handleChange = v => { - const { store } = this.props - store.setAttr('default', v) - } - - render() { - const { store, UI } = this.props - const { enums } = store - - return ( - - -
    - ) - }} - /> - ) - } -} - -export default Editor diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/boolDefaultEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/boolDefaultEditor.js deleted file mode 100644 index b5a79fdf7b9..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/boolDefaultEditor.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, { Component } from 'react' -import DefaultValueGenerator from './defaultValueGenerator' - -const ds = [ - { - label: '手动输入', - value: 'specify' - }, - { - label: '从url获取', - value: 'url' - } -] -class Editor extends Component { - render() { - return ( - - }} - /> - ) - } -} - -export default Editor diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateDefaultEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateDefaultEditor.js deleted file mode 100644 index 238751ed11e..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateDefaultEditor.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, { Component } from 'react' -import DefaultValueGenerator from './defaultValueGenerator' - -const ds = [ - { - label: '当前日期', - value: 'now' - }, - { - label: '未来', - value: 'future' - }, - { - label: '过去', - value: 'past' - }, - { - label: '固定日期', - value: 'specify' - }, - { - label: '从url获取', - value: 'url' - } -] - -const DatePickerDefault = props => ( - { - props.onChange(vStr) - }} - popupAlign={'tr br'} - style={{ - verticalAlign: 'top', - marginLeft: 10, - maxWidth: 90 - }} - /> -) - -class Editor extends Component { - render() { - const { UI } = this.props - return ( - , - future: ( - - ), - past: ( - - ), - specify: - }} - /> - ) - } -} - -export default Editor diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateRangeDefaultEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateRangeDefaultEditor.js deleted file mode 100644 index 4594664795b..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateRangeDefaultEditor.js +++ /dev/null @@ -1,47 +0,0 @@ -import React, { Component } from 'react' -import DateDefaultEditor from './dateDefaultEditor' - -class Editor extends Component { - constructor(props) { - super(props) - this.handleEndDateChange = this.handleEndDateChange.bind(this) - this.handleEndDateChange = this.handleEndDateChange.bind(this) - } - - handleStartDateChange = v => { - this.props.onChange([v, this.props.value[1]]) - } - - handleEndDateChange = v => { - this.props.onChange([this.props.value[0], v]) - } - - render() { - const { store = {}, value = [], UI } = this.props - const style = { - fontSize: 12, - marginBottom: 5 - } - - return ( -
    -
    开始日期
    - -
    结束日期
    - -
    - ) - } -} - -export default Editor diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateTimeDefaultEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateTimeDefaultEditor.js deleted file mode 100644 index 990d2a30e8f..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateTimeDefaultEditor.js +++ /dev/null @@ -1,52 +0,0 @@ -import React, { Component } from 'react' -import DefaultValueGenerator from './defaultValueGenerator' - -const ds = [ - { - label: '当前时间', - value: 'now' - }, - { - label: '固定时间', - value: 'specify' - }, - { - label: '从url获取', - value: 'url' - } -] - -const DatePickerDefault = props => ( - { - if (vStr) { - props.onChange(vStr) - } else { - props.onChange(v.format('HH:mm:ss')) - } - }} - style={{ - verticalAlign: 'top', - marginLeft: 5 - }} - /> -) - -class Editor extends Component { - render() { - const { UI } = this.props - return ( - , - specify: - }} - /> - ) - } -} - -export default Editor diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateTimeRangeDefaultEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateTimeRangeDefaultEditor.js deleted file mode 100644 index de31a6bb0f8..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/dateTimeRangeDefaultEditor.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { Component } from 'react' -import DateTimeDefaultEditor from './dateTimeDefaultEditor' - -class Editor extends Component { - handleStartDateChange = (v, vStr) => { - this.props.onChange([vStr, this.props.value[1]]) - } - - handleEndDateChange = (v, vStr) => { - this.props.onChange([this.props.value[0], vStr]) - } - - render() { - return ( -
    -
    - 开始时间 -
    - -
    - 结束时间 -
    - -
    - ) - } -} - -export default Editor diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/defaultValueGenerator.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/defaultValueGenerator.js deleted file mode 100644 index 132ed49866f..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/defaultValueGenerator.js +++ /dev/null @@ -1,110 +0,0 @@ -import React, { Component } from 'react' - -class DefaultValueGenerator extends Component { - static defaultProps = { - customEditor: {} - } - - constructor(props) { - super(props) - const value = props.value || {} - this.state = { - type: value.type || '', - value: value.value || '', - flag: value.flag || props.flag - } - } - - componentDidUpdate(prevProps, prevState) { - const value = this.props.value || {} - const preValue = prevProps.value || {} - if (JSON.stringify(value) !== JSON.stringify(preValue)) { - this.setState({ - type: value.type || '', - value: value.value || '', - flag: value.flag || this.props.flag - }) - } - } - - handleValueTypeChange = v => { - const { store } = this.props - const { name } = store - - this.setState( - { - type: v, - value: v === 'url' ? name : '' - }, - () => { - this.props.onChange({ - ...this.state - }) - } - ) - } - - handleValueChange = v => { - this.setState( - { - value: v - }, - () => { - this.props.onChange({ - ...this.state - }) - } - ) - } - - getValueEditor = (type, value) => { - if (this.props.customEditor[type]) { - return React.cloneElement(this.props.customEditor[type], { - onChange: this.handleValueChange, - value - }) - } - - if (type === 'url') { - return ( - - - - ) - } else { - return null - } - } - - render() { - const { type, value, flag } = this.state - return ( -
    - - {this.getValueEditor(type, value, flag)} -
    - ) - } -} - -export default DefaultValueGenerator diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/index.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/index.js deleted file mode 100644 index c089fe8f4ef..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/index.js +++ /dev/null @@ -1,118 +0,0 @@ -import React, { Component } from 'react' -import ArrayDefaultEditor from './arrayDefaultEditor' -import BoolDefaultEditor from './boolDefaultEditor' -import DateDefaultEditor from './dateDefaultEditor' -import DateRangeDefaultEditor from './dateRangeDefaultEditor' -import DateTimeDefaultEditor from './dateTimeDefaultEditor' -import DateTimeRangeDefaultEditor from './dateTimeRangeDefaultEditor' -import MonthDefaultEditor from './monthDefaultEditor' -import StringDefaultEditor from './stringDefaultEditor' - -class DefaultValueEditor extends Component { - constructor(props) { - super(props) - this.handleOnChange = this.handleOnChange.bind(this) - } - - getEditorType = () => { - const { store } = this.props - const { type, enums } = store - const xComponent = store['x-component'] - if (type === 'date') { - return 'date' // 当前日期/前${d}天/未来${d}天/日期选择 - } else if (type === 'month') { - return 'month' // 当前月/前${d}月/后${d}月/月份选择 - } else if (type === 'daterange') { - return 'daterange' // 当前日期/前${d}天/未来${d}天/日期选择- 当前日期/前${d}天/未来${d}天/日期选择 - } else if (type === 'time') { - return 'time' // 当前时间/时间选择 - } else if (type === 'datetimerange') { - return 'datetimerange' // 当前时间/时间选择 - 当前时间/时间选择 - } else if (type === 'boolean') { - return 'boolean' // switch - } else if (type === 'string') { - if (xComponent === 'radio' || xComponent === 'checkbox' || enums) { - return 'array' // 数组选择 - } else { - return 'string' - } - } else { - return 'string' - } - } - - handleOnChange = v => { - this.props.onChange(v) - } - - createDefaultValueByType = editorType => { - const { store, value } = this.props - const { enums } = store - const xComponent = store['x-component'] - - switch (editorType) { - case 'date': - case 'time': - case 'month': - return value || {} - case 'daterange': - case 'datetimerange': - // @todo: 不知道为啥这里会传入一个object - return Array.isArray(value) && value.length ? value : [{}, {}] - case 'boolean': - return value || {} - case 'string': - if (xComponent === 'radio' || xComponent === 'checkbox' || enums) { - return value || [] // 数组选择 - } else { - return value || {} - } - default: - return value || {} - } - } - - createEditorProps = editorType => { - const { store } = this.props - - return { - store, - value: this.createDefaultValueByType(editorType), - onChange: this.handleOnChange - } - } - - getEditor = editorType => { - const props = this.createEditorProps(editorType) - const { UI } = this.props - switch (editorType) { - case 'string': - return - case 'array': - return - case 'boolean': - return - case 'month': - return - case 'date': - return - case 'daterange': - return - case 'time': - return - case 'datetimerange': - return - default: - return - } - } - - render() { - const editorType = this.getEditorType() - const editor = this.getEditor(editorType) - - return
    {editor}
    - } -} - -export default DefaultValueEditor diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/monthDefaultEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/monthDefaultEditor.js deleted file mode 100644 index 1897c11c4b1..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/monthDefaultEditor.js +++ /dev/null @@ -1,80 +0,0 @@ -import React, { Component } from 'react' -import DefaultValueGenerator from './defaultValueGenerator' - -const ds = [ - { - label: '当前月', - value: 'now' - }, - { - label: '未来', - value: 'future' - }, - { - label: '过去', - value: 'past' - }, - { - label: '固定月', - value: 'specify' - }, - { - label: '从url获取', - value: 'url' - } -] - -const MonthPickerDefault = props => { - const { UI } = props - const { MonthPicker } = UI.DatePicker - return ( - { - props.onChange(vStr) - }} - style={{ - verticalAlign: 'top', - marginLeft: 20 - }} - /> - ) -} - -class Editor extends Component { - render() { - const { UI } = this.props - return ( - , - future: ( - - ), - past: ( - - ), - specify: - }} - /> - ) - } -} - -export default Editor diff --git a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/stringDefaultEditor.js b/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/stringDefaultEditor.js deleted file mode 100644 index 1a911677a16..00000000000 --- a/packages/builder/src/components/props/editors/fieldAttrEditors/defaultValueEditor/stringDefaultEditor.js +++ /dev/null @@ -1,39 +0,0 @@ -import React, { Component } from 'react' -import DefaultValueGenerator from './defaultValueGenerator' - -const ds = [ - { - label: '手动输入', - value: 'specify' - }, - { - label: '从url获取', - value: 'url' - } -] - -class Editor extends Component { - render() { - const { UI } = this.props - - return ( - - ) - }} - /> - ) - } -} - -export default Editor diff --git a/packages/builder/src/components/props/fileSetting.js b/packages/builder/src/components/props/fileSetting.js deleted file mode 100644 index 92b517bc4e6..00000000000 --- a/packages/builder/src/components/props/fileSetting.js +++ /dev/null @@ -1,196 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { SchemaForm, Field } from '../../utils/baseForm' -import remove from 'lodash.remove' - -const showUploadListData = [ - { value: 'text', label: '文字' }, - { value: 'text-image', label: '图文' }, - { value: 'picture-card', label: '卡片' } -] - -const apiHost = - window.location.href.indexOf('.tmall.com') > -1 - ? '//sop.tmall.com' - : '//sop.daily.tmall.net' -const uploadUrl = `${apiHost}/workflow/instance/formUpload.do` - -// 文件特殊设置 -class fileSetting extends Component { - constructor(props) { - super(props) - this.state = { - xprops: props.xprops || {} - } - } - - componentDidUpdate(prevProps) { - const preXprops = prevProps.xprops || {} - const curXprops = this.props.xprops || {} - if (JSON.stringify(preXprops) !== JSON.stringify(curXprops)) { - this.setState({ - xprops: curXprops - }) - } - } - - onChangeDefaultFile(newDefaultFileList) { - const { onChange } = this.props - const newXprops = this.state.xprops - newXprops.defaultFileList = newDefaultFileList - - this.setState( - { - xprops: newXprops - }, - () => { - onChange && onChange(newXprops) - } - ) - } - - onChangeHandler = formdata => { - const { onChange } = this.props - const newXprops = Object.assign({}, this.state.xprops, formdata) - - if (newXprops.file) { - newXprops.defaultFileList = newXprops.file - delete newXprops.file - } - - this.setState( - { - xprops: newXprops - }, - () => { - onChange && onChange(newXprops) - } - ) - } - - getSchemaValue() { - const { xprops = {} } = this.props - const { - action = '', - limit = 10, - showUploadList = true, - listType = 'text', - defaultFileList = [] - } = xprops - return { - action, - limit, - showUploadList, - listType, - file: defaultFileList - } - } - - renderProps() { - return ( - - - - - - - ) - } - - renderUploadComp() { - const { xprops = {}, UI } = this.props - const { defaultFileList = [] } = xprops - return ( -
    -
    - -
    -
    -
    - { - const imgKey = - res.data && Object.keys(res.data).length - ? Object.keys(res.data)[0] - : null - - return { - code: res.succ === true ? '0' : '1', - name: imgKey, - imgURL: imgKey && res.data[imgKey], - downloadURL: imgKey && res.data[imgKey], - fileURL: imgKey && res.data[imgKey] - } - }} - fileList={defaultFileList} - onRemove={res => { - const newDefaultFileList = - this.props.xprops.defaultFileList || [] - remove( - newDefaultFileList, - item => item.fileURL === res.fileURL - ) - this.onChangeDefaultFile(newDefaultFileList) - }} - onSuccess={res => { - const newDefaultFileList = - this.props.xprops.defaultFileList || [] - newDefaultFileList.push(res) - this.onChangeDefaultFile(newDefaultFileList) - }} - onError={() => { - this.props.UI.Toast.error('文件上传失败') - }} - > - 上传文件 - -
    -
    -
    -
    -
    - ) - } - - render() { - return ( -
    - {this.renderProps()} - {this.renderUploadComp()} -
    - ) - } -} - -fileSetting.propTypes = { - // eslint-disable-next-line - xprops: PropTypes.object, - onChange: PropTypes.func -} - -fileSetting.defaultProps = { - xprops: {}, - // eslint-disable-next-line - onChange: xprops => {} -} - -export default fileSetting diff --git a/packages/builder/src/components/props/propsSetting.js b/packages/builder/src/components/props/propsSetting.js deleted file mode 100644 index 357c270d4e0..00000000000 --- a/packages/builder/src/components/props/propsSetting.js +++ /dev/null @@ -1,263 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { - SchemaForm, - registerFormFields, - connect as formConnect -} from '../../utils/baseForm' -import { connect } from 'react-redux' -import { - showComponentProps, - editComponentProps, - editComponent -} from '../../actions' -import { State } from 'react-powerplug' -import { getCompDetailById, flatObj } from '../../utils/util' - -import PropsStyle from './style' - -import FileSetting from './fileSetting' - -import './defaultValueCascader/index' -import './dataSourceEditor/index' -import ColsDetail from './colsDetail' - -import pickBy from 'lodash.pickby' - -// 属性设置 -class PropsSetting extends Component { - static propTypes = { - componentId: PropTypes.arrayOf(PropTypes.string), - editComponent: PropTypes.func, - editComponentProps: PropTypes.func, - componentProps: PropTypes.object - } - - constructor(props) { - super(props) - registerFormFields({ - colsDetail: formConnect({ - defaultProps: { - UI: this.props.UI - } - })(ColsDetail) - }) - } - - onChangeHandler = formdata => { - const { componentId = [] } = this.props - if (!componentId.length) return false - - const propsData = pickBy(formdata, x => x !== undefined) - - // 是否隐藏属性 - propsData['x-props'] = propsData['x-props'] || {} - propsData['x-props'].style = propsData['x-props'].style || {} - - if (propsData['x-props.htmltype'] === true) { - propsData['x-props'].htmlType = 'hidden' - propsData['x-props'].style.display = 'none' - } else { - propsData['x-props'].htmlType = '' - propsData['x-props'].style.display = 'block' - } - - // 配置选项 - if (propsData.dataSource !== undefined) { - propsData['x-props'] = { - ...propsData['x-props'], - ...propsData.dataSource - } - } - - // key1.key2.key3...keyx 格式转换 - const submitPropsData = flatObj(propsData) - - this.updateComponentPropsData(componentId, submitPropsData) - } - - getSchemaValue() { - const { componentId, componentProps = {}, initSchemaData = {} } = this.props - - if (!componentId.length) return {} - - const curComponentProps = componentProps[componentId.toString()] || [] - - const result = {} - curComponentProps.forEach(compProp => { - const { name, value } = compProp - result[name] = value - }) - - const curComponentAttr = getCompDetailById(componentId, initSchemaData) - - if (curComponentAttr['x-props']) { - result['x-props'] = curComponentAttr['x-props'] - Object.keys(result['x-props']).forEach(key => { - if ( - Object.hasOwnProperty.call(result, `x-props.${key}`) && - result[`x-props.${key}`] === undefined - ) { - result[`x-props.${key}`] = result['x-props'][key] - } - }) - } - - return result - } - - updateComponentPropsData = (componentId, propsData) => { - this.props.editComponent(componentId, propsData) - this.props.editComponentProps(componentId, propsData) - } - - generatePropsSchema() { - const { - initSchemaData = {}, - componentId, - componentProps = {}, - UI - } = this.props - - if (!componentId.length) { - return { - type: 'object', - properties: {} - } - } - - const curComponentProps = componentProps[componentId.toString()] || [] - const curComponentAttr = getCompDetailById(componentId, initSchemaData) - - const finalSchema = {} - curComponentProps.forEach(configItem => { - const xcomponent = configItem['x-component'] - const xProps = configItem['x-props'] || {} - let newXprops = { - ...xProps - } - - if ( - ['defaultValueCascader', 'dataSourceEditor'].indexOf(xcomponent) > -1 - ) { - newXprops = { - ...newXprops, - fieldStore: curComponentAttr, - UI - } - } - - finalSchema[configItem.name] = { - ...configItem, - 'x-props': newXprops - } - }) - - return { - type: 'object', - properties: finalSchema - } - } - - renderConfigList() { - const { componentId } = this.props - - if (!componentId.length) { - return

    请选择待编辑的表单字段

    - } - - return ( - - {({ state, setState }) => ( - { - $('onFormInit').subscribe(() => { - setFieldState('x-props.cols', state => { - if ( - !state.value || - (Array.isArray(state.value) && !state.length) - ) { - state.visible = false - } else { - state.visible = true - } - }) - }) - $('onFieldChange', 'x-props.cols-num').subscribe(fieldState => { - if (!fieldState.value) return - setFieldState('x-props.cols', state => { - const arr = new Array(fieldState.value).fill( - 24 / fieldState.value - ) - state.visible = true - state.value = arr - }) - }) - }} - value={this.getSchemaValue()} - onChange={this.onChangeHandler} - schema={this.generatePropsSchema()} - labelAlign="left" - labelTextAlign="right" - labelCol={8} - > - {' '} - - )} - - ) - } - - renderOptions() { - const { componentId, initSchemaData = {} } = this.props - - if (!componentId.length) return null - - const curComponentAttr = getCompDetailById(componentId, initSchemaData) - - switch (curComponentAttr.type) { - case 'upload': - return ( - { - this.props.editComponent(componentId, { - 'x-props': xprops - }) - }} - /> - ) - default: - return null - } - } - - render() { - return ( - - {this.renderConfigList()} - {this.renderOptions()} - - ) - } -} - -const mapStateToProps = state => state - -const mapDispatchToProps = dispatch => ({ - showComponentProps: (id, comp) => dispatch(showComponentProps(id, comp)), - editComponentProps: (...args) => dispatch(editComponentProps(...args)), - editComponent: (...args) => dispatch(editComponent(...args)) -}) - -class StyledPropsSettingComp extends React.Component { - render() { - return - } -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(StyledPropsSettingComp) diff --git a/packages/builder/src/components/props/style.js b/packages/builder/src/components/props/style.js deleted file mode 100644 index ce83188a332..00000000000 --- a/packages/builder/src/components/props/style.js +++ /dev/null @@ -1,60 +0,0 @@ -import styled from 'styled-components' - -export default styled.div` - .schema-form-container .schema-form-content > .schema-form-field { - &.option-item { - padding: 10px !important; - border: 1px solid transparent; - white-space: nowrap; - .option-item-row { - &:last-child { - margin-bottom: 0 !important; - } - } - &.hover { - border-color: #ccc; - } - &.active { - border-color: #419bf9; - background: rgba(16, 141, 233, 0.1); - } - } - .option-action { - position: relative; - z-index: 1000; - display: inline-block; - padding: 3px 8px; - vertical-align: middle; - cursor: pointer; - &.option-del { - &::after { - content: ''; - position: absolute; - right: 0; - top: 50%; - transform: translate3d(0, -50%, 0); - width: 1px; - height: 15px; - background: #ccc; - } - } - } - } - - .props-tips { - padding-top: 60px; - text-align: center; - color: #999; - } - - .schema-form-container - .schema-form-field.schema-object - .schema-object-item - > .next-form-item-label { - font-size: 14px !important; - font-weight: normal !important; - margin-top: 0 !important; - border-bottom: none !important; - padding-bottom: 0 !important; - } -` diff --git a/packages/builder/src/configs/index.js b/packages/builder/src/configs/index.js deleted file mode 100644 index d9bba17fb35..00000000000 --- a/packages/builder/src/configs/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import defaultSupportFieldList from './supportFieldList' -import supportGlobalCfgList from './supportGlobalCfgList' - -export default { - defaultSupportFieldList: defaultSupportFieldList, - supportGlobalCfgList: supportGlobalCfgList -} diff --git a/packages/builder/src/configs/supportConfigList.js b/packages/builder/src/configs/supportConfigList.js deleted file mode 100644 index 452b16dbefa..00000000000 --- a/packages/builder/src/configs/supportConfigList.js +++ /dev/null @@ -1,284 +0,0 @@ -// 可配置属性 -const FIELDLIST = { - ID: { - name: '__id__', - title: '字段名称', - type: 'string', - description: '字段名称:发起请求时带上的参数id,必填,全局保证唯一。', - required: true - }, - PLACEHOLDER: { - name: 'x-props.placeholder', - title: '占位符', - type: 'string' - }, - DESCRIPTION: { - name: 'description', - title: '提示文案', - type: 'string' - }, - TITLE: { - name: 'title', - title: '标题', - type: 'string', - placeholder: '请输入字段名称,不超过50个字符' - }, - DEFAULT: { - name: 'default', - title: '默认值', - type: 'object', - 'x-component': 'defaultValueCascader' - }, - DATASOURCE: { - name: 'dataSource', - title: '配置选项', - type: 'object', - 'x-component': 'dataSourceEditor' - }, - REQUIRED: { - name: 'required', - title: '是否必填', - type: 'boolean' - }, - READONLY: { - name: 'x-props.readOnly', - title: '是否只读', - type: 'boolean' - }, - DISABLED: { - name: 'x-props.disabled', - title: '是否禁用', - type: 'boolean' - }, - HIDDEN: { - name: 'x-props.htmltype', - title: '是否隐藏', - type: 'boolean' - } -} - -// 远程获取数据源请求选项,返回的数据的value/label根据下面设置进行转换 -const REMOTE_OPT = [ - { - name: 'x-props.labelKey', - title: 'labelKey', - type: 'string', - description: '默认为label' - }, - { - name: 'x-props.valueKey', - title: 'valueKey', - type: 'string', - description: '默认为value' - } -] - -// 默认组件配置 -const defaultProps = Object.keys(FIELDLIST).map(item => FIELDLIST[item]) - -// 默认各个组件的key配置项,key跟supportConfigList的key做映射 -export const getPropsByKey = key => { - switch (key) { - case 'multipleSelect': - return generateProps(null, [ - { - name: 'x-props.multiple', - 'x-props': { - htmlType: 'hidden' - }, - type: 'string' - }, - ...REMOTE_OPT - ]) - case 'select': - case 'cascaderSelect': - case 'treeSelect': - return generateProps(null, REMOTE_OPT) - case 'multipleInput': - return generateProps( - [ - 'ID', - 'TITLE', - 'DEFAULT', - 'DESCRIPTION', - 'PLACEHOLDER', - 'REQUIRED', - 'READONLY', - 'DISABLED', - 'HIDDEN' - ], - [ - { - name: 'x-props.multiple', - 'x-props': { - style: { - display: 'none' - } - }, - type: 'boolean' - } - ] - ) - case 'input': - case 'number': - return generateProps([ - 'ID', - 'TITLE', - 'DEFAULT', - 'DESCRIPTION', - 'PLACEHOLDER', - 'REQUIRED', - 'READONLY', - 'DISABLED', - 'HIDDEN' - ]) - case 'date': - case 'month': - case 'daterange': - case 'time': - return generateProps( - [ - 'ID', - 'TITLE', - 'DEFAULT', - 'DESCRIPTION', - 'REQUIRED', - 'READONLY', - 'DISABLED', - 'HIDDEN', - 'PLACEHOLDER' - ], - [ - { - name: 'x-props.showTime', - title: '是否展示时间', - type: 'boolean' - } - ] - ) - case 'imgUpload': - case 'fileUpload': - return generateProps( - [ - 'ID', - 'TITLE', - 'DESCRIPTION', - 'REQUIRED', - 'READONLY', - 'DISABLED', - 'HIDDEN' - ], - [] - ) - case 'wrapper_layout': - return generateProps( - ['ID'], - [ - { - name: 'x-props.labelCol', - title: '文本占比', - type: 'number', - description: '按照24份等比分,输入需要占的份数' - }, - { - name: 'x-props.wrapperCol', - title: '容器占比', - type: 'number', - description: '按照24份等比分,输入需要占的份数' - } - ] - ) - case 'wrapper_grid': - return generateProps( - ['ID'], - [ - { - name: 'x-props.gutter', - title: '组件间距', - type: 'number' - }, - { - name: 'x-props.cols-num', - title: '组件列数', - type: 'string', - enum: [2, 3, 4, 6, 8, 12, 24], - description: - '默认根据组件个数动态计算等比分列,若有精确控制列宽度请指定列数' - }, - { - name: 'x-props.cols', - title: '列宽度', - type: 'string', - 'x-component': 'colsDetail' - }, - { - name: 'x-props.title', - title: '标题', - type: 'string' - } - ] - ) - case 'wrapper_card': - return generateProps( - ['ID'], - [ - { - name: 'x-props.title', - title: '标题', - type: 'string' - }, - { - name: 'x-props.subTitle', - title: '副标题', - type: 'string' - }, - { - name: 'x-props.showHeadDivider', - title: '是否展示标题底部横线', - type: 'boolean' - } - ] - ) - default: - return generateProps([ - 'ID', - 'TITLE', - 'DEFAULT', - 'DESCRIPTION', - 'PLACEHOLDER', - 'REQUIRED', - 'READONLY', - 'DISABLED', - 'HIDDEN' - ]) - } -} - -/** - * 自由组合需要展示的配置项 - * @param {Array} includeKeys - */ -export const generateProps = (includeKeys = [], extraProps = []) => { - const result = [] - - // 若不传入则返回默认的组件配置 - if (!includeKeys || !includeKeys.length) { - // 简单不去重合并 - return [...defaultProps, ...extraProps] - } - - includeKeys.forEach(key => { - const item = FIELDLIST[key] - if (key && item) { - result.push(FIELDLIST[key.toUpperCase()]) - } - }) - return [...result, ...extraProps] -} - -export const maxShowFieldListLen = 8 - -export default { - getPropsByKey, - generateProps -} diff --git a/packages/builder/src/configs/supportFieldList.js b/packages/builder/src/configs/supportFieldList.js deleted file mode 100644 index 0915521ff86..00000000000 --- a/packages/builder/src/configs/supportFieldList.js +++ /dev/null @@ -1,246 +0,0 @@ -export default [ - { - key: 'input', - icon: 'info', - iconUrl: '//gw.alicdn.com/tfs/TB11eW6DmzqK1RjSZFpXXakSXXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单行文本框', - placeholder: '请输入' - }, - { - key: 'multipleInput', - icon: 'file-text', - iconUrl: '//gw.alicdn.com/tfs/TB1zk14DjTpK1RjSZKPXXa3UpXa-116-78.png', - width: '58', - height: '39', - type: 'string', - title: '多行文本框', - placeholder: '请输入', - 'x-props.multiple': true, - 'x-props': { - multiple: true - } - }, - { - key: 'number', - icon: 'file-text', - iconUrl: '//gw.alicdn.com/tfs/TB1f7i1DhTpK1RjSZFGXXcHqFXa-116-56.png', - width: '58', - height: '28', - type: 'number', - title: '数字选择器' - }, - { - key: 'radio', - icon: 'check-circle-o', - iconUrl: '//gw.alicdn.com/tfs/TB1zQaYDlLoK1RjSZFuXXXn0XXa-56-56.png', - width: '28', - height: '28', - type: 'radio', - title: '单选框', - 'x-component': 'radio', - enum: [ - { - value: '1', - label: '选项1' - }, - { - value: '2', - label: '选项2' - } - ], - 'x-props': { - enum: [ - { - value: '1', - label: '选项1' - }, - { - value: '2', - label: '选项2' - } - ] - } - }, - { - key: 'checkbox', - icon: 'check-square-o', - iconUrl: '//gw.alicdn.com/tfs/TB1ELO7DgHqK1RjSZFPXXcwapXa-56-56.png', - width: '28', - height: '28', - type: 'checkbox', - title: '复选框', - 'x-component': 'checkbox', - enum: [ - { - value: '1', - label: '选项1' - }, - { - value: '2', - label: '选项2' - } - ], - 'x-props': { - enum: [ - { - value: '1', - label: '选项1' - }, - { - value: '2', - label: '选项2' - } - ] - } - }, - { - key: 'imgUpload', - icon: 'picture', - iconUrl: '//gw.alicdn.com/tfs/TB1YC52DkvoK1RjSZPfXXXPKFXa-128-66.png', - width: '64', - height: '33', - type: 'upload', - title: '图片上传' - }, - { - key: 'fileUpload', - icon: 'file', - iconUrl: '//gw.alicdn.com/tfs/TB17eq5DcbpK1RjSZFyXXX_qFXa-128-62.png', - width: '64', - height: '31', - type: 'upload', - title: '文件上传' - }, - { - key: 'select', - icon: 'down-square-o', - iconUrl: '//gw.alicdn.com/tfs/TB1MA14DjTpK1RjSZKPXXa3UpXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '单选下拉框', - 'x-component': 'enhanceSelect', - 'x-props': { - url: '', - enum: [ - { - value: '1', - label: '选项1' - } - ] - } - }, - { - key: 'multipleSelect', - icon: 'down-square-o', - iconUrl: '//gw.alicdn.com/tfs/TB1Ysm3DbvpK1RjSZPiXXbmwXXa-140-56.png', - width: '70', - height: '28', - type: 'string', - title: '多选下拉框', - 'x-component': 'enhanceSelect', - 'x-props': { - multiple: true, - url: '', - enum: [ - { - value: '1', - label: '选项1' - } - ] - } - }, - { - key: 'date', - icon: 'calendar', - iconUrl: '//gw.alicdn.com/tfs/TB10ZW.DkzoK1RjSZFlXXai4VXa-116-56.png', - width: '58', - height: '28', - type: 'date', - title: '日期选择器' - }, - { - key: 'month', - icon: 'calendar', - iconUrl: '//gw.alicdn.com/tfs/TB1aey5DbvpK1RjSZFqXXcXUVXa-116-56.png', - width: '58', - height: '28', - type: 'month', - title: '月份选择器' - }, - { - key: 'daterange', - icon: 'calendar', - iconUrl: '//gw.alicdn.com/tfs/TB1k4C3Db2pK1RjSZFsXXaNlXXa-140-56.png', - width: '70', - height: '28', - type: 'daterange', - title: '日期范围选择器' - }, - { - key: 'time', - icon: 'clock-circle-o', - iconUrl: '//gw.alicdn.com/tfs/TB1D4a8DkPoK1RjSZKbXXX1IXXa-116-56.png', - width: '58', - height: '28', - type: 'time', - title: '时间选择器' - }, - { - key: 'cascaderSelect', - icon: 'clock-circle-o', - iconUrl: '//gw.alicdn.com/tfs/TB1nGG8DkvoK1RjSZFDXXXY3pXa-100-72.png', - width: '50', - height: '36', - type: 'string', - title: '级联选择器', - 'x-component': 'cascaderSelect', - 'x-props': { - url: '' - } - }, - { - key: 'multiInput', - icon: 'clock-circle-o', - iconUrl: '//gw.alicdn.com/tfs/TB1Pym.DcfpK1RjSZFOXXa6nFXa-140-56.png', - width: '70', - height: '28', - type: 'string', - title: '批量输入框', - 'x-component': 'multiInput' - }, - { - key: 'treeSelect', - icon: 'clock-circle-o', - iconUrl: '//gw.alicdn.com/tfs/TB1PWG8DkvoK1RjSZFDXXXY3pXa-116-56.png', - width: '58', - height: '28', - type: 'string', - title: '树形下拉框', - 'x-component': 'treeSelect', - 'x-props': { - url: '' - } - }, - { - key: 'tabSelect', - icon: 'clock-circle-o', - iconUrl: '//gw.alicdn.com/tfs/TB1ch9.DgHqK1RjSZFEXXcGMXXa-132-58.png', - width: '67', - height: '29', - type: 'string', - title: 'tab选择框', - 'x-component': 'tabSelect', - 'x-props': { - dataSource: [ - { - value: 1, - label: 1 - } - ] - } - } -] diff --git a/packages/builder/src/configs/supportGlobalCfgList.js b/packages/builder/src/configs/supportGlobalCfgList.js deleted file mode 100644 index 10fbdd98475..00000000000 --- a/packages/builder/src/configs/supportGlobalCfgList.js +++ /dev/null @@ -1,80 +0,0 @@ -// 全局属性配置 - -const labelAlignEnum = [ - { value: 'left', label: '左侧' }, - { value: 'top', label: '上方' } -] -const labelTextAlignEnum = [ - { value: 'left', label: '左对齐' }, - { value: 'right', label: '右对齐' } -] -const autoAddColonEnum = [ - { value: true, label: '是' }, - { value: false, label: '否' } -] -const needFormButtonGroupEnum = [...autoAddColonEnum] -const inlineEnum = [...autoAddColonEnum] -const sizeEnum = [ - { value: 'large', label: '大' }, - { value: 'medium', label: '中' }, - { value: 'small', label: '小' } -] - -// 默认全局配置值 -export const defaultGlobalCfgValue = { - labelAlign: 'left', - labelTextAlign: 'right', - autoAddColon: true, - needFormButtonGroup: false, - inline: false, - size: 'medium', - labelCol: 8, - wrapperCol: 16, - editable: true -} - -// 默认全局配置属性列表 -export default [ - { - name: 'labelAlign', - title: '标签位置', - type: 'string', - enum: labelAlignEnum, - 'x-component': 'radio' - }, - { - name: 'labelTextAlign', - title: '标签对齐方式', - type: 'string', - enum: labelTextAlignEnum, - 'x-component': 'radio' - }, - { - name: 'inline', - title: '是否单行布局', - type: 'string', - enum: inlineEnum, - 'x-component': 'radio' - }, - { - name: 'size', - title: '组件尺寸', - type: 'string', - enum: sizeEnum, - 'x-component': 'radio' - }, - { - name: 'autoAddColon', - title: '是否加冒号', - type: 'string', - enum: autoAddColonEnum, - 'x-component': 'radio' - }, - { - name: 'needFormButtonGroup', - title: '有提交按钮', - type: 'string', - enum: needFormButtonGroupEnum, - 'x-component': 'radio' - } -] diff --git a/packages/builder/src/configs/supportLayoutList.js b/packages/builder/src/configs/supportLayoutList.js deleted file mode 100644 index 8c2172744cf..00000000000 --- a/packages/builder/src/configs/supportLayoutList.js +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -export default [ - { - key: 'wrapper_layout', - icon: 'clock-circle-o', - type: 'object', - title: 'Layout布局', - __key__: 'layout', - __key__data__: { - 'x-component': 'layout', - 'x-props': { - labelCol: 8, - wrapperCol: 6 - } - } - }, - { - key: 'wrapper_grid', - icon: 'clock-circle-o', - type: 'object', - title: 'Grid布局', - __key__: 'layout', - __key__data__: { - 'x-component': 'grid', - 'x-props': { - gutter: 20 - } - } - }, - { - key: 'wrapper_card', - icon: 'clock-circle-o', - type: 'object', - title: 'FormCard卡片式布局', - __key__: 'layout', - __key__data__: { - 'x-component': 'card', - 'x-props': { - title: '卡片式布局', - showHeadDivider: true - } - } - } -] diff --git a/packages/builder/src/configs/theme.js b/packages/builder/src/configs/theme.js deleted file mode 100644 index b6276f336fa..00000000000 --- a/packages/builder/src/configs/theme.js +++ /dev/null @@ -1,57 +0,0 @@ -// 主题 -export const dark = { - headerBgColor: '#3F486B', - leftColBgColor: '#272D44', - mainColBgColor: '#3F486B', - rightColBgColor: '#313853', - whiteColor: '#fff', - backIconUrl: '//gw.alicdn.com/tfs/TB1mw6TBhjaK1RjSZKzXXXVwXXa-18-34.png', - compHoverBgColor: '#1D2236', - dividerBgColor: '#1E2336', - dividerShadow: '0 1px 0 0 #313853', - backDividerBgColor: '#3F486B', - backDividerShadow: '1px 0 0 0 #313853, 2px 0 0 0 rgba(96, 107, 149, 0.65)', - btnNormalBgColor: '#48527A', - btnPrimaryBgColor: '#5A60FF', - btnPrimaryTxtColor: '#fff' -} - -export const light = { - headerBgColor: '#fff', - leftColBgColor: '#fff', - mainColBgColor: '#f2f2f2', - rightColBgColor: '#fff', - whiteColor: '#555', - backIconUrl: '//gw.alicdn.com/tfs/TB1t0bMBhTpK1RjSZFMXXbG_VXa-18-34.png', - compHoverBgColor: '#f3f3f3', - dividerBgColor: '#f9f9f9', - dividerShadow: '0 1px 0 0 #f2f2f2', - backDividerBgColor: '#f9f9f9', - backDividerShadow: '1px 0 0 0 #999', - btnNormalBgColor: '#f7f8fa', - btnPrimaryBgColor: '#5A60FF', - btnPrimaryTxtColor: '#fff' -} - -export const THEME_ENUM = ['dark', 'light'] - -export const DEFAULT_THEME = THEME_ENUM[0] - -// 全局操作栏按钮icon -export const GLOBAL_BTN_ICON_URL = { - [THEME_ENUM[0]]: { - preview: '//gw.alicdn.com/tfs/TB1t3egCkvoK1RjSZPfXXXPKFXa-32-32.png', - submit: '//gw.alicdn.com/tfs/TB1UnehCcbpK1RjSZFyXXX_qFXa-30-30.png', - code: '//gw.alicdn.com/tfs/TB1lT5hCXzqK1RjSZFvXXcB7VXa-42-32.png' - }, - [THEME_ENUM[1]]: { - preview: '//gw.alicdn.com/tfs/TB1y1GKCiLaK1RjSZFxXXamPFXa-32-32.png', - submit: '//gw.alicdn.com/tfs/TB1UnehCcbpK1RjSZFyXXX_qFXa-30-30.png', - code: '//gw.alicdn.com/tfs/TB15bGkCXzqK1RjSZSgXXcpAVXa-42-32.png' - } -} - -export default { - dark, - light -} diff --git a/packages/builder/src/constants/context.js b/packages/builder/src/constants/context.js deleted file mode 100644 index 5de51647409..00000000000 --- a/packages/builder/src/constants/context.js +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react' -export const { - Consumer: FormConsumer, - Provider: FormProvider -} = React.createContext() diff --git a/packages/builder/src/constants/itemType.js b/packages/builder/src/constants/itemType.js deleted file mode 100644 index 72ceaa9d424..00000000000 --- a/packages/builder/src/constants/itemType.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - CARD: 'card', - FIELD: 'field', - LAYOUT: 'layout' -} diff --git a/packages/builder/src/demo/index-1-x.js b/packages/builder/src/demo/index-1-x.js deleted file mode 100644 index 30d64dbb145..00000000000 --- a/packages/builder/src/demo/index-1-x.js +++ /dev/null @@ -1,147 +0,0 @@ -import React from 'react' -import SchemaForm, { FormButtonGroup, Submit, Reset } from '@uform/next' -import Index from '../index' -import { - Button, - Collapse, - Message, - Upload, - Input, - Select, - DatePicker, - Icon, - Checkbox, - NumberPicker, - TimePicker, - Radio, - Form, - Tab -} from '@alifd/next' - -// style -import '@alifd/next/dist/next.css' - -SchemaForm.FormButtonGroup = FormButtonGroup -SchemaForm.Submit = Submit -SchemaForm.Reset = Reset - -const renderSchema = {} - -const props = { - UI: { - version: '1.x', - Button, - Accordion: Collapse, - Toast: Message, - Upload, - Input, - Select, - Icon, - DatePicker, - TimePicker, - Checkbox, - NumberPicker, - Radio, - RadioGroup: Radio.Group, - TabPane: Tab.Item, - Form, - Tab - }, - // 主题: dark/light,默认dark - // themeStyle: 'light', - // 是否展示布局组件,默认为false - showLayoutField: false, - showPreviewBtn: true, - showSourceCodeBtn: true, - // 控制返回按钮点击事件 - onBackBtnClick: () => { - alert('点击了返回') - }, - // 额外全局按钮 - globalButtonList: [ - // { - // key: 'submit', - // title: '自定义保存', - // render: (props) => { - // return {props.children} - // }, - // props: { - // // loading: true, - // }, - // }, { - // key: 'cancel', - // title: '取消', - // props: { - // onClick: () => { - // alert('点击取消'); - // } - // }, - // } - ], - // 是否展示全局配置 - showGlobalCfg: true, - // 全局配置额外项 - extraGlobalCfgList: [ - { - name: 'labelCol', - title: 'label宽度占比', - type: 'string' - }, - { - name: 'wrapperCol', - title: 'wrapper宽度占比', - type: 'string' - }, - { - name: 'editable', - title: '表单是否可编辑', - description: - '若设置为false,则可快速搭建出表单详情页,只需设置每个组件的默认值', - type: 'boolean' - } - ], - globalCfg: {}, - supportFieldList: [], - includeFieldListKeyList: [ - 'input', - 'multipleInput', - 'number', - 'radio', - 'checkbox', - 'date', - 'month', - 'daterange', - 'time' - ], - - // 渲染引擎 - renderEngine: SchemaForm, - - schema: renderSchema, - // onChange: (data) => { - // console.info('index onChange data', data); - // }, - onSubmit: data => { - alert(`保存数据:${JSON.stringify(data)}`) - console.info('index onSubmit data', data) - } -} - -class Comp extends React.Component { - constructor(props) { - super(props) - this.state = { - schema: renderSchema - } - } - - render() { - return ( -
    - -
    - ) - } -} - -export default Comp diff --git a/packages/builder/src/demo/index.js b/packages/builder/src/demo/index.js deleted file mode 100644 index 6735d2193f7..00000000000 --- a/packages/builder/src/demo/index.js +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react' -import SchemaForm from '@uform/next' -import Index from '../index' -import { - Button, - Accordion, - Feedback, - Upload, - Input, - Select, - DatePicker, - Icon, - Checkbox, - NumberPicker, - Radio, - Form, - Tab -} from '@alife/next' - -import '@alife/next/dist/next.min.css' - -const renderSchema = {} - -const props = { - UI: { - version: '0.x', - Button, - Accordion, - Toast: Feedback.toast, - Upload, - Input, - Select, - Icon, - DatePicker, - Checkbox, - NumberPicker, - Radio, - RadioGroup: Radio.Group, - TabPane: Tab.TabPane, - Form, - Tab - }, - // 主题: dark/light,默认dark - // themeStyle: 'light', - // 是否展示布局组件,默认为false - showLayoutField: false, - showPreviewBtn: true, - showSourceCodeBtn: true, - // 控制返回按钮点击事件 - onBackBtnClick: () => { - alert('点击了返回') - }, - includeFieldListKeyList: ['input', 'number', 'radio', 'date', 'month'], - // 额外全局按钮 - globalButtonList: [ - // { - // key: 'submit', - // title: '自定义保存', - // render: (props) => { - // return {props.children} - // }, - // props: { - // // loading: true, - // }, - // }, { - // key: 'cancel', - // title: '取消', - // props: { - // onClick: () => { - // alert('点击取消'); - // } - // }, - // } - ], - // 是否展示全局配置 - showGlobalCfg: true, - // 全局配置额外项 - extraGlobalCfgList: [], - globalCfg: {}, - supportFieldList: [], - // includeFieldListKeyList: ['input', 'select'], - - // 渲染引擎 - renderEngine: SchemaForm, - - schema: renderSchema, - // onChange: (data) => { - // console.info('index onChange data', data); - // }, - onSubmit: data => { - // eslint-disable-next-line no-console - console.info('index onSubmit data', data) - } -} - -class Comp extends React.Component { - constructor(props) { - super(props) - this.state = { - schema: renderSchema - } - } - - render() { - return ( -
    - -
    - ) - } -} - -export default Comp diff --git a/packages/builder/src/index.js b/packages/builder/src/index.js deleted file mode 100644 index 7cbad14a487..00000000000 --- a/packages/builder/src/index.js +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Provider } from 'react-redux' -import { createStore, applyMiddleware } from 'redux' -import { createLogger } from 'redux-logger' -import thunk from 'redux-thunk' -import rootReducer from './reducers' -import App from './App' -import ThemeList, { THEME_ENUM, DEFAULT_THEME } from './configs/theme' -import { ThemeProvider } from 'styled-components' -import { DragDropContext } from 'react-dnd' -import HTML5Backend from 'react-dnd-html5-backend' - -const logger = createLogger({ - collapsed: true -}) - -const middleware = [ - thunk, - // The address has av_debug=true to play the logger - window.location.href.indexOf('av_debug=true') > -1 && logger -].filter(Boolean) - -const initialState = { - componentId: [], - preview: false, - codemode: false, - componentProps: {}, - gbConfig: { - action: '', - labelCol: 8, - wrapperCol: 8, - labelAlign: 'left', - labelTextAlign: 'right', - autoAddColon: true, - needFormButtonGroup: false, - inline: false, - size: 'medium' - }, - initSchemaData: { - type: 'object', - properties: {} - } -} - -const store = createStore( - rootReducer, - initialState, - applyMiddleware(...middleware) -) - -class Component extends React.Component { - static propTypes = { - themeStyle: PropTypes.string - } - - static defaultProps = { - themeStyle: DEFAULT_THEME - } - - render() { - const props = { ...this.props } - let { themeStyle } = props - - // Can only pass in one of the two enumerated values of dark/light - if (THEME_ENUM.indexOf(themeStyle) === -1) { - console.error('themeStyle must be dark/light') - themeStyle = DEFAULT_THEME - } - - // Avoid the theme attribute passing in conflict with style-components - if (props.theme) { - console.warn('the theme attribute will be ignore') - delete props.theme - } - - return ( - - - - - - ) - } -} - -export default DragDropContext(HTML5Backend)(Component) diff --git a/packages/builder/src/reducers/codemode.js b/packages/builder/src/reducers/codemode.js deleted file mode 100644 index 65bdff5bb28..00000000000 --- a/packages/builder/src/reducers/codemode.js +++ /dev/null @@ -1,12 +0,0 @@ -export default (state = false, action) => { - let newState - const { data = {} } = action - const _codemode = data.codemode || false - switch (action.type) { - case 'CHANGE_CODEMODE': - newState = _codemode - return newState - default: - return state - } -} diff --git a/packages/builder/src/reducers/componentId.js b/packages/builder/src/reducers/componentId.js deleted file mode 100644 index 5e38e2550af..00000000000 --- a/packages/builder/src/reducers/componentId.js +++ /dev/null @@ -1,12 +0,0 @@ -export default (state = [], action) => { - let newState = [...state] - const { data: { componentId = [] } = {}, type } = action - - switch (type) { - case 'CHANGE_COMPONENT': - newState = Array.isArray(componentId) ? componentId : [componentId] - return newState - default: - return state - } -} diff --git a/packages/builder/src/reducers/componentProps.js b/packages/builder/src/reducers/componentProps.js deleted file mode 100644 index 5f02182220d..00000000000 --- a/packages/builder/src/reducers/componentProps.js +++ /dev/null @@ -1,50 +0,0 @@ -import { getPropsByKey } from '../configs/supportConfigList' - -export default (state = {}, action) => { - const newState = Object.assign({}, state) - const { data = {} } = action - const { id, propsData = {}, comp = {} } = data - - switch (action.type) { - case 'SHOW_COMPONENT_PROPS': - if (id && !newState[id]) { - newState[id] = getPropsByKey(comp.key).map(item => { - const { name } = item - return Object.assign( - {}, - item, - comp[name] - ? { - value: comp[name] - } - : {} - ) - }) - } - return newState - case 'DELETE_COMPONENT': - if (id && newState[id]) { - delete newState[id] - } - return newState - case 'EDIT_COMPONENT_PROPS': - if (id && newState[id]) { - newState[id] = newState[id].map(item => { - const { name } = item - const value = propsData[name] - return Object.assign( - {}, - item, - value !== undefined - ? { - value - } - : {} - ) - }) - } - return newState - default: - return state - } -} diff --git a/packages/builder/src/reducers/gbConfig.js b/packages/builder/src/reducers/gbConfig.js deleted file mode 100644 index 3ad0324dbf2..00000000000 --- a/packages/builder/src/reducers/gbConfig.js +++ /dev/null @@ -1,17 +0,0 @@ -import { defaultGlobalCfgValue } from '../configs/supportGlobalCfgList' - -export default (state = {}, action) => { - let newState = { - ...defaultGlobalCfgValue, - ...state - } - const { data = {} } = action - - switch (action.type) { - case 'CHANGE_GB_CONFIG': - newState = Object.assign({}, newState, data) - return newState - default: - return newState - } -} diff --git a/packages/builder/src/reducers/index.js b/packages/builder/src/reducers/index.js deleted file mode 100644 index 073d0d18560..00000000000 --- a/packages/builder/src/reducers/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import { combineReducers } from 'redux' - -import preview from './preview' -import codemode from './codemode' -import componentId from './componentId' -import componentProps from './componentProps' -import gbConfig from './gbConfig' -import initSchemaData from './initSchemaData' - -export default combineReducers({ - componentId, - preview, - codemode, - componentProps, - gbConfig, - initSchemaData -}) diff --git a/packages/builder/src/reducers/initSchemaData.js b/packages/builder/src/reducers/initSchemaData.js deleted file mode 100644 index ff8bb1d0ee5..00000000000 --- a/packages/builder/src/reducers/initSchemaData.js +++ /dev/null @@ -1,193 +0,0 @@ -import { getOrderProperties, initOrderProperties } from '../utils/util' -import merge from 'lodash.merge' - -export default (state = {}, action) => { - let newState = { - ...state - } - const { data = {} } = action - const { - component, - id, - targetId, - propsData = {}, - existId = null, - containerId = [] - } = data - - const loop = (obj, idArr = []) => { - const _idArr = [...idArr] - const _id = _idArr.shift() - if (!_idArr.length) return obj.properties[_id] - return loop(obj.properties[_id], _idArr) - } - - const getProperties = (obj, idArr = []) => { - const _idArr = [...idArr] - const _id = _idArr.shift() - if (!_idArr.length) return obj.properties - return getProperties(obj.properties[_id], _idArr) - } - - const setProperties = (obj, idArr = [], prop) => { - const _idArr = [...idArr] - if (!_idArr.length) { - obj.properties = prop - } else { - const _id = _idArr.shift() - setProperties(obj.properties[_id], _idArr, prop) - } - } - - const deleteItem = (obj, idArr = []) => { - const _idArr = [...idArr] - const _id = _idArr.shift() - if (!_idArr.length) { - delete obj.properties[_id] - } else { - deleteItem(obj.properties[_id], _idArr) - } - } - - switch (action.type) { - case 'INIT_SCHEMA': - // 自动生成z-index顺序 - const newSchema = initOrderProperties(data) - newState = { - ...newState, - ...newSchema - } - return newState - case 'MOVE_COMOPNENT': - const sourceItem = loop(newState, [...id]) - const targetItem = loop(newState, [...targetId]) - - deleteItem(newState, [...id]) - - targetItem.properties[sourceItem.id] = sourceItem - - return newState - case 'CHANGE_COMPONENT_ORDER': - const _propertiesList = getOrderProperties(newState, [...containerId]) - const _sourceItem = loop(newState, [...id]) - const _targetItem = loop(newState, [...targetId]) - const targetIdx = _targetItem['x-index'] - const sourceIdx = _sourceItem['x-index'] - - if (id.length !== targetId.length) { - alert('目前只支持同级别组件的顺序替换') - return newState - } - - _propertiesList[targetIdx] = { - ..._sourceItem, - 'x-index': targetIdx - } - _propertiesList[sourceIdx] = { - ..._targetItem, - 'x-index': sourceIdx - } - - const _properties11 = {} - _propertiesList.forEach(item => { - _properties11[item.id] = { - ...item - } - }) - - setProperties(newState, containerId, _properties11) - - return newState - case 'ADD_COMPONENT': - const propertiesList1 = getOrderProperties(newState, [...containerId]) - - if (existId) { - // 在特定的existId之前插入新的组件 - const propLen = propertiesList1.length - const item = propertiesList1.find(_item => _item.id === existId) - const idx = item['x-index'] - for (let i = propLen; i > idx; i--) { - propertiesList1[i] = { - ...propertiesList1[i - 1], - 'x-index': i - } - } - propertiesList1[idx] = { - ...component, - id, - 'x-index': idx - } - } else { - // 在最后插入新的组件 - propertiesList1[propertiesList1.length] = { - ...component, - id, - 'x-index': propertiesList1.length - } - } - - const _properties1 = {} - propertiesList1.forEach(item => { - _properties1[item.id] = { - ...item - } - }) - - setProperties(newState, containerId, _properties1) - - if (!newState.type) { - newState.type = 'object' - } - - return newState - case 'EDIT_COMPONENT': - const _data_ = getProperties(newState, id) - const lastId = [...id].pop() - - Object.keys(_data_).forEach(compId => { - if (compId) { - _data_[compId] = merge( - {}, - _data_[compId], - id === null || compId === lastId - ? { - active: true, - ...propsData - } - : { - active: false - } - ) - - // hack - if ( - propsData['x-props'] && - propsData['x-props'].enum && - Array.isArray(propsData['x-props'].enum) && - (id === null || compId === lastId) - ) { - _data_[compId]['x-props'] = _data_[compId]['x-props'] || {} - _data_[compId]['x-props'].enum = propsData['x-props'].enum - _data_[compId].enum = propsData['x-props'].enum - } - if ( - propsData['x-props'] && - propsData['x-props'].requestOptions && - propsData['x-props'].requestOptions.data && - (id === null || compId === lastId) - ) { - _data_[compId]['x-props'].requestOptions = - _data_[compId]['x-props'].requestOptions || {} - _data_[compId]['x-props'].requestOptions.data = - propsData['x-props'].requestOptions.data - } - } - }) - return newState - case 'DELETE_COMPONENT': - deleteItem(newState, [...id]) - return newState - default: - return state - } -} diff --git a/packages/builder/src/reducers/preview.js b/packages/builder/src/reducers/preview.js deleted file mode 100644 index 69c236b4a6c..00000000000 --- a/packages/builder/src/reducers/preview.js +++ /dev/null @@ -1,12 +0,0 @@ -export default (state = false, action) => { - let newState - const { data = {} } = action - const _preview = data.preview || false - switch (action.type) { - case 'CHANGE_PREVIEW': - newState = _preview - return newState - default: - return state - } -} diff --git a/packages/builder/src/style.js b/packages/builder/src/style.js deleted file mode 100644 index fede0d2cc1c..00000000000 --- a/packages/builder/src/style.js +++ /dev/null @@ -1,174 +0,0 @@ -import styled from 'styled-components' - -export default styled.div` - position: relative; - min-width: 600px; - overflow: hidden; - .next-form-item { - margin-bottom: 0; - } - .next-checkbox-label { - color: ${props => props.theme.whiteColor}; - } - .preview-main .next-checkbox-label { - color: #333; - } - .schemaform-header { - position: relative; - height: 64px; - background: ${props => props.theme.headerBgColor}; - overflow: hidden; - &::after { - content: ""; - clear: both; - display: table; - } - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.15), 0 0 16px 0 rgba(0, 0, 0, 0.15); - z-index: 2; - - } - .schemaform-back { - position: absolute; - left: 0; - top: 0; - width: 64px; - height: 100%; - text-indent: -999em; - &::before { - content: ""; - position: absolute; - left: 27px; - top: 24px; - width: 9px; - height: 17px; - background: url('${props => - props.theme.backIconUrl}') no-repeat center center; - background-size: 9px 17px; - } - &::after { - content: ""; - position: absolute; - top: 20px; - right: 0; - height: 24px; - width: 1px; - background: ${props => props.theme.backDividerBgColor}; - box-shadow: ${props => props.theme.backDividerShadow}; - } - } - h1 { - position: absolute; - left: 88px; - top: 0; - margin: 0; - font-size: 24px; - font-weight: normal; - line-height: 64px; - color: ${props => props.theme.whiteColor}; - } - .schemaform-header-btns { - float: right; - margin: 14px 24px 0 0; - button { - margin-left: 24px; - height: 36px; - line-height: 36px; - background: ${props => props.theme.btnNormalBgColor}; - color: ${props => props.theme.whiteColor}; - border: none; - box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.25); - &.next-btn-primary { - background: ${props => props.theme.btnPrimaryBgColor}; - color: ${props => props.theme.btnPrimaryTxtColor}; - } - } - } - .schamaform-content { - position: relative; - overflow: hidden; - padding: 0 340px 0 240px; - - &::after { - content: ""; - clear: both; - display: table; - } - .content-col-left { - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 240px; - overflow-y: scroll; - background: ${props => props.theme.leftColBgColor}; - } - .content-col-right { - position: absolute; - top: 0; - right: 0; - width: 340px; - bottom: 0; - background: ${props => props.theme.rightColBgColor}; - overflow-y: scroll; - } - .content-col-main { - position: relative; - height: 100%; - background: ${props => props.theme.mainColBgColor}; - overflow-y: scroll; - } - } - // 复写文件上传组件宽度 - .next-upload-list-text .next-upload-list-item { - max-width: 200px; - } - .schema-form-container .next-form-top .next-form-item-label { - margin-bottom: 0 !important; - } - .next-accordion, .next-collapse { - border: none; - } - .next-accordion-section-title, .next-collapse-panel-title { - background: none; - user-select: none; - color: ${props => props.theme.whiteColor}; - border: none; - &:hover { - background: none; - } - } - .next-collapse-panel:not(:first-child) { - border-top: none; - } - .next-accordion-section, .next-collapse-panel { - position: relative; - &::after { - content: ""; - position: absolute; - left: 0; - bottom: 0; - width: 100%; - height: 1px; - background: ${props => props.theme.dividerBgColor}; - box-shadow: ${props => props.theme.dividerShadow}; - } - &:last-child { - &::after { - display: none; - } - } - } - .next-accordion-section-content, .next-collapse-panel-content { - background: none; - .next-form .next-form-item-label, .next-radio-group .next-radio-label { - color: ${props => props.theme.whiteColor}; - font-size: 12px; - } - } - .next-collapse .next-collapse-panel-icon { - color: ${props => props.theme.whiteColor}; - } - .next-accordion .next-accordion-icon:before { - color: ${props => props.theme.whiteColor}; - } -` diff --git a/packages/builder/src/utils/arg.js b/packages/builder/src/utils/arg.js deleted file mode 100644 index 3cf6a5c922d..00000000000 --- a/packages/builder/src/utils/arg.js +++ /dev/null @@ -1,313 +0,0 @@ -/* - arg.js - v1.4 - JavaScript URL argument processing once and for all. - by Mat Ryer and Ryan Quinn - Copyright (c) 2013 Stretchr, Inc. - Please consider promoting this project if you find it useful. - Permission is hereby granted, free of charge, to any person obtaining a copy of this - software and associated documentation files (the "Software"), to deal in the Software - without restriction, including without limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons - to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies - or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE - FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -/* eslint-disable */ -export default (function () { - - /** @namespace - */ - var Arg = function () { - return Arg.get.apply(global, arguments); - }; - Arg.version = "1.4.0"; - - /** - * Parses the arg string into an Arg.Arg object. - */ - Arg.parse = function (s) { - if (!s) return {}; - if (s.indexOf("=") === -1 && s.indexOf("&") === -1) return {}; - s = Arg._cleanParamStr(s); - var obj = {}; - var pairs = s.split("&"); - for (var pi in pairs) { - if (pairs.hasOwnProperty(pi)) { - var kvsegs = pairs[pi].split("="); - var key = decodeURIComponent(kvsegs[0]), val = Arg.__decode(kvsegs[1]); - Arg._access(obj, key, val); - } - } - return obj; - }; - - /** - * Decodes a URL component (including resolving + to spaces) - */ - Arg.__decode = function (s) { - while (s && s.indexOf("+") > -1) { - s = s.replace("+", " "); - } - s = decodeURIComponent(s); - return s; - }; - - /** - * Helper method to get/set deep nested values in an object based on a string selector - * - * @param {Object} obj Based object to either get/set selector on - * @param {String} selector Object selector ie foo[0][1].bar[0].baz.foobar - * @param {Mixed} value (optional) Value to set leaf located at `selector` to. - * If value is undefined, operates in 'get' mode to return value at obj->selector - * @return {Mixed} - */ - Arg._access = function (obj, selector, value) { - var shouldSet = typeof value !== "undefined"; - var selectorBreak = -1; - var coerce_types = { - 'true': true, - 'false': false, - 'null': null - }; - - // selector could be a number if we're at a numerical index leaf in which case selector.search is not valid - if (typeof selector == 'string' || Object.prototype.toString.call(selector) == '[object String]') { - selectorBreak = selector.search(/[\.\[]/); - } - - // No dot or array notation so we're at a leaf, set value - if (selectorBreak === -1) { - if (Arg.coerceMode) { - value = value && !isNaN(value) ? +value // number - : value === 'undefined' ? undefined // undefined - : coerce_types[value] !== undefined ? coerce_types[value] // true, false, null - : value; // string - } - return shouldSet ? (obj[selector] = value) : obj[selector]; - } - - // Example: - // selector = 'foo[0].bar.baz[2]' - // currentRoot = 'foo' - // nextSelector = '0].bar.baz[2]' -> will be converted to '0.bar.baz[2]' in below switch statement - var currentRoot = selector.substr(0, selectorBreak); - var nextSelector = selector.substr(selectorBreak + 1); - - switch (selector.charAt(selectorBreak)) { - case '[': - // Intialize node as an array if we haven't visted it before - obj[currentRoot] = obj[currentRoot] || []; - nextSelector = nextSelector.replace(']', ''); - - if (nextSelector.search(/[\.\[]/) === -1 && nextSelector.search(/^[0-9]+$/) > -1) { - nextSelector = parseInt(nextSelector, 10); - } - - return Arg._access(obj[currentRoot], nextSelector, value); - case '.': - // Intialize node as an object if we haven't visted it before - obj[currentRoot] = obj[currentRoot] || {}; - return Arg._access(obj[currentRoot], nextSelector, value); - } - - return obj; - }; - - /** - * Turns the specified object into a URL parameter string. - */ - Arg.stringify = function (obj, keyPrefix) { - - switch (typeof (obj)) { - case "object": - var segs = []; - var thisKey; - for (var key in obj) { - - if (!obj.hasOwnProperty(key)) continue; - var val = obj[key]; - - if (typeof (key) === "undefined" || key.length === 0 || typeof (val) === "undefined" || val === null || val.length === 0) continue; - - thisKey = keyPrefix ? keyPrefix + "." + key : key; - - if (typeof obj.length !== "undefined") { - thisKey = keyPrefix ? keyPrefix + "[" + key + "]" : key; - } - - if (typeof val === "object") { - segs.push(Arg.stringify(val, thisKey)); - } else { - segs.push(encodeURIComponent(thisKey) + "=" + encodeURIComponent(val)); - } - - } - return segs.join("&"); - } - - return encodeURIComponent(obj); - - }; - - /** - * Generates a URL with the given parameters. - * (object) = A URL to the current page with the specified parameters. - * (path, object) = A URL to the specified path, with the object of parameters. - * (path, object, object) = A URL to the specified path with the first object as query parameters, - * and the second object as hash parameters. - */ - Arg.url = function () { - - var sep = (Arg.urlUseHash ? Arg.hashQuerySeperator : Arg.querySeperator); - var segs = [window.location.pathname, sep]; - var args = {}; - - switch (arguments.length) { - case 1: // Arg.url(params) - segs.push(Arg.stringify(arguments[0])); - break; - case 2: // Arg.url(path, params) - segs[0] = Arg._cleanPath(arguments[0]); - args = Arg.parse(arguments[0]); - args = Arg.merge(args, arguments[1]); - segs.push(Arg.stringify(args)); - break; - case 3: // Arg.url(path, query, hash) - segs[0] = Arg._cleanPath(arguments[0]); - segs[1] = Arg.querySeperator; - segs.push(Arg.stringify(arguments[1])); - (typeof (arguments[2]) === "string") ? segs.push(Arg.hashSeperator) : segs.push(Arg.hashQuerySeperator); - segs.push(Arg.stringify(arguments[2])); - } - - var s = segs.join(""); - - // trim off sep if it's the last thing - if (s.indexOf(sep) == s.length - sep.length) { - s = s.substr(0, s.length - sep.length); - } - - return s; - - }; - - /** urlUseHash tells the Arg.url method to always put the parameters in the hash. */ - Arg.urlUseHash = false; - - /** The string that seperates the path and query parameters. */ - Arg.querySeperator = "?"; - - /** The string that seperates the path or query, and the hash property. */ - Arg.hashSeperator = "#"; - - /** The string that seperates the the path or query, and the hash query parameters. */ - Arg.hashQuerySeperator = "#?"; - - /** When parsing values if they should be coerced into primitive types, ie Number, Boolean, Undefined */ - Arg.coerceMode = true; - - /** - * Gets all parameters from the current URL. - */ - Arg.all = function () { - var merged = Arg.parse(Arg.querystring() + "&" + Arg.hashstring()); - return Arg._all ? Arg._all : Arg._all = merged; - }; - - /** - * Gets a parameter from the URL. - */ - Arg.get = function (selector, def) { - var val = Arg._access(Arg.all(), selector); - return typeof (val) === "undefined" ? def : val; - }; - - /** - * Gets the query string parameters from the current URL. - */ - Arg.query = function () { - return Arg._query ? Arg._query : Arg._query = Arg.parse(Arg.querystring()); - }; - - /** - * Gets the hash string parameters from the current URL. - */ - Arg.hash = function () { - return Arg._hash ? Arg._hash : Arg._hash = Arg.parse(Arg.hashstring()); - }; - - /** - * Gets the query string from the URL (the part after the ?). - */ - Arg.querystring = function () { - return Arg._cleanParamStr(window.location.search); - }; - - /** - * Gets the hash param string from the URL (the part after the #). - */ - Arg.hashstring = function () { - var rawHref = window.location.href; - var hashIndex = rawHref.indexOf("#"); - var hash = hashIndex >= 0 ? rawHref.substr(hashIndex) : ""; - return Arg._cleanParamStr(hash); - }; - - /* - * Cleans the URL parameter string stripping # and ? from the beginning. - */ - Arg._cleanParamStr = function (s) { - - if (s.indexOf(Arg.querySeperator) > -1) - s = s.split(Arg.querySeperator)[1]; - - if (s.indexOf(Arg.hashSeperator) > -1) - s = s.split(Arg.hashSeperator)[1]; - - if (s.indexOf("=") === -1 && s.indexOf("&") === -1) - return ""; - - while (s.indexOf(Arg.hashSeperator) === 0 || s.indexOf(Arg.querySeperator) === 0) - s = s.substr(1); - - return s; - }; - - Arg._cleanPath = function (p) { - - if (p.indexOf(Arg.querySeperator) > -1) - p = p.substr(0, p.indexOf(Arg.querySeperator)); - - if (p.indexOf(Arg.hashSeperator) > -1) - p = p.substr(0, p.indexOf(Arg.hashSeperator)); - - return p; - }; - - /** - * Merges all the arguments into a new object. - */ - Arg.merge = function () { - var all = {}; - for (var ai in arguments) { - if (arguments.hasOwnProperty(ai)) { - for (var k in arguments[ai]) { - if (arguments[ai].hasOwnProperty(k)) { - all[k] = arguments[ai][k]; - } - } - } - } - return all; - }; - - return Arg; - -})(); \ No newline at end of file diff --git a/packages/builder/src/utils/baseForm.js b/packages/builder/src/utils/baseForm.js deleted file mode 100644 index c0708c9b0b5..00000000000 --- a/packages/builder/src/utils/baseForm.js +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 统一从这里引用uform,方便后续底层升级做适配 - */ -export * from '@uform/react' diff --git a/packages/builder/src/utils/comp.js b/packages/builder/src/utils/comp.js deleted file mode 100644 index b83a634b484..00000000000 --- a/packages/builder/src/utils/comp.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react' -import styled from 'styled-components' - -/** - * 分割线 - */ -export const Divider = styled.div` - height: 1px; - background: #1e2336; - box-shadow: 0 1px 0 0 #313853; -` - -/** - * 头部title - */ -export const Header = styled(props => ( -
    {props.children}
    -))` - padding-left: 16px; - height: 74px; - h2 { - margin: 0; - padding: 0; - font-size: 14px; - height: 40px; - line-height: 45px; - color: ${props => props.theme.whiteColor}; - } - p { - margin: 0; - padding: 0; - font-size: 12px; - color: #9096ad; - } -` - -export const CustomIcon = styled(props => ( - -))` - display: inline-block; - margin-right: 8px; - vertical-align: -2px; - width: ${props => props.width || '15'}px; - height: ${props => props.height || '15'}px; - background-repeat: no-repeat; - background-position: center center; - background-size: ${props => props.width || '15'}px - ${props => props.height || '15'}px; -` - -export default { - Divider, - Header, - CustomIcon -} diff --git a/packages/builder/src/utils/lang.js b/packages/builder/src/utils/lang.js deleted file mode 100644 index ab2361af761..00000000000 --- a/packages/builder/src/utils/lang.js +++ /dev/null @@ -1,143 +0,0 @@ -import { parseDesturctPath } from '@uform/utils' -import moment from 'moment' -import Arg from './arg' - -export const isType = type => obj => - obj != null && Object.prototype.toString.call(obj) === `[object ${type}]` - -export const isFn = isType('Function') - -export const isArr = Array.isArray || isType('Array') - -export const isObj = isType('Object') - -export const isStr = isType('String') - -export const isNum = isType('Number') - -export const isIter = obj => isArr(obj) || isObj(obj) - -const replaceSingleDefault = v => { - if (!isFlagValue(v)) return v - - const { type, flag, value } = v - - const now = moment(Date.now()) - const params = Arg.all() - - if (flag === 'weekRange') { - if (type === 'pastStart') { - return now.subtract(value, 'weeks').format('YYYY-MM-DD') - } else if (type === 'future') { - return now.add(value, 'weeks').format('YYYY-MM-DD') - } else if (type === 'specify') { - return value - } - } else if (flag === 'date') { - if (type === 'past') { - return now.subtract(value, 'days').format('YYYY-MM-DD') - } else if (type === 'future') { - return now.add(value, 'days').format('YYYY-MM-DD') - } else if (type === 'now') { - return now.format('YYYY-MM-DD') - } else if (type === 'specify') { - return value - } else if (type === 'url') { - return params[value] - } - } else if (flag === 'time') { - if (type === 'specify') { - return value - } else if (type === 'now') { - return now.format('HH:MM:SS') - } else if (type === 'url') { - return params[value] - } - } else if (flag === 'month') { - if (type === 'past') { - return now.subtract(value, 'months').format('YYYY-MM') - } else if (type === 'future') { - return now.add(value, 'months').format('YYYY-MM') - } else if (type === 'now') { - return now.format('YYYY-MM') - } else if (type === 'specify') { - return value - } else if (type === 'url') { - return params[value] - } - } else if (type === 'specify') { - return value - } else if (type === 'url') { - return params[value] - } -} - -const replaceDefault = declaredValue => { - if (isArr(declaredValue)) { - const v = [] - for (let i = 0; i < declaredValue.length; i++) { - const _v = replaceSingleDefault(declaredValue[i]) - v.push(_v) - } - return v - } else { - return replaceSingleDefault(declaredValue) - } -} - -const isFlagValue = o => { - if (isArr(o)) { - return o.some(i => isObj(i) && typeof i.flag !== 'undefined') - } else { - return isObj(o) && typeof o.flag !== 'undefined' - } -} - -const getDefault = (v, path) => { - const dPath = parseDesturctPath(path).destruct || path - if (isArr(v) && isArr(dPath)) { - return v.map(i => { - if (isFlagValue(i)) { - i = replaceDefault(i, path) - } - return i - }) - } else if (isFlagValue(v)) { - return replaceDefault(v, path) - } else { - return v - } -} - -const normalizeDefault = (properties, _buf = {}) => - Object.keys(properties).reduce((buf, k) => { - if ( - properties[k].properties && - Object.keys(properties[k].properties).length - ) { - buf[k] = { - type: 'object', - properties: {}, - ...properties[k] - } - normalizeDefault(properties[k].properties, buf[k].properties) - } else { - buf[k] = { - ...properties[k], - default: getDefault(properties[k].default, k) - } - } - return buf - }, _buf) - -export const normalizeSchema = schema => { - if (!schema) return null - const { properties = {}, type = 'object' } = schema - const _properties = normalizeDefault(properties) - - return { - type, - ...schema, - properties: _properties - } -} diff --git a/packages/builder/src/utils/util.js b/packages/builder/src/utils/util.js deleted file mode 100644 index a0f90834b7c..00000000000 --- a/packages/builder/src/utils/util.js +++ /dev/null @@ -1,267 +0,0 @@ -import React from 'react' -import merge from 'lodash.merge' - -export * from './comp' - -/** - * 判断对象是否为空 - * @param {Object} obj 对象 - */ -export const isEmptyObj = obj => { - if (!obj) return true - for (const i in obj) { - if (Object.prototype.hasOwnProperty.call(obj, i)) { - return false - } - } - return true -} - -/** - * 将enums数组格式化成[{value: xxx, label: xxx}]形式 - * @param {Array} enums 需要格式化的数组 - */ -export const wrapEnums = enums => - enums.map(item => - typeof item === 'object' - ? item - : { - value: item, - label: item - } - ) - -/** - * 初始化组件的属性默认值 - * @param {Array} propsList 属性数组 - * @param {Object} comp 组件数据 - */ - -/** - * 根据组件id获取组件信息 - * @param {Array} componentIdList 组件的id list - * @param {Array} schema 组件schema - */ -export const getCompDetailById = (componentIdList = [], schema = {}) => { - const _componentIdList = [...componentIdList] - const _componentId = _componentIdList.shift() - const { properties = {} } = schema - - if (!_componentIdList.length) { - return properties[_componentId] - ? { - id: _componentId, - ...properties[_componentId] - } - : {} - } - - return getCompDetailById(_componentIdList, properties[_componentId]) -} - -/** - * 补全回传的schema格式 - * @param {Object} schema - * @param {Boolean} keepAll 保留所有字段 - */ -export const wrapSubmitSchema = (schema, keepAll = false) => { - if (!schema || typeof schema !== 'object') { - return { - type: 'object', - properties: {} - } - } - - // 深拷贝一份 - const result = JSON.parse(JSON.stringify(schema)) - - if (!schema.type) { - result.type = 'object' - } - if (!schema.properties) { - result.properties = {} - } - - // 以__id__为主键转换一次数据 - const newProperties = {} - const loop = (_newProperties, _properties) => { - Object.keys(_properties).forEach(key => { - const item = JSON.parse(JSON.stringify(_properties[key])) - const newKey = item.__id__ || key - if (!keepAll) { - // 删除可视化配置产生的冗余字段 - Object.keys(item).forEach(itemKey => { - if ( - [ - '__id__', - 'width', - 'height', - 'icon', - 'iconWidth', - 'iconHeight', - 'iconUrl', - 'id', - 'active', - 'placeholder' - ].indexOf(itemKey) > -1 || - /^(x-props.)/gi.test(itemKey) - ) { - delete item[itemKey] - } - }) - } - - _newProperties[newKey] = JSON.parse(JSON.stringify(item)) - - if (item.properties) { - _newProperties[newKey].properties = {} - loop(_newProperties[newKey].properties, item.properties) - } - }) - } - - loop(newProperties, result.properties) - - result.properties = newProperties - - return result -} - -/** - * 根据schema获取有顺序的properties - * @param {Object} schema - * @param {String} containerId 相对容器id - */ -export const getOrderProperties = (schema = {}, containerId = []) => { - if (containerId.length) { - const id = containerId.shift() - return getOrderProperties(schema.properties[id], containerId) - } - - const { properties = {} } = schema - if (isEmptyObj(properties)) return [] - - let newProperties = [] - - Object.keys(properties).forEach(key => { - const item = properties[key] - const index = item['x-index'] - if (typeof index !== 'number') { - newProperties.push({ - ...item, - id: key, - 'x-index': newProperties.length - }) - } - }) - Object.keys(properties).forEach(key => { - const item = properties[key] - const index = item['x-index'] - if (typeof index === 'number') { - if (!newProperties[index]) { - const _key = - index > newProperties.length + 1 ? newProperties.length : index - newProperties[_key] = { - ...item, - id: key - } - } else { - const _tempProperties = newProperties.slice(0, index) - for (let i = index; i < newProperties.length; i++) { - _tempProperties[i + 1] = { - ...newProperties[i], - 'x-index': i + 1 - } - } - _tempProperties[index] = { - ...item, - id: key - } - newProperties = _tempProperties - } - } - }) - - return newProperties -} - -export const initOrderProperties = (schema = {}) => { - const newProperties = getOrderProperties(schema) - const properties = {} - newProperties.forEach(item => { - const newItem = { ...item } - if (newItem.active) { - delete newItem.active - } - properties[item.id] = newItem - }) - const newShema = { - ...schema, - properties - } - - return newShema -} - -export const flatObj = (obj = {}) => { - // 深拷贝一份 - const result = JSON.parse(JSON.stringify(obj)) - const setValueByLoopObj = (_obj, arr, value) => { - const _key = arr.shift() - if (!arr.length) { - if (value && typeof value === 'object') { - if (Array.isArray(value)) { - _obj[_key] = value - } else { - const tempValue = _obj[_key] || {} - _obj[_key] = merge({}, value, tempValue) - } - } else { - _obj[_key] = value - } - } else { - _obj[_key] = _obj[_key] || {} - setValueByLoopObj(_obj[_key], arr, value) - } - } - Object.keys(obj).forEach(originKey => { - const key = originKey.split('.') - setValueByLoopObj(result, key, obj[originKey]) - }) - return result -} - -// 校验schema id是否有重复的 -export const checkRepeatId = (schema = {}) => { - const result = {} - const loop = _schema => { - const temp = {} - const { properties = {} } = _schema - Object.keys(properties).forEach(propKey => { - const item = properties[propKey] - const key = item.__id__ ? item.__id__ : propKey - if (!temp[key]) { - temp[key] = item - } else { - result[key] = item - } - if (item.properties) { - loop(item) - } - }) - } - loop(schema) - return !!Object.keys(result).length -} - -export const wrapComp2Class = Comp => - class extends React.Component { - render() { - return - } - } - -export const isLayoutWrapper = comp => - comp['x-props'] && - comp['x-props']._extra && - comp['x-props']._extra.__key__ === 'layout' diff --git a/packages/builder/tsconfig.json b/packages/builder/tsconfig.json deleted file mode 100644 index 35d009e80e5..00000000000 --- a/packages/builder/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib", - "declaration": false, - "allowJs": true, - "skipLibCheck": true - }, - "include": ["./src/**/*.js"], - "exclude": ["./src/__tests__/*"] -} diff --git a/packages/core/README.md b/packages/core/README.md index 624c47a4d3a..2f71df748bb 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,2 +1,30 @@ # @uform/core -> UForm 内核包 \ No newline at end of file +> UForm 内核包 + +## quick start + +```jsx +import { createForm, LifeCycleTypes, FormLifeCycle, FormPath } from './src' + +const form = createForm() +// form.registerField({ path: 'a', rules: ['number'] }) // string +// form.registerField({ path: 'b', rules: [() => ({ type: 'warning', message: 'warning msg' })] }) // CustomValidator +// form.registerField({ path: 'c', rules: [() => ({ type: 'error', message: 'warning msg' })] }) // CustomValidator +// form.registerField({ path: 'd', rules: [() => 'straight error msg'] }) // CustomValidator +// form.registerField({ path: 'e', rules: [{ required: true, message: 'desc msg' }] }) // ValidateDescription + +form.registerField({ path: 'a', rules: [(value) => { + console.log('==>valuevalue', value); + return value === undefined ? { type: 'error', message: 'a is required' } : null +}] }) +form.registerField({ path: 'b', rules: [(value) => { + return value === undefined ? { type: 'warning', message: 'b is required' } : null +}] }) +// form.setFieldValue('a', 1) +const result = form.validate(); +result.then(({ warnings, errors }) => { + console.log('warnings', warnings); + console.log('errors', errors); +}); + +``` \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index 404fd992d68..41fa458efb1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@uform/core", - "version": "0.4.3", + "version": "0.4.0", "license": "MIT", "main": "lib", "repository": { @@ -16,7 +16,7 @@ "npm": ">=3.0.0" }, "scripts": { - "build": "tsc --declaration" + "build": "rm -rf lib && tsc --declaration" }, "devDependencies": { "typescript": "^3.5.2" @@ -26,11 +26,10 @@ "scheduler": ">=0.11.2" }, "dependencies": { - "@uform/types": "^0.4.3", - "@uform/utils": "^0.4.3", - "@uform/validator": "^0.4.3", - "dot-match": "^0.1.18", - "rxjs": "^6.3.3" + "@uform/types": "^0.4.0", + "@uform/shared": "^0.4.0", + "@uform/validator": "^0.4.0", + "immer": "^3.2.0" }, "publishConfig": { "access": "public" diff --git a/packages/core/src/__test__/form.spec.js b/packages/core/src/__test__/form.spec.js deleted file mode 100644 index 097cf68678f..00000000000 --- a/packages/core/src/__test__/form.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import { createForm } from '../index' - -test('Increase lastValidateValue value processing during initialization', async () => { - const inpueFieldValidate = jest.fn() - const requriedFieldValidate = jest.fn() - - const form = createForm({ - initialValues: { - requriedField: 'defaultValue' - } - }) - - form.registerField('inpueField', { - props: { - requried: true, - 'x-rules': () => - new Promise(resolve => { - inpueFieldValidate() - - resolve() - }) - } - }) - - form.registerField('requriedField', { - props: { - requried: true, - 'x-rules': () => - new Promise(resolve => { - requriedFieldValidate() - - resolve() - }) - } - }) - - form.setValue('inpueField', 1111) - await sleep(1000) - expect(inpueFieldValidate).toHaveBeenCalledTimes(1) - expect(requriedFieldValidate).toHaveBeenCalledTimes(0) - - form.setValue('requriedField', 2222) - await sleep(1000) - expect(inpueFieldValidate).toHaveBeenCalledTimes(1) - expect(requriedFieldValidate).toHaveBeenCalledTimes(1) -}) diff --git a/packages/core/src/__tests__/__snapshots__/index.spec.ts.snap b/packages/core/src/__tests__/__snapshots__/index.spec.ts.snap new file mode 100644 index 00000000000..09f00018cff --- /dev/null +++ b/packages/core/src/__tests__/__snapshots__/index.spec.ts.snap @@ -0,0 +1,5106 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`createForm initialValue 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": 111, + "bb": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": 111, + "bb": 222, + }, + "warnings": Array [], + }, +} +`; + +exports[`createForm initialValues after init 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": 111, + "bb": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": 111, + "bb": 222, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 111, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 111, + "values": Array [ + 111, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 222, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "bb", + "path": "bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 222, + "values": Array [ + 222, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`createForm initialValues on init 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": 111, + "bb": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": 111, + "bb": 222, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 111, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 111, + "values": Array [ + 111, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 222, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "bb", + "path": "bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 222, + "values": Array [ + 222, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`createForm lifecycles 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": 111, + "bb": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": "change aa", + "bb": "change bb", + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 111, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change aa", + "values": Array [ + "change aa", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 222, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "bb", + "path": "bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change bb", + "values": Array [ + "change bb", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`createForm merge values and initialValues 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "a": "x", + "b": "y", + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "a": 1, + "b": 2, + }, + "warnings": Array [], + }, +} +`; + +exports[`createForm values 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": 111, + "bb": 222, + }, + "warnings": Array [], + }, +} +`; + +exports[`createMutators blur 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object {}, + "warnings": Array [], + }, + "a": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "a", + "path": "a", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": undefined, + "values": Array [ + undefined, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`createMutators blur 2`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object {}, + "warnings": Array [], + }, + "a": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "a", + "path": "a", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": undefined, + "values": Array [ + undefined, + ], + "visible": true, + "visited": true, + "warnings": Array [], + }, +} +`; + +exports[`createMutators focus 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object {}, + "warnings": Array [], + }, + "a": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "a", + "path": "a", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": undefined, + "values": Array [ + undefined, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`createMutators focus 2`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object {}, + "warnings": Array [], + }, + "a": Object { + "active": true, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "a", + "path": "a", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": undefined, + "values": Array [ + undefined, + ], + "visible": true, + "visited": true, + "warnings": Array [], + }, +} +`; + +exports[`graph getFormGraph 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": 111, + "bb": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": "change aa", + "bb": "change bb", + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 111, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change aa", + "values": Array [ + "change aa", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 222, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "bb", + "path": "bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change bb", + "values": Array [ + "change bb", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`graph setFormGraph 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": 111, + "bb": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "bb": "change bb", + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 111, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change aa", + "values": Array [ + "change aa", + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 222, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "bb", + "path": "bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change bb", + "values": Array [ + "change bb", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences deep nested visible with VirtualField 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object {}, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "cc": 123, + }, + "values": Array [ + Object { + "cc": 123, + }, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "display": true, + "displayName": "VirtualFieldState", + "initialized": true, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "props": Object {}, + "unmounted": false, + "visible": false, + }, + "aa.bb.cc": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.cc", + "path": "aa.bb.cc", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences deep nested visible(middle part) 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object {}, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Object { + "cc": 123, + }, + }, + "values": Array [ + Object { + "bb": Object { + "cc": 123, + }, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "cc": 123, + }, + "values": Array [ + Object { + "cc": 123, + }, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "aa.bb.cc": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.cc", + "path": "aa.bb.cc", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences deep nested visible(root) 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object {}, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Object { + "cc": 123, + }, + }, + "values": Array [ + Object { + "bb": Object { + "cc": 123, + }, + }, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "cc": 123, + }, + "values": Array [ + Object { + "cc": 123, + }, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "aa.bb.cc": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.cc", + "path": "aa.bb.cc", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences dynamic remove with intialValues 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": 345, + "bb": 678, + }, + ], + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": "change aa", + "bb": 678, + }, + ], + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": 345, + "bb": 678, + }, + ], + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": "change aa", + "bb": 678, + }, + ], + "values": Array [ + Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": "change aa", + "bb": 678, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 123, + "bb": 321, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.0", + "path": "aa.0", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 123, + "bb": 321, + }, + "values": Array [ + Object { + "aa": 123, + "bb": 321, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.0.aa", + "path": "aa.0.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 321, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.0.bb", + "path": "aa.0.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 321, + "values": Array [ + 321, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 345, + "bb": 678, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.1", + "path": "aa.1", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": "change aa", + "bb": 678, + }, + "values": Array [ + Object { + "aa": "change aa", + "bb": 678, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 345, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.1.aa", + "path": "aa.1.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change aa", + "values": Array [ + "change aa", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 678, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.1.bb", + "path": "aa.1.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 678, + "values": Array [ + 678, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences dynamic remove with intialValues 2`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": 345, + "bb": 678, + }, + ], + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Array [ + Object { + "aa": "change aa", + "bb": 678, + }, + ], + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": 345, + "bb": 678, + }, + ], + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": "change aa", + "bb": 678, + }, + ], + "values": Array [ + Array [ + Object { + "aa": "change aa", + "bb": 678, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 123, + "bb": 321, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.0", + "path": "aa.0", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": "change aa", + "bb": 678, + }, + "values": Array [ + Object { + "aa": "change aa", + "bb": 678, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.0.aa", + "path": "aa.0.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change aa", + "values": Array [ + "change aa", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 321, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.0.bb", + "path": "aa.0.bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 678, + "values": Array [ + 678, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences dynamic remove with intialValues 3`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": 345, + "bb": 678, + }, + ], + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": "change aa", + "bb": 678, + }, + ], + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": 345, + "bb": 678, + }, + ], + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": "change aa", + "bb": 678, + }, + ], + "values": Array [ + Array [ + Object { + "aa": 123, + "bb": 321, + }, + Object { + "aa": "change aa", + "bb": 678, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 123, + "bb": 321, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.0", + "path": "aa.0", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 123, + "bb": 321, + }, + "values": Array [ + Object { + "aa": 123, + "bb": 321, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.0.aa", + "path": "aa.0.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.0.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 321, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.0.bb", + "path": "aa.0.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 321, + "values": Array [ + 321, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 345, + "bb": 678, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.1", + "path": "aa.1", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": "change aa", + "bb": 678, + }, + "values": Array [ + Object { + "aa": "change aa", + "bb": 678, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 345, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.1.aa", + "path": "aa.1.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change aa", + "values": Array [ + "change aa", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 678, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.1.bb", + "path": "aa.1.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 678, + "values": Array [ + 678, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences nested dynamic remove 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Array [ + undefined, + Object { + "aa": "change aa", + }, + ], + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + undefined, + Object { + "aa": "change aa", + }, + ], + "values": Array [ + Array [ + undefined, + Object { + "aa": "change aa", + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.1", + "path": "aa.1", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": "change aa", + }, + "values": Array [ + Object { + "aa": "change aa", + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.1.aa", + "path": "aa.1.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change aa", + "values": Array [ + "change aa", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.1.bb", + "path": "aa.1.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": undefined, + "values": Array [ + undefined, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences nested dynamic remove 2`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Array [ + Object { + "aa": "change aa", + }, + ], + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": "change aa", + }, + ], + "values": Array [ + Array [ + Object { + "aa": "change aa", + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences nested dynamic remove 3`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Array [ + undefined, + Object { + "aa": "change aa", + }, + ], + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + undefined, + Object { + "aa": "change aa", + }, + ], + "values": Array [ + Array [ + undefined, + Object { + "aa": "change aa", + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.1", + "path": "aa.1", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": "change aa", + }, + "values": Array [ + Object { + "aa": "change aa", + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.1.aa", + "path": "aa.1.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "change aa", + "values": Array [ + "change aa", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.1.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.1.bb", + "path": "aa.1.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": undefined, + "values": Array [ + undefined, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences nested visible 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Object { + "bb": 123, + "cc": 222, + }, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object {}, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "bb": 123, + "cc": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": 123, + "cc": 222, + }, + "values": Array [ + Object { + "bb": 123, + "cc": 222, + }, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "aa.cc": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 222, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.cc", + "path": "aa.cc", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 222, + "values": Array [ + 222, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences nested visible 2`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Object { + "bb": 123, + "cc": 222, + }, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object {}, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "bb": 123, + "cc": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": 123, + "cc": 222, + }, + "values": Array [ + Object { + "bb": 123, + "cc": 222, + }, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, + "aa.cc": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 222, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.cc", + "path": "aa.cc", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 222, + "values": Array [ + 222, + ], + "visible": false, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`major sences nested visible 3`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Object { + "bb": 123, + "cc": 222, + }, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": 123, + "cc": 222, + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "bb": 123, + "cc": 222, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": 123, + "cc": 222, + }, + "values": Array [ + Object { + "bb": 123, + "cc": 222, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.cc": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 222, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.cc", + "path": "aa.cc", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 222, + "values": Array [ + 222, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`reset array reset forceclear 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "values": Array [ + Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "values": Array [ + Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 123, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.0", + "path": "aa.bb.0", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 123, + }, + "values": Array [ + Object { + "aa": 123, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.0.aa", + "path": "aa.bb.0.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 321, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1", + "path": "aa.bb.1", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 321, + }, + "values": Array [ + Object { + "aa": 321, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 321, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1.aa", + "path": "aa.bb.1.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 321, + "values": Array [ + 321, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`reset array reset forceclear 2`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": Array [], + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Array [], + }, + "values": Array [ + Object { + "bb": Array [], + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [], + "values": Array [ + Array [], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`reset array reset no forceclear(initial values) 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "values": Array [ + Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "values": Array [ + Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 123, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.0", + "path": "aa.bb.0", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 123, + }, + "values": Array [ + Object { + "aa": 123, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.0.aa", + "path": "aa.bb.0.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 321, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1", + "path": "aa.bb.1", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 321, + }, + "values": Array [ + Object { + "aa": 321, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 321, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1.aa", + "path": "aa.bb.1.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 321, + "values": Array [ + 321, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`reset array reset no forceclear(initial values) 2`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + }, + "values": Array [ + Object { + "bb": Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + "values": Array [ + Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 123, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb.0", + "path": "aa.bb.0", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": "aa changed", + }, + "values": Array [ + Object { + "aa": "aa changed", + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb.0.aa", + "path": "aa.bb.0.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa changed", + "values": Array [ + "aa changed", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 321, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1", + "path": "aa.bb.1", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 321, + }, + "values": Array [ + Object { + "aa": 321, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 321, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1.aa", + "path": "aa.bb.1.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 321, + "values": Array [ + 321, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`reset array reset no forceclear(initial values) 3`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": true, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "values": Array [ + Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "values": Array [ + Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 123, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.0", + "path": "aa.bb.0", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 123, + }, + "values": Array [ + Object { + "aa": 123, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 123, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.0.aa", + "path": "aa.bb.0.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": Object { + "aa": 321, + }, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1", + "path": "aa.bb.1", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 321, + }, + "values": Array [ + Object { + "aa": 321, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": 321, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1.aa", + "path": "aa.bb.1.aa", + "pristine": true, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 321, + "values": Array [ + 321, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`reset array reset no forceclear(values) 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + "values": Array [ + Object { + "bb": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + "values": Array [ + Array [ + Object { + "aa": 123, + }, + Object { + "aa": 321, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.0", + "path": "aa.bb.0", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 123, + }, + "values": Array [ + Object { + "aa": 123, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.0.aa", + "path": "aa.bb.0.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 123, + "values": Array [ + 123, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1", + "path": "aa.bb.1", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 321, + }, + "values": Array [ + Object { + "aa": 321, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1.aa", + "path": "aa.bb.1.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 321, + "values": Array [ + 321, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`reset array reset no forceclear(values) 2`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + }, + "values": Array [ + Object { + "bb": Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + "values": Array [ + Array [ + Object { + "aa": "aa changed", + }, + Object { + "aa": 321, + }, + ], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb.0", + "path": "aa.bb.0", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": "aa changed", + }, + "values": Array [ + Object { + "aa": "aa changed", + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb.0.aa", + "path": "aa.bb.0.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa changed", + "values": Array [ + "aa changed", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1", + "path": "aa.bb.1", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "aa": 321, + }, + "values": Array [ + Object { + "aa": 321, + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb.1.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": false, + "mounted": false, + "name": "aa.bb.1.aa", + "path": "aa.bb.1.aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": 321, + "values": Array [ + 321, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`reset array reset no forceclear(values) 3`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object {}, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": false, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": Object { + "bb": Array [], + }, + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa", + "path": "aa", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Object { + "bb": Array [], + }, + "values": Array [ + Object { + "bb": Array [], + }, + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa.bb": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": undefined, + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": false, + "name": "aa.bb", + "path": "aa.bb", + "pristine": false, + "props": Object {}, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": Array [], + "values": Array [ + Array [], + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; diff --git a/packages/core/src/__tests__/field.state.spec.ts b/packages/core/src/__tests__/field.state.spec.ts new file mode 100644 index 00000000000..5c9cd2c1486 --- /dev/null +++ b/packages/core/src/__tests__/field.state.spec.ts @@ -0,0 +1,32 @@ +//import { FieldState } from '../state/field' + +test('subscribe', () => { + //todo +}) +test('unsubscribe', () => { + //todo +}) +test('batch', () => { + //todo +}) +test('getState', () => { + //todo +}) +test('setState', () => { + //todo +}) +test('getSourceState', () => { + //todo +}) +test('setSourceState', () => { + //todo +}) +test('setState', () => { + //todo +}) +test('hasChanged', () => { + //todo +}) +test('getChanged', () => { + //todo +}) diff --git a/packages/core/src/__tests__/form.state.spec.ts b/packages/core/src/__tests__/form.state.spec.ts new file mode 100644 index 00000000000..6135d4643fd --- /dev/null +++ b/packages/core/src/__tests__/form.state.spec.ts @@ -0,0 +1,32 @@ +//import { FormState } from '../state/form' + +test('subscribe', () => { + //todo +}) +test('unsubscribe', () => { + //todo +}) +test('batch', () => { + //todo +}) +test('getState', () => { + //todo +}) +test('setState', () => { + //todo +}) +test('getSourceState', () => { + //todo +}) +test('setSourceState', () => { + //todo +}) +test('setState', () => { + //todo +}) +test('hasChanged', () => { + //todo +}) +test('getChanged', () => { + //todo +}) diff --git a/packages/core/src/__tests__/graph.spec.ts b/packages/core/src/__tests__/graph.spec.ts new file mode 100644 index 00000000000..b707eb01da0 --- /dev/null +++ b/packages/core/src/__tests__/graph.spec.ts @@ -0,0 +1,49 @@ +//import { FormGraph } from '../shared/graph' + +test('constructor',()=>{ + //todo +}) + +test('select',()=>{ + //todo +}) + +test('selectParent',()=>{ + //todo +}) + +test('selectChildren',()=>{ + //todo +}) + +test('exist',()=>{ + //todo +}) + +test('eachChildren',()=>{ + //todo +}) + +test('eachParent',()=>{ + //todo +}) + +test('getLatestParent',()=>{ + //todo +}) + +test('appendNode',()=>{ + //todo +}) + +test('remove',()=>{ + //todo +}) + +test('toJSON',()=>{ + //todo +}) + +test('fromJSON',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/core/src/__tests__/index.spec.ts b/packages/core/src/__tests__/index.spec.ts new file mode 100644 index 00000000000..d8bb50152b0 --- /dev/null +++ b/packages/core/src/__tests__/index.spec.ts @@ -0,0 +1,1130 @@ +import { isEqual } from '@uform/shared' +import { createForm, LifeCycleTypes, FormLifeCycle, FormPath } from '../index' +import { ValidateDescription, ValidatePatternRules } from '@uform/validator' + +// mock datasource +const testValues = { aa: 111, bb: 222 } +const testValues2 = { aa: '123', bb: '321' } +const changeValues = { aa: 'change aa', bb: 'change bb' } +const resetInitValues = { + aa: { + bb: [{ aa: 123 }, { aa: 321 }] + } +} +const deepValues = { + a: { + b: { c: { d: { e: 1}}}, + c: { + e: 2 + } + }, + b: { + c: 3 + } +} + + +describe('createForm', () => { + test('values', () => { + const form = createForm({ + values: testValues + }) + expect(form.getFormState(state => state.values)).toEqual(testValues) + expect(form.getFormState(state => state.pristine)).toEqual(false) + expect(form.getFormState(state => state.initialized)).toEqual(true) + expect(form.getFormGraph()).toMatchSnapshot() + }) + + test('initialValues on init', () => { + const form = createForm({ + initialValues: testValues + }) + const aa = form.registerField({ path: 'aa' }) + const bb = form.registerField({ path: 'bb' }) + expect(form.getFormState(state => state.values)).toEqual(testValues) + expect(form.getFormState(state => state.initialValues)).toEqual(testValues) + expect(form.getFormState(state => state.pristine)).toEqual(true) + expect(form.getFormState(state => state.initialized)).toEqual(true) + expect(aa.getState(state => state.value)).toEqual(testValues.aa) + expect(bb.getState(state => state.value)).toEqual(testValues.bb) + expect(form.getFormGraph()).toMatchSnapshot() + }) + + test('merge values and initialValues', () => { + const form = createForm({ + values: { a: 1, b: 2 }, + initialValues: { a: 'x', b: 'y' } + }) + expect(form.getFormState(state => state.values)).toEqual({ a: 1, b: 2 }) + expect(form.getFormGraph()).toMatchSnapshot() + }) + + test('initialValues after init', () => { + const form = createForm() + const aa = form.registerField({ path: 'aa' }) + const bb = form.registerField({ path: 'bb' }) + form.setFormState(state => { + state.initialValues = testValues + }) + expect(form.getFormState(state => state.values)).toEqual(testValues) + expect(form.getFormState(state => state.initialValues)).toEqual(testValues) + expect(form.getFormState(state => state.pristine)).toEqual(true) + expect(form.getFormState(state => state.initialized)).toEqual(true) + expect(aa.getState(state => state.value)).toEqual(testValues.aa) + expect(bb.getState(state => state.value)).toEqual(testValues.bb) + expect(form.getFormGraph()).toMatchSnapshot() + }) + + test('initialValue', () => { + const form = createForm({ + initialValues: testValues + }) + expect(form.getFormState(state => state.values)).toEqual(testValues) + expect(form.getFormState(state => state.initialValues)).toEqual(testValues) + expect(form.getFormState(state => state.pristine)).toEqual(true) + expect(form.getFormState(state => state.initialized)).toEqual(true) + expect(form.getFormGraph()).toMatchSnapshot() + }) + + test('lifecycles', () => { + const onFormInit = jest.fn() + const onFieldInit = jest.fn() + const onFieldChange = jest.fn() + const form = createForm({ + initialValues: testValues, + lifecycles: [ + new FormLifeCycle(LifeCycleTypes.ON_FORM_INIT, onFormInit), + new FormLifeCycle(LifeCycleTypes.ON_FIELD_INIT, onFieldInit), + new FormLifeCycle(LifeCycleTypes.ON_FIELD_CHANGE, onFieldChange) + ] + }) + + const aa = form.registerField({ path: 'aa', value: testValues2.aa }) + const bb = form.registerField({ path: 'bb', value: testValues2.bb }) + + // registerField will trigger ON_FIELD_CHANGE(because of initialized changed) + expect(onFormInit).toBeCalledTimes(1) + expect(onFieldInit).toBeCalledTimes(2) + expect(onFieldChange).toBeCalledTimes(2) + expect(form.getFormState(state => state.values)).toEqual(testValues2) + + // change field's value + aa.setState(state => state.value = changeValues.aa) + bb.setState(state => state.value = changeValues.bb) + expect(onFieldChange).toBeCalledTimes(4) + expect(form.getFormState(state => state.values)).toEqual(changeValues) + expect(aa.getState(state => state.value)).toEqual(changeValues.aa) + expect(bb.getState(state => state.value)).toEqual(changeValues.bb) + expect(form.getFormGraph()).toMatchSnapshot() + }) +}) + +describe('graph', () => { + test('getFormGraph', () => { + const form = createForm({ + initialValues: testValues + }) + + form.registerField({ path: 'aa', value: changeValues.aa }) + form.registerField({ path: 'bb', value: changeValues.bb }) + expect(form.getFormGraph()).toMatchSnapshot() + }) + + test('setFormGraph', () => { + const form = createForm({ + initialValues: testValues + }) + + form.registerField({ path: 'aa', value: changeValues.aa }) + form.registerField({ path: 'bb', value: changeValues.bb }) + const snapshot = form.getFormGraph() + form.setFieldState('aa', state => state.visible = false) + expect(form.getFormGraph()).toMatchSnapshot() + + // change graph or you can also call it 'time travel' + form.setFormGraph(snapshot) + expect(form.getFormGraph()).toEqual(snapshot) + }) +}) + +describe('submit', () => { + test('onSubmit', async() => { + const onSubmitContructor = jest.fn() + const onSubmitFn = jest.fn() + const changeSubmitPayload = (values) => ({ hello: 'world' }) + const form1 = createForm({ onSubmit: onSubmitContructor }) + const form2 = createForm() + + expect(onSubmitContructor).toBeCalledTimes(0) + expect(onSubmitContructor).toBeCalledTimes(0) + await form1.submit() + await form2.submit() + expect(onSubmitContructor).toBeCalledTimes(1) + expect(onSubmitFn).toBeCalledTimes(0) + + // priority: onSubmitFn > onSubmitContructor + await form1.submit(onSubmitFn) + await form2.submit(onSubmitFn) + expect(onSubmitContructor).toBeCalledTimes(1) + expect(onSubmitFn).toBeCalledTimes(2) + const result = await form2.submit(changeSubmitPayload) + expect(result).toEqual({ validated: { + errors: [], + warnings: [], + }, payload: { hello: 'world'} }) + }) + + test('submitResult', async () => { + const sunmitFailed = jest.fn() + const form = createForm() + form.registerField({ path: 'a', rules: [(value) => { + return value === undefined ? { type: 'error', message: 'a is required' } : null + }] }) + form.registerField({ path: 'b', rules: [(value) => { + return value === undefined ? { type: 'warning', message: 'b is required' } : null + }] }) + + // error failed + try { + await form.submit() + } catch (errors) { + sunmitFailed() + expect(errors).toEqual([{ path: 'a', messages: ['a is required']}]) + } + + // warning pass + form.setFieldValue('a', 1) + let validated + try { + const result = await form.submit() + validated = result.validated + } catch (errors) { + sunmitFailed() + } + + expect(validated.warnings).toEqual([{ path: 'b', messages: ['b is required'] }]) + expect(validated.errors).toEqual([]) + expect(sunmitFailed).toHaveBeenCalledTimes(1) + }) + + test('repeat submit', async () => { + const form = createForm() + const result1 = form.submit() + const result2 = form.submit() + // reuse before result + expect(result1).toEqual(result2) + }) + + test('basic', async () => { + const onSubmitStart = jest.fn() + const onSubmitEnd = jest.fn() + const form = createForm({ + lifecycles: [ + new FormLifeCycle(LifeCycleTypes.ON_FORM_SUBMIT_START, onSubmitStart), + new FormLifeCycle(LifeCycleTypes.ON_FORM_SUBMIT_END, onSubmitEnd), + ], + onSubmit: () => { + expect(form.getFormState(state => state.submitting)).toEqual(true) + } + }) + expect(form.getFormState(state => state.submitting)).toEqual(false) + expect(onSubmitStart).toBeCalledTimes(0) + expect(onSubmitEnd).toBeCalledTimes(0) + await form.submit() + expect(form.getFormState(state => state.submitting)).toEqual(false) + expect(onSubmitStart).toBeCalledTimes(1) + expect(onSubmitEnd).toBeCalledTimes(1) + }) +}) + +describe('reset', () => { + test('array reset forceclear', async () => { + const form = createForm({ + initialValues: resetInitValues + }) + + form.registerField({ path: 'aa' }) + form.registerField({ path: 'aa.bb' }) + form.registerField({ path: 'aa.bb.0' }) + form.registerField({ path: 'aa.bb.1' }) + form.registerField({ path: 'aa.bb.0.aa' }) + form.registerField({ path: 'aa.bb.1.aa' }) + expect(form.getFormGraph()).toMatchSnapshot() + + form.setFieldState('aa.bb.0.aa', state => { + state.value = 'aa changed' + }) + await form.reset({ forceClear: true }) + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFormState(state => state.values)).toEqual({ aa: { bb: [] } }) + expect(form.getFormState(state => state.initialValues)).toEqual(resetInitValues) + }) + + test('array reset no forceclear(initial values)', async () => { + const form = createForm({ + initialValues: resetInitValues + }) + form.registerField({ path: 'aa' }) + form.registerField({ path: 'aa.bb' }) + form.registerField({ path: 'aa.bb.0' }) + form.registerField({ path: 'aa.bb.1' }) + form.registerField({ path: 'aa.bb.0.aa' }) + form.registerField({ path: 'aa.bb.1.aa' }) + expect(form.getFormGraph()).toMatchSnapshot() + form.setFieldState('aa.bb.0.aa', state => { + state.value = 'aa changed' + }) + expect(form.getFormGraph()).toMatchSnapshot() + await form.reset() + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFormState(state => state.values)).toEqual(resetInitValues) + expect(form.getFormState(state => state.initialValues)).toEqual(resetInitValues) + }) + + test('array reset no forceclear(values)', async () => { + const form = createForm({ + values: resetInitValues + }) + form.registerField({ path: 'aa' }) + form.registerField({ path: 'aa.bb' }) + form.registerField({ path: 'aa.bb.0' }) + form.registerField({ path: 'aa.bb.1' }) + form.registerField({ path: 'aa.bb.0.aa' }) + form.registerField({ path: 'aa.bb.1.aa' }) + expect(form.getFormGraph()).toMatchSnapshot() + form.setFieldState('aa.bb.0.aa', state => { + state.value = 'aa changed' + }) + expect(form.getFormGraph()).toMatchSnapshot() + await form.reset() + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFormState(state => state.values)).toEqual({ aa: { bb: [] }}) + expect(form.getFormState(state => state.initialValues)).toEqual({}) + }) +}) + +describe('clearErrors', () => { + test('basic', async () => { + const form = createForm() + const warnMsg = ['warning msg'] + const errMsg = ['error msg'] + form.registerField({ path: 'b', rules: [(v) => v === undefined ? ({ type: 'warning', message: 'warning msg' }) : undefined] }) // CustomValidator warning + form.registerField({ path: 'c', rules: [(v) => v === undefined ? ({ type: 'error', message: 'error msg' }) : undefined] }) // CustomValidator error + const result1 = await form.validate() + expect(result1.warnings).toEqual([{ path: 'b', messages: warnMsg }]) + expect(result1.errors).toEqual([{ path: 'c', messages: errMsg}]) + + form.clearErrors('b') + expect(form.getFormState(state => state.warnings)).toEqual([]) + expect(form.getFormState(state => state.errors)).toEqual([{ path: 'c', messages: errMsg}]) + + form.clearErrors('c') + expect(form.getFormState(state => state.warnings)).toEqual([]) + expect(form.getFormState(state => state.errors)).toEqual([]) + + const result2 = await form.validate() + expect(result2.warnings).toEqual([{ path: 'b', messages: warnMsg }]) + expect(result2.errors).toEqual([{ path: 'c', messages: errMsg}]) + + form.clearErrors() + expect(form.getFormState(state => state.warnings)).toEqual([]) + expect(form.getFormState(state => state.errors)).toEqual([]) + }) + + test('wildcard path', async () => { + + }) + + test('effect', async () => { + + }) +}) + +describe('validate', () => { + test('empty', async () => { + const form = createForm() + const { warnings, errors } = await form.validate() + expect(warnings).toEqual([]) + expect(errors).toEqual([]) + }) + + test('onValidateFailed', async () => { + const onValidateFailedTrigger = jest.fn() + const onValidateFailed = ({ warnings, errors }) => { + expect(warnings).toEqual([{ path: 'b', messages: ['warning msg'] }]) + expect(errors).toEqual([{ path: 'c', messages: ['error msg']}]) + onValidateFailedTrigger(); + }; + const form = createForm({ + onValidateFailed + }) + form.registerField({ path: 'b', rules: [() => ({ type: 'warning', message: 'warning msg' })] }) // CustomValidator warning + form.registerField({ path: 'c', rules: [() => ({ type: 'error', message: 'error msg' })] }) // CustomValidator error + await form.validate() + expect(onValidateFailedTrigger).toBeCalledTimes(1) + }) + + test('validate basic', async () => { + const onValidateStart = jest.fn() + const onValidateEnd = jest.fn() + const form = createForm({ + lifecycles: [ + new FormLifeCycle(LifeCycleTypes.ON_FORM_VALIDATE_START, onValidateStart), + new FormLifeCycle(LifeCycleTypes.ON_FORM_VALIDATE_END, onValidateEnd), + ], + }) + form.registerField({ path: 'a', rules: ['number'] }) // string + form.registerField({ path: 'b', rules: [() => ({ type: 'warning', message: 'warning msg' })] }) // CustomValidator warning + form.registerField({ path: 'c', rules: [() => ({ type: 'error', message: 'warning msg' })] }) // CustomValidator error + form.registerField({ path: 'd', rules: [() => 'straight error msg'] }) // CustomValidator string + form.registerField({ path: 'e', rules: [{ required: true, message: 'desc msg' }] }) // ValidateDescription + + expect(onValidateStart).toBeCalledTimes(0) + expect(onValidateEnd).toBeCalledTimes(0) + expect(form.getFormState(state => state.validating)).toEqual(false) + const validatePromise = form.validate() + expect(form.getFormState(state => state.validating)).toEqual(true) + expect(onValidateStart).toBeCalledTimes(1) + validatePromise.then((validated) => { + expect(form.getFormState(state => state.validating)).toEqual(false) + expect(onValidateEnd).toBeCalledTimes(1) + const { warnings, errors } = validated; + expect(warnings.length).toEqual(1) + expect(errors.length).toEqual(4) + }) + }) + + test('path', async () => { + const form = createForm() + form.registerField({ path: 'b', rules: [(v) => v === undefined ? ({ type: 'warning', message: 'warning msg' }) : undefined] }) // CustomValidator warning + form.registerField({ path: 'c', rules: [(v) => v === undefined ? ({ type: 'error', message: 'error msg' }) : undefined] }) // CustomValidator error + const bResult = await form.validate('b') + expect(bResult.warnings).toEqual([{ path: 'b', messages: ['warning msg'] }]) + expect(bResult.errors).toEqual([]) + expect(form.getFieldState('b', state => state.warnings)).toEqual(['warning msg']) + expect(form.getFieldState('c', state => state.errors)).toEqual([]) + expect(form.getFormState(state => state.warnings)).toEqual([{ path: 'b', messages: ['warning msg'] }]) + expect(form.getFormState(state => state.errors)).toEqual([]) + + const cResult = await form.validate('c') + expect(cResult.warnings).toEqual([]) + expect(cResult.errors).toEqual([{ path: 'c', messages: ['error msg']}]) + expect(form.getFieldState('b', state => state.warnings)).toEqual(['warning msg']) + expect(form.getFieldState('c', state => state.errors)).toEqual(['error msg']) + expect(form.getFormState(state => state.warnings)).toEqual([{ path: 'b', messages: ['warning msg'] }]) + expect(form.getFormState(state => state.errors)).toEqual([{ path: 'c', messages: ['error msg']}]) + + form.setFieldValue('b', 1) + form.setFieldValue('c', 1) + const bResult2 = await form.validate('b') + const cResult2 = await form.validate('c') + expect(bResult2.warnings).toEqual([]) + expect(bResult2.errors).toEqual([]) + expect(cResult2.warnings).toEqual([]) + expect(cResult2.errors).toEqual([]) + expect(form.getFieldState('b', state => state.warnings)).toEqual([]) + expect(form.getFieldState('c', state => state.errors)).toEqual([]) + expect(form.getFormState(state => state.warnings)).toEqual([]) + expect(form.getFormState(state => state.errors)).toEqual([]) + }) +}) + +describe('setFormState', () => { + test('no callback', async () => { + const form = createForm() + const state = form.getFormState() + form.setFormState() + expect(form.getFormState()).toEqual(state) + }) + + test('set', async() => { + const form = createForm() + // pristine 依赖 draft.values === draft.initialValues + // invalid 依赖 errors.length === 0 + // valid 是 invalid的取反 + // loading 取决于validating + // mounted 和 unmounted 互为取反,先读mounted + // errors, warnings 无法被设置,会从最新的state中获取 + form.registerField({ path: 'b', rules: [(v) => v === undefined ? ({ type: 'warning', message: 'warning msg' }) : undefined] }) // CustomValidator warning + form.registerField({ path: 'c', rules: [(v) => v === undefined ? ({ type: 'error', message: 'error msg' }) : undefined] }) // CustomValidator error + const { errors, warnings } = await form.validate() + const invalid = errors.length > 0 + + const values = { b: '2' } + const initialValues = { a: '1' } + const validating = true + form.setFormState((state) => { + state.pristine = false + state.valid = false + state.invalid = false + state.loading = false + state.validating = validating + state.submitting = true + state.initialized = false + state.editable = false + state.values = values + state.initialValues = initialValues + state.mounted = true + state.unmounted = true + state.props = { hello: 'world' } + }) + expect(form.getFormState()).toEqual({ + displayName: 'FormState', + pristine: isEqual(values, initialValues), + valid: !invalid, + invalid: invalid, + loading: validating, + validating: validating, + submitting: true, + initialized: false, + editable: false, + errors, + warnings, + values, + initialValues, + mounted: true, + unmounted: false, + props: { hello: 'world' }, + }) + }) +}) + +describe('getFormState', () => { + test('basic', async () => { + const form = createForm() + const state = form.getFormState() + expect(state).toEqual(form.getFormState((state) => state)) + expect(form.getFormState(state => state.pristine)).toEqual(state.pristine) + expect(form.getFormState(state => state.valid)).toEqual(state.valid) + expect(form.getFormState(state => state.invalid)).toEqual(state.invalid) + expect(form.getFormState(state => state.loading)).toEqual(state.loading) + expect(form.getFormState(state => state.validating)).toEqual(state.validating) + expect(form.getFormState(state => state.submitting)).toEqual(state.submitting) + expect(form.getFormState(state => state.initialized)).toEqual(state.initialized) + expect(form.getFormState(state => state.editable)).toEqual(state.editable) + expect(form.getFormState(state => state.errors)).toEqual(state.errors) + expect(form.getFormState(state => state.warnings)).toEqual(state.warnings) + expect(form.getFormState(state => state.values)).toEqual(state.values) + expect(form.getFormState(state => state.initialValues)).toEqual(state.initialValues) + expect(form.getFormState(state => state.mounted)).toEqual(state.mounted) + expect(form.getFormState(state => state.unmounted)).toEqual(state.unmounted) + expect(form.getFormState(state => state.props)).toEqual(state.props) + }) +}) + +describe('setFieldState', () => { + test('no callback', async () => { + const form = createForm() + form.registerField({ path: 'a' }) + const state = form.getFieldState('a') + form.setFieldState('a') + expect(form.getFieldState('a')).toEqual(state) + }) + + test('validating and loading', () => { + const form = createForm() + form.registerField({ path: 'a', rules: [ + (v) => v === undefined ? ({ type: 'warning', message: 'warning msg' }) : undefined, + (v) => v === undefined ? ({ type: 'error', message: 'error msg' }) : undefined + ] }) + expect(form.getFieldState('a', state => state.validating)).toEqual(false) + expect(form.getFieldState('a', state => state.loading)).toEqual(false) + form.setFieldState('a', state => state.validating = true) + expect(form.getFieldState('a', state => state.loading)).toEqual(true) + form.setFieldState('a', state => state.validating = false) + expect(form.getFieldState('a', state => state.loading)).toEqual(false) + }); + + test('value, values and parseValues', () => { + const form = createForm() + form.registerField({ path: 'a' }) + expect(form.getFieldState('a', state => state.modified)).toEqual(false) + expect(form.getFieldState('a', state => state.value)).toEqual(undefined) + expect(form.getFieldState('a', state => state.values)).toEqual([undefined]) + const arr = [1,2,3] + form.setFieldState('a', state => state.value = arr) + expect(form.getFieldState('a', state => state.modified)).toEqual(true) + expect(form.getFieldState('a', state => state.value)).toEqual(arr) + expect(form.getFieldState('a', state => state.values)).toEqual([arr]) + form.setFieldState('a', state => state.values = ['e', 'context']) + // values 第一个参数会是value, 处理onChange的多参数一般会和values[0]同步,这里value测试极端情况 + expect(form.getFieldState('a', state => state.value)).toEqual(arr) + expect(form.getFieldState('a', state => state.values)).toEqual([arr, 'context']) + // visible为false或者已卸载的组件无法修改value + form.setFieldState('a', state => state.visible = false) + form.setFieldState('a', state => state.value = [4,5,6]) + expect(form.getFieldState('a', state => state.value)).toEqual(arr) + form.setFieldState('a', state => { + state.visible = true; + state.unmounted = true; + }) + form.setFieldState('a', state => state.value = [4,5,6]) + expect(form.getFieldState('a', state => state.value)).toEqual(arr) + }); + + test('mount and unmount', () => { + const form = createForm() + form.registerField({ path: 'a' }) + expect(form.getFieldState('a', state => state.mounted)).toEqual(false) + expect(form.getFieldState('a', state => state.unmounted)).toEqual(false) + form.setFieldState('a', state => state.mounted = true) + expect(form.getFieldState('a', state => state.mounted)).toEqual(true) + expect(form.getFieldState('a', state => state.unmounted)).toEqual(false) + }); + + test('rules, required and parseRules', () => { + const form = createForm() + form.registerField({ path: 'a' }) + expect(form.getFieldState('a', state => state.required)).toEqual(false) + expect(form.getFieldState('a', state => state.rules)).toEqual([]) + form.setFieldState('a', state => state.required = true) + expect(form.getFieldState('a', state => state.required)).toEqual(true) + const customValidator: ValidatePatternRules[] = [(v, _: ValidateDescription) => v === undefined ? ({ type: 'warning', message: 'warning msg' }) : null]; + form.setFieldState('a', state => state.rules = customValidator) + const rules = form.getFieldState('a', state => state.rules); + expect(rules).toEqual([...customValidator, { required: true }]) + }); + + test('pristine', () => { // 无法被修改,依赖value和initialValue的差别 + const form = createForm() + form.registerField({ path: 'a' }) + expect(form.getFieldState('a', state => state.pristine)).toEqual(true) + form.setFieldState('a', state => state.pristine = false) + expect(form.getFieldState('a', state => state.pristine)).toEqual(true) + form.setFieldState('a', state => state.value = '1') + expect(form.getFieldState('a', state => state.pristine)).toEqual(false) + form.setFieldState('a', state => state.initialValue = '1') + expect(form.getFieldState('a', state => state.pristine)).toEqual(true) + }); + + test('invalid 和 valid', () => { // 无法被修改,依赖错误信息和告警信息 + const form = createForm() + form.registerField({ path: 'a' }) + expect(form.getFieldState('a', state => state.invalid)).toEqual(false) + expect(form.getFieldState('a', state => state.valid)).toEqual(true) + form.setFieldState('a', state => state.invalid = true) + form.setFieldState('a', state => state.valid = false) + expect(form.getFieldState('a', state => state.invalid)).toEqual(false) + expect(form.getFieldState('a', state => state.valid)).toEqual(true) + }); + + test('set errors and warnings', async() => { + const form = createForm() + const a = form.registerField({ path: 'a', rules: [ + (v) => v === undefined ? ({ type: 'warning', message: 'warning msg' }) : undefined, + (v) => v === undefined ? ({ type: 'error', message: 'error msg' }) : undefined + ] }) + const state = form.getFieldState('a') + expect(state.effectErrors).toEqual([]) + expect(state.effectWarnings).toEqual([]) + expect(state.ruleErrors).toEqual([]) + expect(state.ruleWarnings).toEqual([]) + + const mutators = form.createMutators(a) + const result = await mutators.validate() + expect(result.errors).toEqual([{ path: 'a', messages: ['error msg'] }]) + expect(result.warnings).toEqual([{ path: 'a', messages: ['warning msg'] }]) + // 校验后影响的是ruleErrors, ruleWarnings + const state2 = form.getFieldState('a') + expect(state2.effectErrors).toEqual([]) + expect(state2.effectWarnings).toEqual([]) + expect(state2.ruleErrors).toEqual(['error msg']) + expect(state2.ruleWarnings).toEqual(['warning msg']) + expect(state2.errors).toEqual(['error msg']) + expect(state2.warnings).toEqual(['warning msg']) + + // effectError 和 effectWarnings 需要通过设置 errors 和 warning 进行设置 + // 看起来有问题,但是对于开发者并不透出effectErrors的概念,只让他感知这是errors + // errors = effectErrors + ruleErrors + // warnings = effectWarnings + ruleWarnings + form.setFieldState('a', state => state.errors = ['effect errors msg']) + form.setFieldState('a', state => state.warnings = ['effect warning msg']) + const state3 = form.getFieldState('a') + expect(state3.effectErrors).toEqual(['effect errors msg']) + expect(state3.effectWarnings).toEqual(['effect warning msg']) + expect(state3.errors).toEqual(['error msg', 'effect errors msg']) + expect(state3.warnings).toEqual(['warning msg', 'effect warning msg']) + + // 不可编辑,清空所有错误和警告信息 + form.setFieldState('a', state => state.editable = false) + const state4 = form.getFieldState('a') + expect(state4.effectErrors).toEqual([]) + expect(state4.effectWarnings).toEqual([]) + expect(state4.errors).toEqual([]) + expect(state4.warnings).toEqual([]) + form.setFieldState('a', state => state.editable = true) + + // 隐藏,清空所有错误和警告信息 + await mutators.validate() + form.setFieldState('a', state => state.errors = ['effect errors msg']) + form.setFieldState('a', state => state.warnings = ['effect warning msg']) + form.setFieldState('a', state => state.visible = false) + const state6 = form.getFieldState('a') + expect(state6.effectErrors).toEqual([]) + expect(state6.effectWarnings).toEqual([]) + expect(state6.errors).toEqual([]) + expect(state6.warnings).toEqual([]) + + // 卸载组件,清空所有错误和警告信息 + await mutators.validate() + form.setFieldState('a', state => state.errors = ['effect errors msg']) + form.setFieldState('a', state => state.warnings = ['effect warning msg']) + const state7 = form.getFieldState('a') + expect(state7.effectErrors).toEqual([]) + expect(state7.effectWarnings).toEqual([]) + expect(state7.errors).toEqual([]) + expect(state7.warnings).toEqual([]) + }); + + test('set editable', async() => { + const form = createForm() + form.registerField({ path: 'a' }) + const state = form.getFieldState('a') + + // 初始化 + expect(state.editable).toEqual(true) + expect(state.selfEditable).toEqual(undefined) + expect(state.formEditable).toEqual(undefined) + expect(form.getFieldState('a', state => state.editable)).toEqual(true) + // 简单设置 (editable会影响selfEditable) + form.setFieldState('a', state => state.editable = false) + expect(form.getFieldState('a', state => state.editable)).toEqual(false) + expect(form.getFieldState('a', state => state.selfEditable)).toEqual(false) + // 设置影响计算的值selfEditable + form.setFieldState('a', state => state.selfEditable = true) + expect(form.getFieldState('a', state => state.editable)).toEqual(true) + // 设置影响计算的值formEditable(selfEditable优先级高于formEditable) + form.setFieldState('a', state => state.selfEditable = undefined) + form.setFieldState('a', state => state.formEditable = false) + expect(form.getFieldState('a', state => state.editable)).toEqual(false) + // 支持函数(UI层传入) + form.setFieldState('a', state => state.formEditable = () => true) + expect(form.getFieldState('a', state => state.editable)).toEqual(true) + // editable会影响selfEditable, 设置顺序又因为 editable > selfEditable > formEditable + // 前两者都无效时,最终返回formEditable的值 + form.setFieldState('a', state => state.editable = undefined) + form.setFieldState('a', state => state.formEditable = () => false) + expect(form.getFieldState('a', state => state.selfEditable)).toEqual(undefined) + expect(form.getFieldState('a', state => state.editable)).toEqual(false) + }); +}) + +describe('getFieldState', () => { + const form = createForm() + form.registerField({ path: 'a' }) + const state = form.getFieldState('a') + expect(state).toEqual(form.getFieldState('a', (state) => state)) + expect(form.getFieldState('a', state => state.name)).toEqual(state.name) + expect(form.getFieldState('a', state => state.initialized)).toEqual(state.initialized) + expect(form.getFieldState('a', state => state.pristine)).toEqual(state.pristine) + expect(form.getFieldState('a', state => state.valid)).toEqual(state.valid) + expect(form.getFieldState('a', state => state.touched)).toEqual(state.touched) + expect(form.getFieldState('a', state => state.invalid)).toEqual(state.invalid) + expect(form.getFieldState('a', state => state.visible)).toEqual(state.visible) + expect(form.getFieldState('a', state => state.display)).toEqual(state.display) + expect(form.getFieldState('a', state => state.editable)).toEqual(state.editable) + expect(form.getFieldState('a', state => state.formEditable)).toEqual(state.formEditable) + expect(form.getFieldState('a', state => state.loading)).toEqual(state.loading) + expect(form.getFieldState('a', state => state.modified)).toEqual(state.modified) + expect(form.getFieldState('a', state => state.active)).toEqual(state.active) + expect(form.getFieldState('a', state => state.visited)).toEqual(state.visited) + expect(form.getFieldState('a', state => state.validating)).toEqual(state.validating) + expect(form.getFieldState('a', state => state.errors)).toEqual(state.errors) + expect(form.getFieldState('a', state => state.values)).toEqual(state.values) + expect(form.getFieldState('a', state => state.effectErrors)).toEqual(state.effectErrors) + expect(form.getFieldState('a', state => state.warnings)).toEqual(state.warnings) + expect(form.getFieldState('a', state => state.effectWarnings)).toEqual(state.effectWarnings) + expect(form.getFieldState('a', state => state.value)).toEqual(state.value) + expect(form.getFieldState('a', state => state.initialValue)).toEqual(state.initialValue) + expect(form.getFieldState('a', state => state.rules)).toEqual(state.rules) + expect(form.getFieldState('a', state => state.required)).toEqual(state.required) + expect(form.getFieldState('a', state => state.mounted)).toEqual(state.mounted) + expect(form.getFieldState('a', state => state.unmounted)).toEqual(state.unmounted) + expect(form.getFieldState('a', state => state.props)).toEqual(state.props) +}) + +describe('setFieldValue', () => { + const form = createForm() + form.registerField({ path: 'a' }) + form.setFieldValue('a') + expect(form.getFieldValue('a')).toEqual(undefined) + expect(form.getFieldState('a', state => state.value)).toEqual(undefined) + expect(form.getFormState(state => state.values)).toEqual({ a: undefined }) + form.setFieldValue('a', 1) + expect(form.getFieldValue('a')).toEqual(1) + expect(form.getFieldState('a', state => state.value)).toEqual(1) + expect(form.getFormState(state => state.values)).toEqual({ a: 1 }) +}) + +describe('getFieldValue', () => { + test('normal path', async () => { + const form = createForm({ values: deepValues }) + form.registerField({ path: 'a' }) + form.registerField({ path: 'a.b' }) + form.registerField({ path: 'a.b.c' }) + form.registerField({ path: 'a.b.c.d' }) + form.registerField({ path: 'a.b.c.d.e' }) + + expect(form.getFieldValue('a')).toEqual(deepValues.a) + expect(form.getFieldValue('a.b')).toEqual(deepValues.a.b) + expect(form.getFieldValue('a.b.c')).toEqual(deepValues.a.b.c) + expect(form.getFieldValue('a.b.c.d')).toEqual(deepValues.a.b.c.d) + expect(form.getFieldValue('a.b.c.d.e')).toEqual(deepValues.a.b.c.d.e) + }) + + test('virtual path', async () => { + const form = createForm({ values: deepValues }) + form.registerField({ path: 'a' }) + form.registerVirtualField({ path: 'a.b' }) + form.registerField({ path: 'a.b.c' }) + form.registerVirtualField({ path: 'a.b.c.d' }) + form.registerField({ path: 'a.b.c.d.e' }) + + expect(form.getFieldValue('a')).toEqual(deepValues.a) + expect(form.getFieldValue('a.b')).toEqual(deepValues.a) + expect(form.getFieldValue('a.b.c')).toEqual(deepValues.a.c) + expect(form.getFieldValue('a.b.c.d')).toEqual(deepValues.a.c) + expect(form.getFieldValue('a.b.c.d.e')).toEqual(deepValues.a.c.e) + }) + + test('virtual path(head)', async () => { + const form = createForm({ values: deepValues }) + form.registerVirtualField({ path: 'a' }) + form.registerField({ path: 'a.b' }) + form.registerField({ path: 'a.b.c' }) + + expect(form.getFieldValue('a')).toEqual(deepValues) + expect(form.getFieldValue('a.b')).toEqual(deepValues.b) + expect(form.getFieldValue('a.b.c')).toEqual(deepValues.b.c) + }) +}) + +describe('registerField', () => { + test('basic', async () => { + const form = createForm({ values: { a: 1 } }) + form.registerField({ path: 'a' }) + form.registerField({ path: 'b' }) + form.registerField({ path: 'c' }) + form.registerField({ path: 'd', editable: false }) + form.registerField({ path: 'e', editable: true }) + expect(form.getFieldValue('a')).toEqual(1) + expect(form.getFieldValue('b')).toEqual(undefined) + expect(form.getFieldState('a', state => state.values)).toEqual([1]) + expect(form.getFieldState('b', state => state.values)).toEqual([undefined]) + expect(form.getFieldState('c', state => state.editable)).toEqual(true) + expect(form.getFieldState('d', state => state.editable)).toEqual(false) + expect(form.getFieldState('e', state => state.editable)).toEqual(true) + }) + + test('merge', async () => { + const form = createForm({ values: { a: 1, b: 2, c: 3, d: 4 }}) + form.registerField({ path: 'a' }) + form.registerField({ path: 'b', value: 'x' }) + form.registerField({ path: 'c', initialValue: 'y' }) + form.registerField({ path: 'd', initialValue: 'z', value: 's' }) + expect(form.getFieldValue('a')).toEqual(1) + expect(form.getFieldValue('b')).toEqual('x') + expect(form.getFieldValue('c')).toEqual(3) // false, 得到y + expect(form.getFieldValue('d')).toEqual('s') + }) +}) + +describe('registerVirtualField', () => { + test('basic', async () => { + const onChange = jest.fn() + const vprops = { hello: 'world' }; + const form = createForm({ values: { a: 1 } }) + form.registerVirtualField({ path: 'a' }) + form.registerVirtualField({ path: 'b', onChange }) + expect(onChange).toBeCalledTimes(1) // initialized + form.registerVirtualField({ path: 'c', props: vprops }) + expect(form.getFieldValue('a')).toEqual({ a: 1 }) // 根据dataPath法则,会拿到根路径的value + expect(form.getFieldState('a', state => state.values)).toEqual(undefined) // 不存在这个属性 + expect(form.getFieldState('c', state => state.props)).toEqual(vprops) + expect(form.getFieldState('b', state => state.display)).toEqual(true) + expect(form.getFieldState('b', state => state.visible)).toEqual(true) + form.setFieldState('b', state => state.display = false) + expect(form.getFieldState('b', state => state.display)).toEqual(false) + form.setFieldState('b', state => state.visible = false) + expect(form.getFieldState('b', state => state.visible)).toEqual(false) + expect(onChange).toBeCalledTimes(3) + }) +}) + +describe('createMutators', () => { + const arr = ['a', 'b'] + test('change', async () => { + const form = createForm() + const a = form.registerField({ path: 'a' }) + const mutators = form.createMutators(a) + expect(form.getFieldState('a', (state => ({ values: state.values, value: state.value })))).toEqual({ + value: undefined, + values: [undefined], + }) + mutators.change(1,2,3,4) + expect(form.getFieldState('a', (state => ({ values: state.values, value: state.value })))).toEqual({ + value: 1, + values: [1,2,3,4], + }) + }) + + test('focus', async () => { + const form = createForm() + const a = form.registerField({ path: 'a' }) + const mutators = form.createMutators(a) + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFieldState('a', (state => ({ active: state.active, visited: state.visited })))).toEqual({ + active: false, + visited: false, + }) + mutators.focus() + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFieldState('a', (state => ({ active: state.active, visited: state.visited })))).toEqual({ + active: true, + visited: true, + }) + }) + + test('blur', async () => { + const form = createForm() + const a = form.registerField({ path: 'a' }) + const mutators = form.createMutators(a) + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFieldState('a', (state => ({ active: state.active, visited: state.visited })))).toEqual({ + active: false, + visited: false, + }) + mutators.focus() + mutators.blur() + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFieldState('a', (state => ({ active: state.active, visited: state.visited })))).toEqual({ + active: false, + visited: true, + }) + }) + + test('push', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', value: [] }) + const mutators = form.createMutators(mm) + expect(form.getFieldValue('mm')).toEqual([]) + mutators.push({}) + expect(form.getFieldValue('mm')).toEqual([{}]) + }) + + test('pop', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', value: [{}] }) + const mutators = form.createMutators(mm) + expect(form.getFieldValue('mm')).toEqual([{}]) + mutators.pop() + expect(form.getFieldValue('mm')).toEqual([]) + }) + + test('insert', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', value: arr }) + const mutators = form.createMutators(mm) + expect(form.getFieldValue('mm')).toEqual(arr) + mutators.insert(1, 'x') + expect(form.getFieldValue('mm')).toEqual(['a','x','b']) + }) + + test('remove', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', value: arr }) + const mutators = form.createMutators(mm) + expect(form.getFieldValue('mm')).toEqual(arr) + mutators.remove(1) + expect(form.getFieldValue('mm')).toEqual(arr.slice(0, 1)) + }) + + test('exist', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', value: arr }) + const mutators = form.createMutators(mm) + expect(mutators.exist(1)).toEqual(true) + }) + + test('shift', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', value: arr }) + const mutators = form.createMutators(mm) + expect(form.getFieldValue('mm')).toEqual(arr) + mutators.shift() + expect(form.getFieldValue('mm')).toEqual(arr.slice(1)) + }) + + test('unshift', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', value: arr }) + const mutators = form.createMutators(mm) + expect(form.getFieldValue('mm')).toEqual(arr) + mutators.unshift('x') + expect(form.getFieldValue('mm')).toEqual(['x', ...arr]) + }) + + test('move', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', value: arr }) + const mutators = form.createMutators(mm) + expect(form.getFieldValue('mm')).toEqual(arr) + mutators.move(0, 1) + expect(form.getFieldValue('mm')).toEqual(arr.reverse()) + }) + + test('validate', async () => { + const form = createForm() + const mm = form.registerField({ path: 'mm', rules: [(v) => v === undefined ? ({ type: 'warning', message: 'warning msg' }) : undefined] }) + const mutators = form.createMutators(mm) + const result = await mutators.validate() + expect(result.errors).toEqual([]) + expect(result.warnings).toEqual([{ path: 'mm', messages: ['warning msg'] }]) + mutators.change(1) + const result2 = await mutators.validate() + expect(result2.errors).toEqual([]) + expect(result2.warnings).toEqual([]) + }) +}) + +describe('transformDataPath', () => { + test('normal path', async () => { + const form = createForm() + form.registerField({ path: 'a' }) + form.registerField({ path: 'a.b' }) + form.registerField({ path: 'a.b.c' }) + form.registerField({ path: 'a.b.c.d' }) + form.registerField({ path: 'a.b.c.d.e' }) + + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a')).toString()).toEqual('a') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b')).toString()).toEqual('a.b') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b.c')).toString()).toEqual('a.b.c') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b.c.d')).toString()).toEqual('a.b.c.d') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b.c.d.e')).toString()).toEqual('a.b.c.d.e') + }) + + test('virtual path', async () => { + const form = createForm() + form.registerField({ path: 'a' }) + form.registerVirtualField({ path: 'a.b' }) + form.registerField({ path: 'a.b.c' }) + form.registerVirtualField({ path: 'a.b.c.d' }) + form.registerField({ path: 'a.b.c.d.e' }) + + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a')).toString()).toEqual('a') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b')).toString()).toEqual('a') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b.c')).toString()).toEqual('a.c') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b.c.d')).toString()).toEqual('a.c') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b.c.d.e')).toString()).toEqual('a.c.e') + }) + + test('virtual path(head)', async () => { + const form = createForm() + form.registerVirtualField({ path: 'a' }) + form.registerField({ path: 'a.b' }) + form.registerField({ path: 'a.b.c' }) + + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a')).toString()).toEqual('') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b')).toString()).toEqual('b') + expect(form.unsafe_do_not_use_transform_data_path(new FormPath('a.b.c')).toString()).toEqual('b.c') + }) +}) + +describe('major sences', () => { + test('dynamic remove with intialValues', async () => { + const form = createForm({ + initialValues: { + aa: [{ aa: 123, bb: 321 }, { aa: 345, bb: 678 }] + } + }) + const aa = form.registerField({ path: 'aa' }) + form.registerField({ path: 'aa.0' }) + form.registerField({ path: 'aa.0.aa' }) + form.registerField({ path: 'aa.0.bb' }) + form.registerField({ path: 'aa.1' }) + form.registerField({ path: 'aa.1.aa' }) + form.registerField({ path: 'aa.1.bb' }) + form.setFieldState('aa.1.aa', state => { + state.value = 'change aa' + }) + const mutators = form.createMutators(aa) + const snapshot = form.getFormGraph() + expect(snapshot).toMatchSnapshot() + mutators.remove(0) + expect(form.getFormGraph()).toMatchSnapshot() + form.setFormGraph(snapshot) + expect(form.getFormGraph()).toMatchSnapshot() + }) + + test('nested dynamic remove', () => { + const form = createForm({ + useDirty: true + }) + const aa = form.registerField({ path: 'aa', value: [] }) + form.registerField({ path: 'aa.0' }) + form.registerField({ path: 'aa.0.aa' }) + form.registerField({ path: 'aa.0.bb' }) + form.registerField({ path: 'aa.1' }) + form.registerField({ path: 'aa.1.aa' }) + form.registerField({ path: 'aa.1.bb' }) + form.setFieldState('aa.1.aa', state => { + state.value = 'change aa' + }) + + const mutators = form.createMutators(aa) + const snapshot = form.getFormGraph() + expect(snapshot).toMatchSnapshot() + mutators.remove(0) + expect(form.getFormGraph()).toMatchSnapshot() + form.setFormGraph(snapshot) + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFormGraph()).toEqual(snapshot) + }) + + test('nested visible', () => { + const form = createForm() + form.registerField({ path: 'aa', value: {} }) + form.registerField({ path: 'aa.bb', initialValue: 123 }) + form.registerField({ path: 'aa.cc', initialValue: 222 }) + form.setFieldState('aa', state => state.visible = false) + expect(form.getFormState(state => state.values)).toEqual({}) + + expect(form.getFormGraph()).toMatchSnapshot() + form.setFieldState('aa.bb', state => state.value = '123') + expect(form.getFormGraph()).toMatchSnapshot() + + form.setFieldState('aa', state => state.visible = true) + expect(form.getFormGraph()).toMatchSnapshot() + }) + + test('deep nested visible(root)', () => { + const form = createForm() + form.registerField({ path: 'aa', value: {} }) + form.registerField({ path: 'aa.bb' }) + form.registerField({ path: 'aa.bb.cc', value: 123 }) + form.setFieldState('aa', state => state.visible = false) + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFormState(state => state.values)).toEqual({}) + }) + + test('deep nested visible(middle part)', () => { + const form = createForm() + form.registerField({ path: 'aa', value: {} }) + form.registerField({ path: 'aa.bb' }) + form.registerField({ path: 'aa.bb.cc', value: 123 }) + form.setFieldState('aa.bb', state => state.visible = false) + expect(form.getFormGraph()).toMatchSnapshot() + expect(form.getFormState(state => state.values)).toEqual({ aa: {} }) + }) + + test('deep nested visible with VirtualField', () => { + const form = createForm() + form.registerField({ path: 'aa', value: {} }) + form.registerVirtualField({ path: 'aa.bb' }) + form.registerField({ path: 'aa.bb.cc', value: 123 }) + form.setFieldState('aa', state => { + state.visible = false + }) + expect(form.getFormGraph()).toMatchSnapshot() + }) +}) diff --git a/packages/core/src/__tests__/lifecycle.spec.ts b/packages/core/src/__tests__/lifecycle.spec.ts new file mode 100644 index 00000000000..d93d33fc19d --- /dev/null +++ b/packages/core/src/__tests__/lifecycle.spec.ts @@ -0,0 +1,13 @@ +//import { FormHeart, FormLifeCycle, LifeCycleTypes } from '../shared/lifecycle' + +test('create lifecycle',()=>{ + //todo +}) + +test('create form heart',()=>{ + //todo +}) + +test('heart with lifecycle',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/core/src/__tests__/model.spec.ts b/packages/core/src/__tests__/model.spec.ts new file mode 100644 index 00000000000..bfd1a6dea3e --- /dev/null +++ b/packages/core/src/__tests__/model.spec.ts @@ -0,0 +1,71 @@ +//import { createStateModel } from '../shared/model' + +test('createStateModel', () => { + //todo +}) + +describe('proxy model', () => { + test('subscribe', () => { + //todo + }) + test('unsubscribe', () => { + //todo + }) + test('batch', () => { + //todo + }) + test('getState', () => { + //todo + }) + test('setState', () => { + //todo + }) + test('getSourceState', () => { + //todo + }) + test('setSourceState', () => { + //todo + }) + test('setState', () => { + //todo + }) + test('hasChanged', () => { + //todo + }) + test('getChanged', () => { + //todo + }) +}) + +describe('dirty model', () => { + test('subscribe', () => { + //todo + }) + test('unsubscribe', () => { + //todo + }) + test('batch', () => { + //todo + }) + test('getState', () => { + //todo + }) + test('setState', () => { + //todo + }) + test('getSourceState', () => { + //todo + }) + test('setSourceState', () => { + //todo + }) + test('setState', () => { + //todo + }) + test('hasChanged', () => { + //todo + }) + test('getChanged', () => { + //todo + }) +}) diff --git a/packages/core/src/__tests__/vfield.state.spec.ts b/packages/core/src/__tests__/vfield.state.spec.ts new file mode 100644 index 00000000000..4c1f7679617 --- /dev/null +++ b/packages/core/src/__tests__/vfield.state.spec.ts @@ -0,0 +1,32 @@ +//import { VirtualFieldState } from '../state/VirtualField' + +test('subscribe', () => { + //todo +}) +test('unsubscribe', () => { + //todo +}) +test('batch', () => { + //todo +}) +test('getState', () => { + //todo +}) +test('setState', () => { + //todo +}) +test('getSourceState', () => { + //todo +}) +test('setSourceState', () => { + //todo +}) +test('setState', () => { + //todo +}) +test('hasChanged', () => { + //todo +}) +test('getChanged', () => { + //todo +}) diff --git a/packages/core/src/field.ts b/packages/core/src/field.ts deleted file mode 100644 index 5a5b8dc8237..00000000000 --- a/packages/core/src/field.ts +++ /dev/null @@ -1,586 +0,0 @@ -import { - Broadcast, - publishFieldState, - isEqual, - clone, - isFn, - isBool, - toArr, - isStr, - hasRequired, - resolveFieldPath, - isEmpty -} from './utils' -import { - IFieldOptions, - IRuleDescription, - Path, - IField, - IFormPathMatcher, - IFieldState -} from '@uform/types' -import { Form } from './form' - -const filterSchema = (value: any, key: string): boolean => - key !== 'properties' && key !== 'items' - -export class Field implements IField { - public dirty: boolean - - public dirtyType: string - - public pristine: boolean - - public valid: boolean - - public invalid: boolean - - public visible: boolean - - public display: boolean - - public editable: boolean - - public loading: boolean - - public errors: string[] - - public effectErrors: string[] - - public name: string - - public value: any - - public hiddenFromParent: boolean - - public shownFromParent: boolean - - public initialValue: any - - public namePath: string[] - - public path: string[] - - public rules: IRuleDescription[] - - public required: boolean - - public props: any - - public lastValidateValue: any - - private context: Form - - private removed: boolean - - private destructed: boolean - - private alreadyHiddenBeforeUnmount: boolean - - private fieldbrd: Broadcast - - private unSubscribeOnChange: () => void - - constructor(context: Form, options: IFieldOptions) { - this.fieldbrd = new Broadcast() - this.context = context - this.dirty = false - this.pristine = true - this.valid = true - this.removed = false - this.invalid = false - this.visible = true - this.display = true - this.editable = true - this.destructed = false - this.loading = false - this.errors = [] - this.props = {} - this.effectErrors = [] - this.initialize(options) - } - - public initialize(options: IFieldOptions) { - const rules = this.getRulesFromProps(options.props) - this.value = !isEqual(this.value, options.value) - ? clone(options.value) - : this.value - this.name = !isEmpty(options.name) ? options.name : this.name || '' - this.namePath = resolveFieldPath(this.name) - - this.path = resolveFieldPath( - !isEmpty(options.path) ? options.path : this.path || [] - ) - this.rules = !isEmpty(rules) ? rules : this.rules - this.required = hasRequired(this.rules) - - if (isEmpty(options.props)) { - this.initialValue = !isEmpty(options.initialValue) - ? options.initialValue - : this.initialValue - } else { - this.initialValue = !isEqual(this.initialValue, options.initialValue) - ? options.initialValue - : !isEmpty(this.initialValue) - ? this.initialValue - : this.getInitialValueFromProps(options.props) - this.props = !isEmpty(this.props) - ? { ...this.props, ...clone(options.props) } - : clone(options.props) - const editable = this.getEditableFromProps(options.props) - this.editable = !isEmpty(editable) ? editable : this.getContextEditable() - } - - if (options.initialValue) { - this.lastValidateValue = options.initialValue - } - - if ( - this.pristine && - !isEmpty(this.initialValue) && - ((isEmpty(this.value) && this.visible) || - (this.removed && !this.shownFromParent)) - ) { - this.value = clone(this.initialValue) - this.context.setIn(this.name, this.value) - } - - this.mount() - - if (isFn(options.onChange)) { - this.onChange(options.onChange) - } - - this.context.syncUpdate(() => { - this.context.dispatchEffect('onFieldInit', this.publishState()) - }) - } - - public getInitialValueFromProps(props: any) { - if (props) { - if (!isEqual(this.initialValue, props.default)) { - return props.default - } - } - } - - public getContextEditable() { - return this.getEditable(this.context.editable) - } - - public getEditableFromProps(props: any) { - if (props) { - if (!isEmpty(props.editable)) { - return this.getEditable(props.editable) - } else { - if (props['x-props'] && !isEmpty(props['x-props'].editable)) { - return this.getEditable(props['x-props'].editable) - } - } - } - } - - public getRulesFromProps(props: any) { - if (props) { - const rules = toArr(props['x-rules']) - if (props.required && !rules.some(rule => rule.required)) { - rules.push({ required: true }) - } - return clone(rules) - } - return this.rules - } - - public getRequiredFromProps(props: any) { - if (!isEmpty(props.required)) { - return props.required - } - } - - public getEditable(editable: boolean | ((name: string) => boolean)): boolean { - if (isFn(editable)) { - return editable(this.name) - } - if (isBool(editable)) { - return editable - } - return this.editable - } - - public onChange(fn: (payload: any) => void) { - if (isFn(fn)) { - if (this.unSubscribeOnChange) { - this.unSubscribeOnChange() - } - fn(this.publishState()) - this.unSubscribeOnChange = this.subscribe(fn) - } - } - - public pathEqual(path: Path | IFormPathMatcher): boolean { - if (isStr(path)) { - if (path === this.name) { - return true - } - } - - path = resolveFieldPath(path) - - if (path.length === this.path.length) { - for (let i = 0; i < path.length; i++) { - if (path[i] !== this.path[i]) { - return false - } - } - return true - } else if (path.length === this.namePath.length) { - for (let i = 0; i < path.length; i++) { - if (path[i] !== this.namePath[i]) { - return false - } - } - return true - } - - return false - } - - public match(path: Path | IFormPathMatcher) { - if (isFn(path)) { - return path(this) - } - if (isStr(path)) { - if (path === this.name) { - return true - } - } - - path = resolveFieldPath(path) - - if (path.length === this.path.length) { - for (let i = 0; i < path.length; i++) { - if (path[i] !== this.path[i]) { - return false - } - } - return true - } else if (path.length === this.namePath.length) { - for (let i = 0; i < path.length; i++) { - if (path[i] !== this.namePath[i]) { - return false - } - } - return true - } - - return false - } - - public publishState() { - return publishFieldState(this) - } - - public syncContextValue() { - if (this.visible) { - const contextValue = this.context.getValue(this.name, true) - const contextInitialValue = this.context.getInitialValue( - this.name, - this.path - ) - if (!isEqual(this.value, contextValue)) { - this.value = contextValue - } - if (!isEqual(this.initialValue, contextInitialValue)) { - this.initialValue = contextInitialValue - } - } - } - - public subscribe(callback) { - return this.fieldbrd.subscribe(callback) - } - - public notify(force?: boolean) { - if (!this.dirty && !force) { - return - } - this.fieldbrd.notify(this.publishState()) - this.dirty = false - this.dirtyType = '' - } - - public unsubscribe() { - this.fieldbrd.unsubscribe() - } - - public changeProps(props: any, force?: boolean) { - const lastProps = this.props - if (isEmpty(props)) { - return - } - if (force || !isEqual(lastProps, props, filterSchema)) { - this.props = clone(props, filterSchema) - const editable = this.getEditableFromProps(this.props) - if (!isEmpty(editable)) { - this.editable = this.getEditableFromProps(this.props) - } - const rules = this.getRulesFromProps(this.props) - if (!isEmpty(rules)) { - this.rules = rules - } - this.dirty = true - this.notify() - } - } - - public changeEditable(editable: boolean | ((name: string) => boolean)): void { - if (!this.props || !isEmpty(this.props.editable)) { - return - } - if (this.props['x-props'] && !isEmpty(this.props['x-props'].editable)) { - return - } - this.editable = this.getEditable(editable) - this.dirty = true - this.notify() - } - - public mount() { - if (this.removed) { - if (!this.alreadyHiddenBeforeUnmount && !this.visible) { - this.visible = true - } - this.removed = false - this.context.dispatchEffect('onFieldChange', this.publishState()) - } - } - - public unmount() { - if (!this.visible) { - this.alreadyHiddenBeforeUnmount = true - } else { - this.alreadyHiddenBeforeUnmount = false - } - this.visible = false - this.removed = true - if (!this.context) { - return - } - if (!this.hiddenFromParent) { - this.context.deleteIn(this.name) - } - } - - public checkState(published = this.publishState()) { - if (!isEqual(published.value, this.value)) { - this.value = published.value - this.pristine = false - this.context.setIn(this.name, this.value) - this.context.updateChildrenValue(this) - this.dirtyType = 'value' - this.dirty = true - } - - if (!isEqual(published.initialValue, this.initialValue)) { - this.initialValue = published.initialValue - this.context.setInitialValueIn(this.name, this.value) - this.context.updateChildrenInitalValue(this) - this.dirtyType = 'initialValue' - this.dirty = true - } - - const editable = this.getEditable(published.editable) - if (!isEqual(editable, this.editable)) { - this.editable = editable - this.dirtyType = 'editable' - this.dirty = true - } else { - const prevEditable = this.getEditableFromProps(this.props) - const propsEditable = this.getEditableFromProps(published.props) - if ( - !isEmpty(propsEditable) && - !isEqual(propsEditable, this.editable) && - !isEqual(prevEditable, propsEditable) - ) { - this.editable = propsEditable - this.dirtyType = 'editable' - this.dirty = true - } - } - - published.errors = toArr(published.errors).filter(v => !!v) - - if (!isEqual(published.errors, this.effectErrors)) { - this.effectErrors = published.errors - this.valid = this.effectErrors.length > 0 && this.errors.length > 0 - this.invalid = !this.valid - this.dirtyType = 'errors' - this.dirty = true - } - if (!isEqual(published.rules, this.rules)) { - this.rules = published.rules - this.errors = [] - this.valid = true - if (hasRequired(this.rules)) { - this.required = true - published.required = true - } - this.invalid = false - this.dirtyType = 'rules' - this.dirty = true - } else { - const prePropsRules = this.getRulesFromProps(this.props) - const propsRules = this.getRulesFromProps(published.props) - if ( - !isEmpty(propsRules) && - !isEqual(prePropsRules, propsRules) && - !isEqual(propsRules, this.rules) - ) { - this.rules = propsRules - this.errors = [] - if (hasRequired(this.rules)) { - this.required = true - published.required = true - } - this.valid = true - this.invalid = false - this.dirtyType = 'rules' - this.dirty = true - } - } - if (!isEqual(published.required, this.required)) { - this.required = !!published.required - if (this.required) { - if (!hasRequired(this.rules)) { - this.rules = toArr(this.rules).concat({ - required: true - }) - this.errors = [] - this.valid = true - this.invalid = false - } - } else { - this.rules = toArr(this.rules).filter(rule => { - if (rule && rule.required) { - return false - } - return true - }) - this.errors = [] - this.valid = true - this.invalid = false - } - this.dirty = true - } else { - const propsRequired = this.getRequiredFromProps(published.props) - if (!isEmpty(propsRequired) && !isEqual(propsRequired, this.required)) { - this.required = !!propsRequired - this.errors = [] - if (this.required) { - if (!hasRequired(this.rules)) { - this.rules = toArr(this.rules).concat({ - required: true - }) - this.errors = [] - this.valid = true - this.invalid = false - } - } else { - this.rules = toArr(this.rules).filter(rule => { - if (rule && rule.required) { - return false - } - return true - }) - this.errors = [] - this.valid = true - this.invalid = false - } - this.dirty = true - } - } - - if (published.loading !== this.loading) { - this.loading = published.loading - this.dirtyType = 'loading' - this.dirty = true - } - - if (!isEqual(published.visible, this.visible)) { - this.visible = !!published.visible - if (this.visible) { - this.value = - this.value !== undefined ? this.value : clone(this.initialValue) - if (this.value !== undefined) { - this.context.setIn(this.name, this.value) - } - this.context.updateChildrenVisible(this, true) - } else { - this.context.deleteIn(this.name) - this.context.updateChildrenVisible(this, false) - } - this.dirtyType = 'visible' - this.dirty = true - } - - if (!isEqual(published.display, this.display)) { - this.display = !!published.display - this.context.updateChildrenDisplay(this, this.display) - this.dirtyType = 'display' - this.dirty = true - } - - if (!isEqual(published.props, this.props, filterSchema)) { - this.props = clone(published.props, filterSchema) - this.dirtyType = 'props' - this.dirty = true - } - if (this.editable === false) { - this.errors = [] - this.effectErrors = [] - } - } - - public updateState(reducer: (fieldStte: IFieldState) => void) { - if (!isFn(reducer)) { - return - } - if (this.removed) { - return - } - const published = { - name: this.name, - path: this.path, - props: clone(this.props, filterSchema), - value: clone(this.value), - initialValue: clone(this.initialValue), - valid: this.valid, - loading: this.loading, - editable: this.editable, - invalid: this.invalid, - pristine: this.pristine, - rules: clone(this.rules), - errors: clone(this.effectErrors), - visible: this.visible, - display: this.display, - required: this.required - } - reducer(published) - this.checkState(published) - } - - public destructor() { - if (this.destructed) { - return - } - this.destructed = true - if (this.value !== undefined) { - this.value = undefined - this.context.deleteIn(this.name) - } - this.context.updateChildrenVisible(this, false) - delete this.context - this.unsubscribe() - delete this.fieldbrd - } -} diff --git a/packages/core/src/form.ts b/packages/core/src/form.ts deleted file mode 100644 index 69928865b3c..00000000000 --- a/packages/core/src/form.ts +++ /dev/null @@ -1,847 +0,0 @@ -import { - Broadcast, - each, - reduce, - isEqual, - isFn, - isStr, - isArr, - setIn, - getIn, - deleteIn, - clone, - isEmpty, - toArr, - publishFormState, - raf, - caf, - isChildField, - getSchemaNodeFromPath, - BufferList, - defer -} from './utils' -import { Field } from './field' -import { runValidation, format } from '@uform/validator' -import { Subject } from 'rxjs/internal/Subject' -import { filter } from 'rxjs/internal/operators/filter' -import { FormPath } from './path' -import { - IFormOptions, - IFieldOptions, - IFieldState, - IField, - IFormPathMatcher, - IFormState, - ISchema, - Path, - IFieldMap, - ISubscribers -} from '@uform/types' - -type Editable = boolean | ((name: string) => boolean) - -const defaults = (opts: T): T => - ({ - initialValues: {}, - values: {}, - onSubmit: () => {}, - effects: () => {}, - ...opts - } as T) - -export class Form { - public editable: Editable - - private options: IFormOptions - - private publisher: Broadcast - - private state: IFormState - - private fields: IFieldMap - - private subscribes: ISubscribers - - private updateQueue: any[] - - private updateBuffer: BufferList - - private schema: ISchema - - private initialized: boolean - - private destructed: boolean - - private fieldSize: number - - private syncUpdateMode: boolean - - private updateRafId: any - - private rafValidateId: any - - private batchUpdateField: boolean - - private validating: boolean - - private traverse: (schema: ISchema) => ISchema - - constructor(opts: IFormOptions) { - this.getFieldState = this.getFieldState.bind(this) - this.getFormState = this.getFormState.bind(this) - this.options = defaults(opts) - this.publisher = new Broadcast() - this.initialized = false - this.state = {} as IFormState - this.fields = {} - this.subscribes = opts.subscribes || {} - this.updateQueue = [] - this.updateBuffer = new BufferList() - this.editable = opts.editable - this.schema = opts.schema || {} - this.traverse = opts.traverse - this.initialize({ - values: this.options.values, - initialValues: this.options.initialValues - }) - this.initializeEffects() - this.initialized = true - this.destructed = false - this.fieldSize = 0 - } - - public changeValues(values: any) { - const lastValues = this.state.values - const lastDirty = this.state.dirty - this.state.values = values || {} - this.state.dirty = - lastDirty || (this.initialized ? !isEqual(values, lastValues) : false) - this.updateFieldsValue() - } - - public changeEditable(editable: Editable) { - this.editable = editable - each(this.fields, field => { - field.changeEditable(editable) - }) - } - - public isDirtyValues(values: any) { - return !isEmpty(values) && !isEqual(this.state.values, values) - } - - public setFieldState = ( - path: Path | IFormPathMatcher, - callback: (fieldState: IFieldState) => void - ): Promise => { - if (this.destructed) { - return - } - return new Promise(resolve => { - if (isStr(path) || isArr(path) || isFn(path)) { - this.updateQueue.push({ path, callback, resolve }) - } - if (this.syncUpdateMode) { - this.updateFieldStateFromQueue() - return resolve() - } else if (this.updateQueue.length > 0) { - if (this.updateRafId !== undefined) { - caf(this.updateRafId) - } - this.updateRafId = raf(() => { - if (this.destructed) { - return - } - this.updateFieldStateFromQueue() - }) - } else { - return resolve() - } - }) - } - - public getFieldState( - path: Path | IFormPathMatcher, - callback: (fieldState: IFieldState) => void - ): void - public getFieldState(path: Path | IFormPathMatcher): IFieldState - - public getFieldState( - path: Path | IFormPathMatcher, - callback?: (fieldState: IFieldState) => void - ): any { - let field: IField - each(this.fields, innerField => { - if (innerField.match(path)) { - field = innerField - return false - } - }) - if (field) { - field.syncContextValue() - return isFn(callback) - ? callback(field.publishState()) - : field.publishState() - } - } - - public getFormState(callback: (formState: IFormState) => void): void - public getFormState(): IFormState - public getFormState(callback?: any): any { - return isFn(callback) ? callback(this.publishState()) : this.publishState() - } - - public setFormState = (callback: (formState: IFormState) => void) => { - if (this.destructed) { - return - } - if (!isFn(callback)) { - return - } - const published = this.publishState() - callback(published) - return Promise.resolve(this.checkState(published)) - } - - public registerField(name: string, options: IFieldOptions) { - const value = this.getValue(name) - const initialValue = this.getInitialValue(name, options.path) - const field = this.fields[name] - if (field) { - field.initialize({ - path: options.path, - onChange: options.onChange, - value, - initialValue - } as IFieldOptions) - this.asyncUpdate(() => { - this.updateFieldStateFromBuffer(field) - }) - } else { - this.fields[name] = new Field(this, { - name, - value, - path: options.path, - initialValue, - props: this.traverse ? this.traverse(options.props) : options.props - }) - const field = this.fields[name] - if (options.onChange) { - this.asyncUpdate(() => { - this.updateFieldStateFromBuffer(field) - field.onChange(options.onChange) - }) - this.dispatchEffect('onFieldChange', field.publishState()) - } - this.fieldSize++ - } - return this.fields[name] - } - - public setIn(name: string, value: any) { - setIn(this.state.values, name, value, path => { - return getSchemaNodeFromPath(this.schema, path) - }) - } - - public setInitialValueIn(name: string, value: any) { - setIn(this.state.initialValues, name, value) - } - - public setValue(name: string, value: any) { - const field = this.fields[name] - if (field) { - field.updateState(state => { - state.value = value - }) - field.pristine = false - if (field.dirty) { - field.notify() - this.dispatchEffect('onFieldInputChange', field.publishState()) - this.internalValidate(this.state.values).then(() => { - this.formNotify(field.publishState()) - }) - } - } - } - - public setErrors(name: string, errors: string[] | string, ...args: string[]) { - errors = toArr(errors) - const field = this.fields[name] - if (field) { - const lastErrors = field.errors - if (!isEqual(lastErrors, errors)) { - field.errors = errors.map(msg => format(msg, ...args)) - if (errors.length) { - field.invalid = true - field.valid = false - } else { - field.invalid = false - field.valid = true - } - field.dirty = true - field.notify() - } - } - } - - public updateChildrenValue(parent: Field) { - if (!parent.path || this.batchUpdateField) { - return - } - each(this.fields, (field, $name) => { - if (isChildField(field, parent)) { - const newValue = this.getValue($name) - if (!isEqual(field.value, newValue)) { - field.dirty = true - field.value = newValue - field.notify() - this.dispatchEffect('onFieldChange', field.publishState()) - } - } - }) - } - - public updateChildrenInitalValue(parent: Field) { - if (!parent.path) { - return - } - each(this.fields, (field, $name) => { - if (isChildField(field, parent)) { - const newValue = this.getInitialValue($name) - if (!isEqual(field.initialValue, newValue)) { - field.dirty = true - field.initialValue = newValue - } - } - }) - } - - public updateFieldInitialValue(): Promise { - if (this.state.dirty && this.initialized) { - each(this.fields, (field, name) => { - const newValue = this.getInitialValue(name) - field.initialValue = newValue - }) - } - return Promise.resolve() - } - - public updateFieldsValue(validate = true): Promise { - const { promise, resolve } = defer() - const update = () => { - const updateList = [] - this.batchUpdateField = true - each(this.fields, (field, name) => { - const newValue = this.getValue(name) - field.updateState(state => { - state.value = newValue - }) - if (field.dirty) { - updateList.push( - new Promise(resolve => { - raf(() => { - if (this.destructed) { - return - } - field.notify() - this.dispatchEffect('onFieldChange', field.publishState()) - resolve() - }) - }) - ) - } - }) - this.batchUpdateField = false - resolve(Promise.all(updateList)) - } - if (this.state.dirty && this.initialized) { - if (validate) { - this.internalValidate(this.state.values, true).then(() => { - this.formNotify() - update() - }) - } else { - update() - } - } - - return promise - } - - public updateChildrenVisible(parent: Field, visible?: boolean) { - if (!parent.path) { - return - } - each(this.fields, (field, $name) => { - if ($name === parent.name) { - return - } - if (isChildField(field, parent)) { - if (!visible) { - this.deleteIn($name) - } else { - const value = - field.value !== undefined ? field.value : clone(field.initialValue) - if (field.value !== undefined) { - this.setIn($name, value) - } - } - if (field.visible !== visible) { - if (visible) { - if (field.hiddenFromParent) { - field.visible = visible - field.hiddenFromParent = false - field.shownFromParent = true - field.dirty = true - } - } else { - field.visible = visible - field.hiddenFromParent = true - field.shownFromParent = false - field.dirty = true - } - } - } - }) - } - - public updateChildrenDisplay(parent: Field, display?: boolean) { - if (!parent.path) { - return - } - each(this.fields, (field, $name) => { - if ($name === parent.name) { - return - } - if (isChildField(field, parent)) { - if (field.display !== display) { - if (display) { - if (field.hiddenFromParent) { - field.display = display - field.hiddenFromParent = false - field.shownFromParent = true - field.dirty = true - } - } else { - field.display = display - field.hiddenFromParent = true - field.shownFromParent = false - field.dirty = true - } - } - } - }) - } - - public getInitialValue(name: string, path?: Path) { - const initialValue = getIn(this.state.initialValues, name) - let schema: ISchema - let schemaDefault: any - if (initialValue === undefined) { - schema = path ? getSchemaNodeFromPath(this.schema, path) : undefined - schemaDefault = schema && schema.default - if (schemaDefault !== undefined) { - this.setIn(name, schemaDefault) - } - } - return initialValue !== undefined ? initialValue : schemaDefault - } - - public getValue(name?: string, copy?: boolean) { - return copy - ? clone(getIn(this.state.values, name)) - : getIn(this.state.values, name) - } - - public deleteIn(name: string) { - deleteIn(this.state.values, name) - } - - public deleteInitialValues(name: string) { - deleteIn(this.state.initialValues, name) - } - - public reset(forceClear?: boolean, validate: boolean = true) { - each(this.fields, (field, name) => { - const value = this.getValue(name) - const initialValue = this.getInitialValue(name, field.path) - if (!validate) { - if (field.errors.length > 0) { - field.errors = [] - field.dirty = true - } - if (field.effectErrors.length > 0) { - field.effectErrors = [] - field.dirty = true - } - } - if (!isEmpty(value) || !isEmpty(initialValue)) { - field.updateState(state => { - state.value = forceClear ? undefined : initialValue - }) - field.pristine = true - } - if (field.dirty) { - field.notify() - this.formNotify(field.publishState()) - } - }) - if (!validate) { - const formState = this.publishState() - this.dispatchEffect('onFormReset', formState) - if (isFn(this.options.onReset)) { - this.options.onReset({ formState }) - } - } else { - this.internalValidate(this.state.values, true).then(() => { - this.formNotify() - raf(() => { - if (this.destructed) { - return - } - const formState = this.publishState() - this.dispatchEffect('onFormReset', formState) - if (isFn(this.options.onReset)) { - this.options.onReset({ formState }) - } - }) - }) - } - } - - public publishState() { - return publishFormState(this.state) - } - - public formNotify(fieldState?: IFieldState) { - const formState = this.publishState() - if (isFn(this.options.onFieldChange)) { - this.options.onFieldChange({ formState, fieldState }) - } - if (fieldState) { - this.dispatchEffect('onFieldChange', fieldState) - } - if (this.state.dirty) { - this.publisher.notify({ formState, fieldState }) - } - this.state.dirty = false - return formState - } - - public validate(): Promise { - this.validating = true - return this.internalValidate(this.state.values, true).then(() => { - return new Promise((resolve, reject) => { - this.formNotify() - raf(() => { - this.validating = false - if (this.destructed) { - return - } - if (this.state.valid) { - resolve(this.publishState()) - } else { - if (this.options.onValidateFailed) { - this.options.onValidateFailed(this.state.errors) - } - reject(this.state.errors) - } - }) - }) - }) - } - - public submit() { - if (this.validating) - return new Promise(resolve => { - resolve(this.publishState()) - }) - return this.validate().then((formState: IFormState) => { - this.dispatchEffect('onFormSubmit', formState) - if (isFn(this.options.onSubmit)) { - this.options.onSubmit({ formState }) - } - return formState - }) - } - - public subscribe(callback: (payload: any) => void) { - return this.publisher.subscribe(callback) - } - - public destructor() { - if (this.destructed) { - return - } - this.destructed = true - this.publisher.unsubscribe() - each(this.subscribes, effect => { - effect.unsubscribe() - }) - each(this.fields, (field, key) => { - field.destructor() - delete this.fields[key] - }) - this.fieldSize = 0 - delete this.fields - delete this.publisher - } - - public dispatchEffect = (eventName: string, ...args: any[]) => { - if (this.subscribes[eventName]) { - this.subscribes[eventName].next(...args) - } - } - - public syncUpdate(fn: () => void) { - if (isFn(fn)) { - this.syncUpdateMode = true - fn() - this.syncUpdateMode = false - } - } - - public initialize({ - initialValues = this.state.initialValues, - values = this.state.values - }) { - const lastValues = this.state.values - const lastDirty = this.state.dirty - const currentInitialValues = clone(initialValues) || {} - const currentValues = isEmpty(values) - ? clone(currentInitialValues) - : clone(values) || {} - this.state = { - valid: true, - invalid: false, - errors: [], - pristine: true, - initialValues: currentInitialValues, - values: currentValues, - dirty: - lastDirty || - (this.initialized ? !isEqual(currentValues, lastValues) : false) - } - if (this.options.onFormChange && !this.initialized) { - this.subscribe(this.options.onFormChange) - this.options.onFormChange({ - formState: this.publishState() - }) - } - this.updateFieldsValue(false) - } - - public selectEffect = ( - eventName: string, - eventFilter: string | IFormPathMatcher - ) => { - if (!this.subscribes[eventName]) { - this.subscribes[eventName] = new Subject() - } - if (isStr(eventFilter) || isFn(eventFilter)) { - const predicate = isStr(eventFilter) - ? FormPath.match(eventFilter as string) - : (eventFilter as IFormPathMatcher) - return this.subscribes[eventName].pipe(filter(predicate)) as Subject - } - return this.subscribes[eventName] - } - - private initializeEffects() { - const { effects } = this.options - if (isFn(effects)) { - effects(this.selectEffect, { - setFieldState: this.setFieldState, - getFieldState: this.getFieldState, - getFormState: this.getFormState, - setFormState: this.setFormState - }) - } - } - - private checkState(published: any): Promise { - if (!isEqual(this.state.values, published.values)) { - this.state.values = published.values - this.state.dirty = true - return this.updateFieldsValue() - } - if (!isEqual(this.state.initialValues, published.initialValues)) { - this.state.initialValues = published.initialValues - this.state.dirty = true - return this.updateFieldInitialValue() - } - - return Promise.resolve() - } - - private asyncUpdate(fn: () => void) { - if (isFn(fn)) { - if (this.syncUpdateMode) { - this.syncUpdateMode = false - fn() - this.syncUpdateMode = true - } else { - fn() - } - } - } - - private updateFieldStateFromQueue() { - const failed = {} - const rafIdMap = {} - each(this.updateQueue, ({ path, callback, resolve }, i) => { - each(this.fields, field => { - if (path && (isFn(path) || isArr(path) || isStr(path))) { - if (isFn(path) ? path(field) : field.pathEqual(path)) { - field.updateState(callback) - if (this.syncUpdateMode) { - field.dirty = false - } - if ((path as IFormPathMatcher).hasWildcard) { - this.updateBuffer.push( - (path as IFormPathMatcher).pattern, - callback, - { path, resolve } - ) - } - if (field.dirty) { - const dirtyType = field.dirtyType - field.notify() - if (rafIdMap[field.name]) { - caf(rafIdMap[field.name]) - } - rafIdMap[field.name] = raf(() => { - if (this.destructed) { - return - } - if (dirtyType === 'value') { - this.internalValidate().then(() => { - this.formNotify(field.publishState()) - }) - } else { - this.formNotify(field.publishState()) - } - }) - } - } else { - failed[i] = failed[i] || 0 - failed[i]++ - if (this.fieldSize <= failed[i]) { - if (isArr(path)) { - this.updateBuffer.push(path.join('.'), callback, { - path, - resolve - }) - } else if (isStr(path)) { - this.updateBuffer.push(path, callback, { path, resolve }) - } else if (isFn(path) && (path as IFormPathMatcher).pattern) { - this.updateBuffer.push( - (path as IFormPathMatcher).pattern, - callback, - { - path, - resolve - } - ) - } - } - } - } - }) - if (resolve && isFn(resolve)) { - resolve() - } - }) - this.updateQueue = [] - } - - private updateFieldStateFromBuffer(field: IField) { - const rafIdMap = {} - this.updateBuffer.forEach(({ path, values, key }) => { - if (isFn(path) ? path(field) : field.pathEqual(path)) { - values.forEach(callback => field.updateState(callback)) - if (this.syncUpdateMode) { - field.dirty = false - } - if (field.dirty) { - const dirtyType = field.dirtyType - field.notify() - if (rafIdMap[field.name]) { - caf(rafIdMap[field.name]) - } - rafIdMap[field.name] = raf(() => { - if (this.destructed) { - return - } - if (dirtyType === 'value') { - this.internalValidate().then(() => { - this.formNotify(field.publishState()) - }) - } else { - this.formNotify(field.publishState()) - } - }) - } - if (!path.hasWildcard) { - this.updateBuffer.remove(key) - } - } - }) - } - - private internalValidate( - values: any = this.state.values, - forceUpdate?: boolean - ) { - if (this.destructed) { - return - } - return new Promise(resolve => { - if (this.rafValidateId) { - caf(this.rafValidateId) - } - this.rafValidateId = raf(() => { - if (this.destructed) { - return resolve() - } - return runValidation( - values || this.state.values, - this.fields, - forceUpdate - ) - .then(response => { - const lastValid = this.state.valid - const newErrors = reduce( - response, - (buf, { name, errors }) => { - if (!errors.length) { - return buf - } else { - return buf.concat({ name, errors }) - } - }, - [] - ) - this.state.valid = newErrors.length === 0 - this.state.invalid = !this.state.valid - this.state.errors = newErrors - if (this.state.valid !== lastValid) { - this.state.dirty = true - } - const lastPristine = this.state.pristine - if (!isEqual(this.state.values, this.state.initialValues)) { - this.state.pristine = false - } else { - this.state.pristine = true - } - if (lastPristine !== this.state.pristine) { - this.state.dirty = true - } - return response - }) - .then(resolve) - }) - }) - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 64ed2905d4a..cd51939b6ae 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,94 +1,990 @@ -import { IFormOptions, ISchema } from '@uform/types' import { - setLocale as setValidationLocale, - setLanguage as setValidationLanguage -} from '@uform/validator' - -import { Form } from './form' -import { - calculateSchemaInitialValues, isFn, + isEqual, + toArr, + isNum, + isArr, + clone, + isValid, + FormPath, + FormPathPattern, each, - isEmpty, - clone -} from './utils' - -export * from './path' - -export const createForm = ({ - initialValues, - values, - onSubmit, - onReset, - schema, - onFormChange, - onFieldChange, - onFormWillInit, - subscribes, - editable, - effects, - onValidateFailed, - traverse -}: IFormOptions) => { - let fields = [] - let calculatedValues = calculateSchemaInitialValues( - schema, - isEmpty(values) ? clone(initialValues) : clone(values), - ({ name, path, schemaPath }, schema: ISchema, value: any) => { - fields.push({ name, path, schemaPath, schema, value }) - } - ) - - if (isEmpty(values)) { - initialValues = calculatedValues - } else { - values = calculatedValues - } - - const form = new Form({ - initialValues, - values, - onSubmit, - onReset, - subscribes, - onFormChange, - onFieldChange, - editable, - effects, - onValidateFailed, - schema, - traverse - }) + deprecate, + isObj +} from '@uform/shared' +import { + FormValidator, + setValidationLanguage, + setValidationLocale +} from '@uform/validator' +import { FormHeart } from './shared/lifecycle' +import { FormGraph } from './shared/graph' +import { FormState } from './state/form' +import { VirtualFieldState } from './state/virtual-field' +import { FieldState } from './state/field' +import { + IFormState, + IFieldState, + IVirtualFieldState, + IFormCreatorOptions, + IFieldStateProps, + IVirtualFieldStateProps, + IForm, + IFormSubmitResult, + IFormValidateResult, + IFormResetOptions, + IField, + IVirtualField, + isField, + FormHeartSubscriber, + LifeCycleTypes, + isVirtualField +} from './types' +export * from './shared/lifecycle' +export * from './types' - if (isFn(onFormWillInit)) { - onFormWillInit(form) +export const createForm = (options: IFormCreatorOptions = {}): IForm => { + function onGraphChange({ type, payload }) { + heart.notify(LifeCycleTypes.ON_FORM_GRAPH_CHANGE, graph) + if (type === 'GRAPH_NODE_WILL_UNMOUNT') { + validator.unregister(payload.path.toString()) + } + } + + function onFormChange(published: IFormState) { + heart.notify(LifeCycleTypes.ON_FORM_CHANGE, state) + const valuesChanged = state.hasChanged('values') + const initialValuesChanged = state.hasChanged('initialValues') + const unmountedChanged = state.hasChanged('unmounted') + const mountedChanged = state.hasChanged('mounted') + const initializedChanged = state.hasChanged('initialized') + const editableChanged = state.hasChanged('editable') + if (valuesChanged || initialValuesChanged) { + /** + * 影子更新:不会触发具体字段的onChange,如果不这样处理,会导致任何值变化都会导致整树rerender + */ + shadowUpdate(() => { + graph.eachChildren((field: IField | IVirtualField) => { + if (isField(field)) { + field.setState(state => { + if (state.visible) { + if (valuesChanged) { + const path = FormPath.parse(state.name) + const parent = graph.getLatestParent(path) + const parentValue = getFormValuesIn(parent.path) + const value = getFormValuesIn(state.name) + /** + * https://github.com/alibaba/uform/issues/267 dynamic remove node + */ + let removed = false + if ( + isArr(parentValue) && + !path.existIn(parentValue, parent.path) + ) { + if ( + !parent.path + .getNearestChildPathBy(path) + .existIn(parentValue, parent.path) + ) { + graph.remove(state.name) + removed = true + } + } else { + each(env.removeNodes, (_, name) => { + if (path.includes(name)) { + graph.remove(path) + delete env.removeNodes[name] + removed = true + } + }) + } + if (removed) return + if (!isEqual(value, state.value)) { + state.value = isValid(value) ? value : state.initialValue + } + } + if (initialValuesChanged) { + const initialValue = getFormInitialValuesIn(state.name) + if (!isEqual(initialValue, state.initialValue)) { + state.initialValue = initialValue + if (!isValid(state.value)) { + state.value = initialValue + } + } + } + } + }) + } + }) + }) + if (valuesChanged) { + heart.notify(LifeCycleTypes.ON_FORM_VALUES_CHANGE, state) + } + if (initialValuesChanged) { + heart.notify(LifeCycleTypes.ON_FORM_INITIAL_VALUES_CHANGE, state) + } + } + + if (editableChanged) { + graph.eachChildren((field: IField | IVirtualField) => { + if (isField(field)) { + field.setState(state => { + state.formEditable = published.editable + }) + } + }) + } + + if (unmountedChanged && published.unmounted) { + heart.notify(LifeCycleTypes.ON_FORM_UNMOUNT, state) + } + if (mountedChanged && published.mounted) { + heart.notify(LifeCycleTypes.ON_FORM_MOUNT, state) + } + if (initializedChanged) { + heart.notify(LifeCycleTypes.ON_FORM_INIT, state) + } } - fields = fields.map(({ name, schemaPath, schema }) => { - return form.registerField(name || schemaPath.join('.'), { - path: schemaPath, - props: schema + function onFieldChange({ onChange, field, path }) { + return (published: IFieldState) => { + const valueChanged = field.hasChanged('value') + const initialValueChanged = field.hasChanged('initialValue') + const visibleChanged = field.hasChanged('visible') + const displayChanged = field.hasChanged('display') + const unmountedChanged = field.hasChanged('unmounted') + const mountedChanged = field.hasChanged('mounted') + const initializedChanged = field.hasChanged('initialized') + const warningsChanged = field.hasChanged('warnings') + const errorsChanges = field.hasChanged('errors') + if (initializedChanged) { + heart.notify(LifeCycleTypes.ON_FIELD_INIT, field) + const isEmptyValue = !isValid(published.value) + const isEmptyInitialValue = !isValid(published.initialValue) + if (isEmptyValue || isEmptyInitialValue) { + field.setState((state: IFieldState) => { + if (isEmptyValue) state.value = getFormValuesIn(state.name) + if (isEmptyInitialValue) + state.initialValue = getFormInitialValuesIn(state.name) + }, true) + } + } + + if (visibleChanged) { + if (!published.visible) { + deleteFormValuesIn(path, true) + } else { + setFormValuesIn(path, published.value) + } + graph.eachChildren( + path, + childState => { + childState.setState((state: IFieldState) => { + state.visible = published.visible + }) + }, + false + ) + } + if (displayChanged) { + graph.eachChildren( + path, + childState => { + childState.setState((state: IFieldState) => { + state.display = published.display + }) + }, + false + ) + } + + if (unmountedChanged) { + if (published.unmounted) { + deleteFormValuesIn(path, true) + } else { + setFormValuesIn(path, published.value) + } + graph.eachChildren( + path, + childState => { + childState.setState((state: IFieldState) => { + state.unmounted = published.unmounted + }) + }, + false + ) + } + if (mountedChanged && published.mounted) { + heart.notify(LifeCycleTypes.ON_FIELD_MOUNT, field) + } + + if (valueChanged) { + setFormValuesIn(path, published.value) + heart.notify(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, field) + } + if (initialValueChanged) { + setFormInitialValuesIn(path, published.initialValue) + heart.notify(LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE, field) + } + + if (errorsChanges) { + syncFormMessages('errors', published.name, published.errors) + } + + if (warningsChanged) { + syncFormMessages('warnings', published.name, published.warnings) + } + + if (isFn(onChange) && (!env.shadowStage || env.leadingStage)) { + onChange(field) + } + heart.notify(LifeCycleTypes.ON_FIELD_CHANGE, field) + } + } + + //实时同步Form Messages + function syncFormMessages(type: string, path: string, messages: string[]) { + state.unsafe_setSourceState(state => { + let foundField = false + state[type] = state[type] || [] + state[type] = state[type].reduce((buf: any, item: any) => { + if (item.path === path) { + foundField = true + return messages.length ? buf.concat({ path, messages }) : buf + } else { + return buf.concat(item) + } + }, []) + if (!foundField && messages.length) { + state[type].push({ + path, + messages + }) + } + if (state.errors.length) { + state.invalid = true + state.valid = false + } else { + state.invalid = false + state.valid = true + } }) - }) + } + + function onVirtualFieldChange({ onChange, field, path }) { + return (published: IVirtualFieldState) => { + const visibleChanged = field.hasChanged('visible') + const displayChanged = field.hasChanged('display') + const unmountedChanged = field.hasChanged('unmounted') + const mountedChanged = field.hasChanged('mounted') + const initializedChnaged = field.hasChanged('initialized') + + if (initializedChnaged) { + heart.notify(LifeCycleTypes.ON_FIELD_INIT, field) + } + + if (visibleChanged) { + graph.eachChildren(path, childState => { + childState.setState((state: IVirtualFieldState) => { + state.visible = published.visible + }) + }) + } + + if (displayChanged) { + graph.eachChildren(path, childState => { + childState.setState((state: IVirtualFieldState) => { + state.display = published.display + }) + }) + } + + if (unmountedChanged) { + graph.eachChildren(path, childState => { + childState.setState((state: IVirtualFieldState) => { + state.unmounted = published.unmounted + }) + }) + } + if (mountedChanged && published.mounted) { + heart.notify(LifeCycleTypes.ON_FIELD_MOUNT, field) + } + if (isFn(onChange)) onChange(field) + heart.notify(LifeCycleTypes.ON_FIELD_CHANGE, field) + } + } - form.syncUpdate(() => { - form.dispatchEffect('onFormInit', form.publishState()) - each( - fields, - field => { - form.dispatchEffect('onFieldChange', field.publishState()) + function registerVirtualField({ + name, + path, + props, + onChange + }: IVirtualFieldStateProps): IVirtualField { + let nodePath = FormPath.parse(path || name) + let field: IVirtualField + const createField = () => { + let field: IVirtualField + field = new VirtualFieldState({ + nodePath, + useDirty: options.useDirty + }) + field.subscribe(onVirtualFieldChange({ onChange, field, path: nodePath })) + field.batch(() => { + batchRunTaskQueue(field, nodePath) + field.setState((state: IVirtualFieldState) => { + state.initialized = true + state.props = props + }) + }) + return field + } + if (graph.exist(nodePath)) { + field = graph.get(nodePath) + if (isField(field)) { + field = createField() + graph.replace(nodePath, field) + } + } else { + field = createField() + graph.appendNode(nodePath, field) + } + return field + } + + function registerField({ + path, + name, + value, + initialValue, + required, + rules, + editable, + onChange, + props + }: Exclude): IField { + let field: IField + let nodePath = FormPath.parse(path || name) + let dataPath = transformDataPath(path) + const createField = () => { + let field: IField + field = new FieldState({ + nodePath, + dataPath, + useDirty: options.useDirty + }) + heart.notify(LifeCycleTypes.ON_FIELD_WILL_INIT, field) + field.subscribe(onFieldChange({ onChange, field, path: nodePath })) + field.batch(() => { + batchRunTaskQueue(field, nodePath) + field.setState((state: IFieldState) => { + const formValue = getFormValuesIn(dataPath) + const formInitialValue = getFormInitialValuesIn(dataPath) + state.initialized = true + if (isValid(value)) { + // value > formValue > initialValue + state.value = value + } else { + state.value = isValid(formValue) ? formValue : initialValue + } + // initialValue > formInitialValue + state.initialValue = isValid(initialValue) + ? initialValue + : formInitialValue + + state.props = props + state.required = required + state.rules = rules as any + state.editable = editable + state.formEditable = options.editable + }) + }) + validator.register(nodePath, validate => { + const { value, rules, editable, visible, unmounted } = field.getState() + // 不需要校验的情况有: 非编辑态(editable),已销毁(unmounted), 逻辑上不可见(visible) + if (editable === false || visible === false || unmounted === true) + return validate(value, []) + clearTimeout((field as any).validateTimer) + ;(field as any).validateTimer = setTimeout(() => { + field.setState(state => { + state.validating = true + }) + }, 60) + validate(value, rules).then(({ errors, warnings }) => { + clearTimeout((field as any).validateTimer) + field.setState((state: IFieldState) => { + state.validating = false + state.ruleErrors = errors + state.ruleWarnings = warnings + }) + }) + }) + return field + } + if (graph.exist(nodePath)) { + field = graph.get(nodePath) + if (isVirtualField(nodePath)) { + field = createField() + graph.replace(nodePath, field) + } + } else { + field = createField() + graph.appendNode(nodePath, field) + } + return field + } + + function transformDataPath(path: FormPathPattern) { + const newPath = FormPath.getPath(path) + return newPath.reduce((path: FormPath, key: string, index: number) => { + const realPath = newPath.slice(0, index + 1) + const dataPath = path.concat(key) + const selected = graph.get(realPath) + if (isVirtualField(selected)) { + return path + } + return dataPath + }, FormPath.getPath('')) + } + + function setFormIn( + path: FormPathPattern, + key: string, + value: any, + silent?: boolean + ) { + state.setState(state => { + FormPath.setIn(state[key], path, value) + }, silent) + } + + function deleteFormIn(path: FormPathPattern, key: string, silent?: boolean) { + state.setState(state => { + FormPath.deleteIn(state[key], path) + }, silent) + } + + function deleteFormValuesIn(path: FormPathPattern, silent?: boolean) { + deleteFormIn(transformDataPath(path), 'values', silent) + } + + function setFormValuesIn( + path: FormPathPattern, + value?: any, + silent?: boolean + ) { + return setFormIn(transformDataPath(path), 'values', value, silent) + } + + function setFormInitialValuesIn( + path: FormPathPattern, + value?: any, + silent?: boolean + ) { + return setFormIn(transformDataPath(path), 'initialValues', value, silent) + } + + function getFormIn(path: FormPathPattern, key?: string) { + return state.getState(state => FormPath.getIn(state[key], path)) + } + + function getFormValuesIn(path: FormPathPattern) { + return getFormIn(transformDataPath(path), 'values') + } + + function getFormInitialValuesIn(path: FormPathPattern) { + return getFormIn(transformDataPath(path), 'initialValues') + } + + function createMutators(field: IField) { + if (!(field instanceof FieldState)) { + throw new Error( + 'The `createMutators` can only accept FieldState instance.' + ) + } + + function setValue(...values: any[]) { + field.setState((state: IFieldState) => { + state.value = values[0] + state.values = values + }) + heart.notify(LifeCycleTypes.ON_FIELD_INPUT_CHANGE, field) + heart.notify(LifeCycleTypes.ON_FORM_INPUT_CHANGE, state) + } + + function removeValue(key: string | number) { + const name = field.unsafe_getSourceState(state => state.name) + env.removeNodes[name] = true + field.setState((state: IFieldState) => { + state.value = undefined + state.values = [] + }, true) + deleteFormValuesIn(key ? FormPath.parse(name).concat(key) : name) + heart.notify(LifeCycleTypes.ON_FIELD_VALUE_CHANGE, field) + heart.notify(LifeCycleTypes.ON_FIELD_INPUT_CHANGE, field) + heart.notify(LifeCycleTypes.ON_FORM_INPUT_CHANGE, state) + heart.notify(LifeCycleTypes.ON_FIELD_CHANGE, field) + } + + function getValue() { + return field.unsafe_getSourceState(state => state.value) + } + return { + change(...values: any[]) { + setValue(...values) + return values[0] + }, + focus() { + field.setState((state: IFieldState) => { + state.active = true + state.visited = true + }) }, - true - ) + blur() { + field.setState((state: IFieldState) => { + state.active = false + }) + }, + push(value: any) { + const arr = toArr(getValue()).slice() + arr.push(value) + setValue(arr) + return arr + }, + pop() { + const arr = toArr(getValue()).slice() + arr.pop() + setValue(arr) + return arr + }, + insert(index: number, value: any) { + const arr = toArr(getValue()).slice() + arr.splice(index, 0, value) + setValue(arr) + return arr + }, + remove(index?: number | string) { + let val = getValue() + if (isNum(index) && isArr(val)) { + val = [].concat(val) + val.splice(index, 1) + setValue(val) + } else { + removeValue(index) + } + }, + exist(index?: number | string) { + const newPath = field.unsafe_getSourceState(state => + FormPath.parse(state.path) + ) + let val = getValue() + return (index !== undefined ? newPath.concat(index) : newPath).existIn( + val, + newPath + ) + }, + unshift(value: any) { + const arr = toArr(getValue()).slice() + arr.unshift(value) + setValue(arr) + return arr + }, + shift() { + const arr = toArr(getValue()).slice() + arr.shift() + setValue(arr) + return arr + }, + move($from: number, $to: number) { + const arr = toArr(getValue()).slice() + const item = arr[$from] + arr.splice($from, 1) + arr.splice($to, 0, item) + setValue(arr) + return arr + }, + moveUp(index: number) { + const arr = toArr(getValue()).slice() + const item = arr[index] + const len = arr.length + arr.splice(index, 1) + arr.splice(index - 1 < 0 ? len - 1 : index - 1, 0, item) + setValue(arr) + return arr + }, + moveDown(index: number) { + const arr = toArr(getValue()).slice() + const item = arr[index] + const len = arr.length + arr.splice(index, 1) + arr.splice(index + 1 > len ? 0 : index + 1, 0, item) + setValue(arr) + return arr + }, + validate() { + return validate(field.unsafe_getSourceState(state => state.path)) + } + } + } + + function clearErrors(pattern: FormPathPattern = '*') { + // 1. 指定路径或全部子路径清理 + graph.eachChildren('', pattern, field => { + if (isField(field)) { + field.setState(state => { + state.ruleErrors = [] + state.ruleWarnings = [] + state.effectErrors = [] + state.effectWarnings = [] + }) + } + }) + } + + async function reset({ + forceClear = false, + validate = true + }: IFormResetOptions = {}): Promise { + let result: Promise + leadingUpdate(() => { + graph.eachChildren(field => { + field.setState((state: IFieldState) => { + state.modified = false + state.ruleErrors = [] + state.ruleWarnings = [] + state.effectErrors = [] + state.effectWarnings = [] + // forceClear仅对设置initialValues的情况下有意义 + if (forceClear || !isValid(state.initialValue)) { + if (isArr(state.value)) { + state.value = [] + } else if (!isObj(state.value)) { + state.value = undefined + } + } else { + const value = clone(state.initialValue) + if (isArr(state.value)) { + if (isArr(value)) { + state.value = value + } else { + state.value = [] + } + } else if (isObj(state.value)) { + if (isObj(value)) { + state.value = value + } else { + state.value = {} + } + } else { + state.value = value + } + } + }) + }) + if (isFn(options.onReset)) { + options.onReset() + } + if (validate) { + result = formApi.validate() + } + }) + + return result + } + + async function submit( + onSubmit?: (values: IFormState['values']) => any | Promise + ): Promise { + // 重复提交,返回前一次的promise + if (state.getState(state => state.submitting)) return env.submittingTask + onSubmit = onSubmit || options.onSubmit + state.setState(state => { + state.submitting = true + }) + heart.notify(LifeCycleTypes.ON_FORM_SUBMIT_START, state) + env.submittingTask = validate() + .then(validated => { + const { errors } = validated + if (errors.length) { + state.setState(state => { + state.submitting = false + }) + heart.notify(LifeCycleTypes.ON_FORM_SUBMIT_END, state) + return Promise.reject(errors) + } + if (isFn(onSubmit)) { + return Promise.resolve( + onSubmit(state.getState(state => state.values)) + ).then(payload => ({ validated, payload })) + } + return { validated, payload: undefined } + }) + .then(response => { + const { + validated: { errors, warnings } + } = response + state.setState(state => { + state.submitting = false + }) + heart.notify(LifeCycleTypes.ON_FORM_SUBMIT_END, state) + if (errors.length) { + return Promise.reject(errors) + } + if (warnings.length) { + console.warn(warnings) + } + return response + }) + return env.submittingTask + } + + async function validate( + path?: FormPathPattern, + opts?: {} + ): Promise { + if (!state.getState(state => state.validating)) { + state.unsafe_setSourceState(state => { + state.validating = true + }) + // 渲染优化 + clearTimeout(env.validateTimer) + env.validateTimer = setTimeout(() => { + state.notify() + }, 60) + } + + heart.notify(LifeCycleTypes.ON_FORM_VALIDATE_START, state) + return validator.validate(path, opts).then(payload => { + clearTimeout(env.validateTimer) + state.setState(state => { + state.validating = false + }) + if (isFn(options.onValidateFailed)) { + options.onValidateFailed(payload) + } + heart.notify(LifeCycleTypes.ON_FORM_VALIDATE_END, state) + return payload + }) + } + + function setFormState(callback?: (state: IFormState) => any) { + leadingUpdate(() => { + state.setState(callback) + }) + } + + function getFormState(callback?: (state: IFormState) => any) { + return state.getState(callback) + } + + function batchRunTaskQueue(field: IField | IVirtualField, path: FormPath) { + env.taskQueue.forEach((task, index) => { + const { pattern, callbacks } = task + if (pattern.match(path)) { + callbacks.forEach(callback => { + field.setState(callback) + }) + if (!path.isWildMatchPattern && !path.isMatchPattern) { + env.taskQueue.splice(index, 1) + env.taskQueue.forEach(({ pattern }, index) => { + if (pattern.toString() === path.toString()) { + env.taskIndexes[path.toString()] = index + } + }) + } + } + }) + } + + function setFieldState( + path: FormPathPattern, + callback?: (state: IFieldState) => void + ) { + if (!isFn(callback)) return + let matchCount = 0 + let pattern = FormPath.getPath(path) + graph.select(pattern, field => { + field.setState(callback) + matchCount++ + }) + if (matchCount === 0 || pattern.isWildMatchPattern) { + let taskIndex = env.taskIndexes[pattern.toString()] + if (isValid(taskIndex)) { + if ( + !env.taskQueue[taskIndex].callbacks.some(fn => isEqual(fn, callback)) + ) { + env.taskQueue[taskIndex].callbacks.push(callback) + } + } else { + env.taskIndexes[pattern.toString()] = env.taskQueue.length + env.taskQueue.push({ + pattern, + callbacks: [callback] + }) + } + } + } + + function setFieldValue(path: FormPathPattern, value?: any) { + setFieldState(path, state => { + state.value = value + }) + } + + function getFieldValue(path?: FormPathPattern) { + return getFormValuesIn(path) + } + + function setFieldInitialValue(path?: FormPathPattern, value?: any) { + setFieldState(path, state => { + state.initialValue = value + }) + } + + function getFieldInitialValue(path?: FormPathPattern) { + return getFormInitialValuesIn(path) + } + + function getFieldState( + path: FormPathPattern, + callback?: (state: IFieldState) => any + ) { + const field = graph.select(path) + return field && field.getState(callback) + } + + function getFormGraph() { + return graph.map(node => { + return node.getState() + }) + } + + function setFormGraph(nodes: {}) { + each(nodes, (node: IFieldState | IVirtualFieldState, key) => { + let nodeState: any + if (graph.exist(key)) { + nodeState = graph.get(key) + nodeState.unsafe_setSourceState(state => { + Object.assign(state, node) + }) + } else { + if (node.displayName === 'VirtualFieldState') { + nodeState = registerVirtualField({ + path: key + }) + nodeState.unsafe_setSourceState(state => { + Object.assign(state, node) + }) + } else if (node.displayName === 'FieldState') { + nodeState = registerField({ + path: key + }) + nodeState.unsafe_setSourceState(state => { + Object.assign(state, node) + }) + } + } + if (nodeState) { + nodeState.notify(state.getState()) + } + }) + } + + function shadowUpdate(callback: () => void) { + env.shadowStage = true + if (isFn(callback)) { + callback() + } + env.shadowStage = false + } + + function leadingUpdate(callback: () => void) { + env.leadingStage = true + if (isFn(callback)) { + callback() + } + env.leadingStage = false + } + + const state = new FormState(options) + const validator = new FormValidator(options) + const graph = new FormGraph() + const formApi = { + submit, + reset, + clearErrors, + validate, + setFormState, + getFormState, + setFieldState, + getFieldState, + registerField, + registerVirtualField, + createMutators, + getFormGraph, + setFormGraph, + setFieldValue, + unsafe_do_not_use_transform_data_path: transformDataPath, //eslint-disable-line + setValue: deprecate( + setFieldValue, + 'setValue', + 'Please use the setFieldValue.' + ), + getFieldValue, + getValue: deprecate( + getFieldValue, + 'getValue', + 'Please use the getFieldValue.' + ), + setFieldInitialValue, + getFieldInitialValue, + getInitialValue: deprecate( + getFieldInitialValue, + 'getInitialValue', + 'Please use the getFieldInitialValue.' + ), + subscribe: (callback?: FormHeartSubscriber) => { + heart.subscribe(callback) + }, + unsubscribe: (callback?: FormHeartSubscriber) => { + heart.unsubscribe(callback) + }, + notify: (type: string, payload: T) => { + heart.notify(type, payload) + } + } + const heart = new FormHeart({ ...options, context: formApi }) + const env = { + validateTimer: null, + graphChangeTimer: null, + shadowStage: false, + leadingStage: false, + taskQueue: [], + taskIndexes: {}, + removeNodes: {}, + submittingTask: undefined + } + heart.notify(LifeCycleTypes.ON_FORM_WILL_INIT, state) + state.subscribe(onFormChange) + graph.appendNode('', state) + state.setState((state: IFormState) => { + state.initialized = true }) - return form + graph.subscribe(onGraphChange) + return formApi } +export const registerValidationFormats = FormValidator.registerFormats + +export const registerValidationRules = FormValidator.registerRules + +export const registerValidationMTEngine = FormValidator.registerMTEngine + export { - setValidationLocale, setValidationLanguage, - Form, - calculateSchemaInitialValues + setValidationLocale, + FormPath, + FormPathPattern, + FormGraph } export default createForm diff --git a/packages/core/src/path.ts b/packages/core/src/path.ts deleted file mode 100644 index 8a60d3dcd9f..00000000000 --- a/packages/core/src/path.ts +++ /dev/null @@ -1,81 +0,0 @@ -import createMatcher from 'dot-match' -import { resolveFieldPath, isStr, isFn, isArr, reduce } from './utils' -import { IFormPathMatcher } from '@uform/types' -type Filter = (payload: any) => boolean - -const matchWithFilter = (result: boolean, filter: Filter, payload: any) => { - if (isFn(filter)) { - return result && filter(payload) - } - return result -} - -const wildcardRE = /\*/ - -export const FormPath = { - match( - pattern: string, - isRealPath?: boolean | Filter, - filter?: Filter - ): IFormPathMatcher { - pattern = pattern + '' - const match = createMatcher(pattern) - if (isFn(isRealPath)) { - filter = isRealPath as Filter - isRealPath = false - } - const matcher = (payload: any) => { - if (payload && payload.fieldState) { - return matchWithFilter( - match( - resolveFieldPath( - isRealPath ? payload.fieldState.path : payload.fieldState.name - ) - ), - filter, - payload.fieldState - ) - } else if (payload && payload.name && payload.path) { - return matchWithFilter( - match(resolveFieldPath(isRealPath ? payload.path : payload.name)), - filter, - payload - ) - } else if (isStr(payload)) { - return matchWithFilter(match(resolveFieldPath(payload)), filter, { - name: payload - }) - } else if (isArr(payload)) { - return matchWithFilter(match(payload), filter, { path: payload }) - } - return false - } - - matcher.hasWildcard = wildcardRE.test(pattern) - matcher.pattern = pattern - - return matcher - }, - exclude(matcher: IFormPathMatcher) { - return (path: any): boolean => - isFn(matcher) - ? !matcher(path) - : isStr(matcher) - ? !FormPath.match(matcher)(path) - : false - }, - transform( - path: string, - regexp: RegExp, - calllback: (...args: string[]) => string - ) { - const args = reduce( - resolveFieldPath(path), - (buf: string[], key: string) => { - return new RegExp(regexp).test(key) ? buf.concat(key) : buf - }, - [] - ) - return calllback(...args) - } -} diff --git a/packages/core/src/shared/graph.ts b/packages/core/src/shared/graph.ts new file mode 100644 index 00000000000..246bec5ad89 --- /dev/null +++ b/packages/core/src/shared/graph.ts @@ -0,0 +1,270 @@ +import { + each, + reduce, + map, + isFn, + FormPath, + FormPathPattern +} from '@uform/shared' +import { Subscrible } from './subscrible' +import { + FormGraphNodeRef, + FormGraphMatcher, + FormGraphEacher, +} from '../types' + +export class FormGraph extends Subscrible<{ + type: string + payload: FormGraphNodeRef +}> { + private refrences: { + [key in string]: FormGraphNodeRef + } + + private nodes: { + [key in string]: NodeType + } + + private buffer: { + path: FormPath + ref: FormGraphNodeRef + latestParent?: { + ref: FormGraphNodeRef + path: FormPath + } + }[] + + constructor() { + super() + this.refrences = {} + this.nodes = {} + this.buffer = [] + } + + /** + * 模糊匹配API + * @param path + * @param matcher + */ + select(path: FormPathPattern, matcher?: FormGraphMatcher) { + const pattern = FormPath.parse(path) + if (!matcher) { + const node = this.get(pattern) + if (node) { + return node + } + } + for (let name in this.nodes) { + const node = this.nodes[name] + if (pattern.match(name)) { + if (isFn(matcher)) { + const result = matcher(node, FormPath.parse(name)) + if (result === false) { + return node + } + } else { + return node + } + } + } + } + + get(path: FormPathPattern) { + return this.nodes[FormPath.getPath(path).toString()] + } + + selectParent(path: FormPathPattern) { + return this.get(FormPath.getPath(path).parent()) + } + + selectChildren(path: FormPathPattern) { + const ref = this.refrences[FormPath.getPath(path).toString()] + if (ref && ref.children) { + return reduce( + ref.children, + (buf, path) => { + return buf.concat(this.get(path)).concat(this.selectChildren(path)) + }, + [] + ) + } + return [] + } + + exist(path: FormPathPattern) { + return !!this.get(FormPath.getPath(path)) + } + + /** + * 递归遍历所有children + * 支持模糊匹配 + */ + eachChildren(eacher: FormGraphEacher, recursion?: boolean): void + eachChildren( + path: FormPathPattern, + eacher: FormGraphEacher, + recursion?: boolean + ): void + eachChildren( + path: FormPathPattern, + selector: FormPathPattern, + eacher: FormGraphEacher, + recursion?: boolean + ): void + eachChildren( + path: any, + selector: any = true, + eacher: any = true, + recursion: any = true + ) { + if (isFn(path)) { + recursion = selector + eacher = path + path = '' + selector = '*' + } + if (isFn(selector)) { + recursion = eacher + eacher = selector + selector = '*' + } + const ref = this.refrences[FormPath.getPath(path).toString()] + if (ref && ref.children) { + return each(ref.children, path => { + if (isFn(eacher)) { + const node = this.get(path) + if (node && FormPath.parse(path).match(selector)) { + eacher(node, path) + if (recursion) { + this.eachChildren(path, selector, eacher, recursion) + } + } + } + }) + } + } + + /** + * 递归遍历所有parent + */ + eachParent(path: FormPathPattern, eacher: FormGraphEacher) { + const selfPath = FormPath.getPath(path) + const ref = this.refrences[selfPath.toString()] + if (isFn(eacher)) { + eacher(this.get(selfPath), selfPath) + if (ref.parent) { + this.eachParent(ref.parent.path, eacher) + } + } + } + + getLatestParent(path: FormPathPattern) { + const selfPath = FormPath.getPath(path) + const parentPath = FormPath.getPath(path).parent() + if (selfPath.toString() === parentPath.toString()) return undefined + if (this.refrences[parentPath.toString()]) + return { ref: this.refrences[parentPath.toString()], path: parentPath } + return this.getLatestParent(parentPath) + } + + map(mapper: (node: NodeType) => any) { + return map(this.nodes, mapper) + } + + reduce( + reducer: (buffer: T, node: NodeType, key: string) => T, + initial: T + ) { + return reduce(this.nodes, reducer, initial) + } + + appendNode(path: FormPathPattern, node: NodeType) { + const selfPath = FormPath.getPath(path) + const parentPath = selfPath.parent() + const parentRef = this.refrences[parentPath.toString()] + const selfRef: FormGraphNodeRef = { + path: selfPath, + children: [] + } + if (this.get(selfPath)) return + this.nodes[selfPath.toString()] = node + this.refrences[selfPath.toString()] = selfRef + if (parentRef) { + parentRef.children.push(selfPath) + selfRef.parent = parentRef + } else { + const latestParent = this.getLatestParent(selfPath) + if (latestParent) { + latestParent.ref.children.push(selfPath) + selfRef.parent = latestParent.ref + this.buffer.push({ + path: selfPath, + ref: selfRef, + latestParent: latestParent + }) + } + } + this.buffer.forEach(({ path, ref, latestParent }, index) => { + if ( + path.parent().match(selfPath) || + (selfPath.includes(latestParent.path) && + path.includes(selfPath) && + selfPath.toString() !== path.toString()) + ) { + selfRef.children.push(path) + ref.parent = selfRef + latestParent.ref.children.splice( + latestParent.ref.children.indexOf(path), + 1 + ) + this.buffer.splice(index, 1) + } + }) + this.notify({ + type: 'GRAPH_NODE_DID_MOUNT', + payload: selfRef + }) + } + + remove(path: FormPathPattern) { + const selfPath = FormPath.getPath(path) + const selfRef = this.refrences[selfPath.toString()] + if (!selfRef) return + this.notify({ + type: 'GRAPH_NODE_WILL_UNMOUNT', + payload: selfRef + }) + if (selfRef.children) { + selfRef.children.forEach(path => { + this.remove(path) + }) + } + this.buffer = this.buffer.filter(({ ref }) => { + return selfRef !== ref + }) + delete this.nodes[selfPath.toString()] + delete this.refrences[selfPath.toString()] + if (selfRef.parent) { + selfRef.parent.children.forEach((path, index) => { + if (path.match(selfPath)) { + selfRef.parent.children.splice(index, 0) + } + }) + } + } + + replace(path: FormPathPattern, node: NodeType) { + const selfPath = FormPath.getPath(path) + const selfRef = this.refrences[selfPath.toString()] + if (!selfRef) return + this.notify({ + type: 'GRAPH_NODE_WILL_UNMOUNT', + payload: selfRef + }) + this.nodes[selfPath.toString()] = node + this.notify({ + type: 'GRAPH_NODE_DID_MOUNT', + payload: selfRef + }) + } +} diff --git a/packages/core/src/shared/lifecycle.ts b/packages/core/src/shared/lifecycle.ts new file mode 100644 index 00000000000..0f899bd0d9a --- /dev/null +++ b/packages/core/src/shared/lifecycle.ts @@ -0,0 +1,113 @@ +import { isFn, isStr, isArr, isObj, each } from '@uform/shared' +import { + FormLifeCyclePayload, + FormLifeCycleHandler, + FormHeartSubscriber +} from '../types' + +export class FormLifeCycle { + private listener: FormLifeCyclePayload + + constructor(handler: FormLifeCycleHandler) + constructor(type: string, handler: FormLifeCycleHandler) + constructor(handlerMap: { [key: string]: FormLifeCycleHandler }) + constructor(...params: any[]) { + this.listener = this.buildListener(params) + } + buildListener(params: any[]) { + return function(payload: { type: string; payload: Payload }, ctx: any) { + for (let index = 0; index < params.length; index++) { + let item = params[index] + if (isFn(item)) { + item.call(this, payload, ctx) + } else if (isStr(item) && isFn(params[index + 1])) { + if (item === payload.type) { + params[index + 1].call(this, payload.payload, ctx) + } + index++ + } else if (isObj(item)) { + each(item, (handler, type) => { + if (isFn(handler) && isStr(type)) { + if (type === payload.type) { + handler.call(this, payload.payload, ctx) + return false + } + } + }) + } + } + } + } + + notify = (type: any, payload: Payload, ctx?: any) => { + if (isStr(type)) { + this.listener.call(ctx, { type, payload }, ctx) + } + } +} + +export class FormHeart { + private lifecycles: FormLifeCycle[] + + private context: Context + + private subscribers: FormHeartSubscriber[] + + constructor({ + lifecycles, + context + }: { + lifecycles?: FormLifeCycle[] + context?: Context + }) { + this.lifecycles = this.buildLifeCycles(lifecycles || []) + this.subscribers = [] + this.context = context + } + + buildLifeCycles(lifecycles: FormLifeCycle[]) { + return lifecycles.reduce((buf, item) => { + if (item instanceof FormLifeCycle) { + return buf.concat(item) + } else { + if (typeof item === 'object') { + this.context = item + return buf + } else if (isArr(item)) { + return this.buildLifeCycles(item) + } + return buf + } + }, []) + } + + unsubscribe = (callback?: FormHeartSubscriber) => { + if (isFn(callback)) { + this.subscribers = this.subscribers.filter( + fn => fn.toString() !== callback.toString() + ) + } else { + this.subscribers = [] + } + } + + subscribe = (callback?: FormHeartSubscriber) => { + if ( + isFn(callback) && + !this.subscribers.some(fn => fn.toString() === callback.toString()) + ) { + this.subscribers.push(callback) + } + } + + notify = (type: any, payload: P, context?: C) => { + if (isStr(type)) { + this.lifecycles.forEach(lifecycle => { + lifecycle.notify(type, payload, context || this.context) + }) + this.subscribers.forEach(callback => { + callback({ type, payload }) + }) + } + } +} diff --git a/packages/core/src/shared/model.ts b/packages/core/src/shared/model.ts new file mode 100644 index 00000000000..532fd5d8f90 --- /dev/null +++ b/packages/core/src/shared/model.ts @@ -0,0 +1,180 @@ +import { clone, isEqual, isFn, each, globalThisPolyfill } from '@uform/shared' +import produce, { Draft } from 'immer' +import { Subscrible } from './subscrible' +import { IStateModelFactory, StateDirtyMap, IModel, StateModel } from '../types' +const hasProxy = !!globalThisPolyfill.Proxy + +export const createStateModel = ( + Factory: IStateModelFactory +) => { + return class Model extends Subscrible + implements IModel { + public state: State & { displayName?: string } + public props: Props & DefaultProps & { useDirty?: boolean } + public displayName?: string + public dirtyNum: number + public dirtyMap: StateDirtyMap + public batching: boolean + public controller: StateModel + constructor(defaultProps: DefaultProps) { + super() + this.state = { ...Factory.defaultState } + this.props = { + ...Factory.defaultProps, + ...defaultProps + } + this.dirtyMap = {} + this.dirtyNum = 0 + this.batching = false + this.controller = new Factory(this.state, this.props) + this.displayName = Factory.displayName + this.state.displayName = this.displayName + } + + batch = (callback?: () => void) => { + this.batching = true + if (isFn(callback)) { + callback() + } + if (this.dirtyNum > 0) { + this.notify(this.getState()) + } + this.dirtyMap = {} + this.dirtyNum = 0 + this.batching = false + } + + getState = (callback?: (state: State) => any) => { + if (isFn(callback)) { + return callback(this.getState()) + } else { + if (!hasProxy || this.props.useDirty) { + if (isFn(this.controller.publishState)) { + return this.controller.publishState(this.state) + } + return clone(this.state) + } else { + return produce(this.state, () => {}) + } + } + } + + unsafe_getSourceState = (callback?: (state: State) => any) => { + if (isFn(callback)) { + return callback(this.state) + } else { + return this.state + } + } + + unsafe_setSourceState = (callback: (state: State) => void) => { + if (isFn(callback)) { + if (!hasProxy || this.props.useDirty) { + callback(this.state) + } else { + this.state = produce(this.state, callback) + } + } + } + + setState = ( + callback: (state: State | Draft) => State | void, + silent = false + ) => { + if (isFn(callback)) { + if (!hasProxy || this.props.useDirty) { + const draft = this.getState() + this.dirtyNum = 0 + this.dirtyMap = {} + callback(draft) + if (isFn(this.controller.computeState)) { + this.controller.computeState(draft, this.state) + } + const draftKeys = Object.keys(draft || {}) + const stateKeys = Object.keys(this.state || {}) + each( + draftKeys.length > stateKeys.length ? draft : this.state, + (value, key) => { + if (!isEqual(value, draft[key])) { + this.state[key] = draft[key] + this.dirtyMap[key] = true + this.dirtyNum++ + } + } + ) + if (isFn(this.controller.dirtyCheck)) { + const result = this.controller.dirtyCheck(this.dirtyMap) + if (result !== undefined) { + Object.assign(this.dirtyMap, result) + } + } + if (this.dirtyNum > 0 && !silent) { + if (this.batching) return + this.notify(this.getState()) + this.dirtyMap = {} + this.dirtyNum = 0 + } + } else { + this.dirtyNum = 0 + this.dirtyMap = {} + //用proxy解决脏检查计算属性问题 + this.state = produce( + this.state, + draft => { + callback(draft) + if (isFn(this.controller.computeState)) { + this.controller.computeState(draft, this.state) + } + }, + patches => { + patches.forEach(({ path, op, value }) => { + if (!this.dirtyMap[path[0]]) { + if (op === 'replace') { + if (!isEqual(this.state[path[0]], value)) { + this.dirtyMap[path[0]] = true + this.dirtyNum++ + } + } else { + this.dirtyMap[path[0]] = true + this.dirtyNum++ + } + } + }) + } + ) + if (isFn(this.controller.dirtyCheck)) { + const result = this.controller.dirtyCheck(this.dirtyMap) + if (result !== undefined) { + Object.assign(this.dirtyMap, result) + } + } + if (this.dirtyNum > 0 && !silent) { + if (this.batching) return + this.notify(this.getState()) + this.dirtyMap = {} + this.dirtyNum = 0 + } + } + } + } + + hasChanged = (key?: string) => + key ? this.dirtyMap[key] === true : this.dirtyNum > 0 + + getChanged = () => { + if (!hasProxy || this.props.useDirty) { + return clone(this.dirtyMap) + } else { + return this.dirtyMap + } + } + + watch = (key: string, callback?: (dirtys: StateDirtyMap) => any) => { + if (this.hasChanged(key)) { + if (isFn(callback)) { + callback(this.getChanged()) + } + } + } + } +} diff --git a/packages/core/src/shared/subscrible.ts b/packages/core/src/shared/subscrible.ts new file mode 100644 index 00000000000..d1acdde036a --- /dev/null +++ b/packages/core/src/shared/subscrible.ts @@ -0,0 +1,29 @@ +import { isFn, each } from '@uform/shared' +import { Subscriber } from '../types' + +export class Subscrible { + subscribers: Subscriber[] = [] + + subscribe = (callback?: Subscriber) => { + if ( + isFn(callback) && + !this.subscribers.some(fn => fn.toString() === callback.toString()) + ) { + this.subscribers.push(callback) + } + } + + unsubscribe = (callback?: Subscriber) => { + if (isFn(callback)) { + this.subscribers = this.subscribers.filter(fn => { + return fn.toString() !== callback.toString() + }) + } else { + this.subscribers.length = 0 + } + } + + notify = (payload?: Payload) => { + each(this.subscribers, callback => callback(payload)) + } +} diff --git a/packages/core/src/state/field.ts b/packages/core/src/state/field.ts new file mode 100644 index 00000000000..2e4f73ccf64 --- /dev/null +++ b/packages/core/src/state/field.ts @@ -0,0 +1,187 @@ +import { createStateModel } from '../shared/model' +import { clone, toArr, isValid, isEqual, FormPath, isFn } from '@uform/shared' +import { IFieldState, IFieldStateProps } from '../types' +/** + * 核心数据结构,描述表单字段的所有状态 + */ +export const FieldState = createStateModel( + class FieldState { + static displayName = 'FieldState' + static defaultState = { + name: '', + path: '', + initialized: false, + pristine: true, + valid: true, + modified: false, + touched: false, + active: false, + visited: false, + invalid: false, + visible: true, + display: true, + loading: false, + validating: false, + errors: [], + values: [], + ruleErrors: [], + ruleWarnings: [], + effectErrors: [], + warnings: [], + effectWarnings: [], + editable: true, + selfEditable: undefined, + formEditable: undefined, + value: undefined, + initialValue: undefined, + rules: [], + required: false, + mounted: false, + unmounted: false, + props: {} + } + + static defaultProps = { + path: '' + } + + private state: IFieldState + + private nodePath: FormPath + + private dataPath: FormPath + + constructor(state: IFieldState, props: IFieldStateProps) { + this.state = state + this.nodePath = FormPath.getPath(props.nodePath) + this.dataPath = FormPath.getPath(props.dataPath) + this.state.name = this.dataPath.entire + this.state.path = this.nodePath.entire + } + + readValues({ value, values }: IFieldStateProps) { + if (isValid(values)) { + values = toArr(values) + values[0] = value + } else { + if (isValid(value)) { + values = toArr(value) + } + } + return { + value, + values: toArr(values) + } + } + + readRules({ rules, required }: IFieldStateProps) { + let newRules = isValid(rules) ? clone(toArr(rules)) : this.state.rules + if (isValid(required)) { + if (required) { + if (!newRules.some(rule => rule && rule.required)) { + newRules.push({ required: true }) + } + } else { + newRules = newRules.filter(rule => rule && !rule.required) + } + } else { + required = newRules.some(rule => rule && rule.required) + } + return { + rules: newRules, + required + } + } + + computeState(draft: IFieldState, prevState: IFieldState) { + //如果是隐藏状态,则禁止修改值 + if (!draft.visible || draft.unmounted) { + draft.value = prevState.value + draft.initialValue = prevState.initialValue + } + //操作重定向 + if (!isEqual(draft.errors, prevState.errors)) { + draft.effectErrors = draft.errors + } + if (!isEqual(draft.warnings, prevState.warnings)) { + draft.effectWarnings = draft.warnings + } + //容错逻辑 + draft.rules = toArr(draft.rules).filter(v => !!v) + draft.effectWarnings = toArr(draft.effectWarnings).filter(v => !!v) + draft.effectErrors = toArr(draft.effectErrors).filter(v => !!v) + draft.ruleWarnings = toArr(draft.ruleWarnings).filter(v => !!v) + draft.ruleErrors = toArr(draft.ruleErrors).filter(v => !!v) + + draft.errors = draft.ruleErrors.concat(draft.effectErrors) + draft.warnings = draft.ruleWarnings.concat(draft.effectWarnings) + + if (!isEqual(draft.editable, prevState.editable)) { + draft.selfEditable = draft.editable + } + draft.editable = isValid(draft.selfEditable) + ? draft.selfEditable + : isValid(draft.formEditable) + ? isFn(draft.formEditable) + ? draft.formEditable(draft.name) + : draft.formEditable + : true + + const { value, values } = this.readValues(draft) + draft.value = value + draft.values = values + if ( + draft.initialized && + prevState.initialized && + !isEqual(draft.value, prevState.value) + ) { + draft.modified = true + } + if (isEqual(draft.value, draft.initialValue)) { + draft.pristine = true + } else { + draft.pristine = false + } + if (!isValid(draft.props)) { + draft.props = prevState.props + } + + if (draft.validating === true) { + draft.loading = true + } else if (draft.validating === false) { + draft.loading = false + } + // 以下几种情况清理错误和警告信息 + // 1. 字段设置为不可编辑 + // 2. 字段隐藏 + // 3. 字段被卸载 + if ( + (draft.selfEditable !== prevState.selfEditable && + !draft.selfEditable) || + draft.visible === false || + draft.unmounted === true + ) { + draft.errors = [] + draft.effectErrors = [] + draft.warnings = [] + draft.effectWarnings = [] + } + if (draft.mounted === true) { + draft.unmounted = false + } + if (draft.unmounted === true) { + draft.mounted = false + } + if (draft.errors.length) { + draft.invalid = true + draft.valid = false + } else { + draft.invalid = false + draft.valid = true + } + const { rules, required } = this.readRules(draft) + draft.rules = rules + draft.required = required + } + } +) diff --git a/packages/core/src/state/form.ts b/packages/core/src/state/form.ts new file mode 100644 index 00000000000..cfc05ade446 --- /dev/null +++ b/packages/core/src/state/form.ts @@ -0,0 +1,68 @@ +import { createStateModel } from '../shared/model' +import { toArr, clone, isEqual } from '@uform/shared' +import { IFormState, IFormStateProps } from '../types' +/** + * 核心数据结构,描述Form级别状态 + */ +export const FormState = createStateModel( + class FormState { + static displayName = 'FormState' + static defaultState = { + pristine: true, + valid: true, + invalid: false, + loading: false, + validating: false, + initialized: false, + submitting: false, + editable: true, + errors: [], + warnings: [], + values: {}, + initialValues: {}, + mounted: false, + unmounted: false, + props: {} + } + + static defaultProps = { + lifecycles: [] + } + + private state: IFormState + constructor(state: IFormState, props: IFormStateProps) { + this.state = state + this.state.initialValues = clone(props.initialValues || {}) + this.state.values = clone(props.values || props.initialValues || {}) + this.state.editable = props.editable + } + + computeState(draft: IFormState, prevState: IFormState) { + draft.errors = toArr(draft.errors).filter(v => !!v) + draft.warnings = toArr(draft.warnings).filter(v => !!v) + if (draft.errors.length) { + draft.invalid = true + draft.valid = false + } else { + draft.invalid = false + draft.valid = true + } + if (isEqual(draft.values, draft.initialValues)) { + draft.pristine = true + } else { + draft.pristine = false + } + if (draft.validating === true) { + draft.loading = true + } else if (draft.validating === false) { + draft.loading = false + } + if (draft.mounted === true) { + draft.unmounted = false + } + if (draft.unmounted === true) { + draft.mounted = false + } + } + } +) diff --git a/packages/core/src/state/virtual-field.ts b/packages/core/src/state/virtual-field.ts new file mode 100644 index 00000000000..0f7028bc52b --- /dev/null +++ b/packages/core/src/state/virtual-field.ts @@ -0,0 +1,56 @@ +import { createStateModel } from '../shared/model' +import { FormPath, isValid } from '@uform/shared' +import { IVirtualFieldState, IVirtualFieldStateProps } from '../types' + +/** + * UForm特有,描述一个虚拟字段, + * 它不占用数据空间,但是它拥有状态, + * 可以联动控制Field或者VirtualField的状态 + * 类似于现在UForm的Card之类的容器布局组件 + */ +export const VirtualFieldState = createStateModel< + IVirtualFieldState, + IVirtualFieldStateProps +>( + class VirtualFieldState { + static displayName = 'VirtualFieldState' + static defaultState = { + name: '', + path: '', + initialized: false, + visible: true, + display: true, + mounted: false, + unmounted: false, + props: {} + } + + static defaultProps = { + path: '', + props: {} + } + + private state: IVirtualFieldState + + private path: FormPath + + constructor(state: IVirtualFieldState, props: IVirtualFieldStateProps) { + this.state = state + this.path = FormPath.getPath(props.nodePath) + this.state.name = this.path.entire + this.state.path = this.path.entire + } + + computeState(draft: IVirtualFieldState, prevState: IVirtualFieldState) { + if (draft.mounted === true) { + draft.unmounted = false + } + if (!isValid(draft.props)) { + draft.props = prevState.props + } + if (draft.unmounted === true) { + draft.mounted = false + } + } + } +) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 00000000000..c1d00804800 --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,319 @@ +import { FormPath, FormPathPattern, isFn } from '@uform/shared' +import { ValidatePatternRules, ValidateNodeResult } from '@uform/validator' +import { FormLifeCycle } from './shared/lifecycle' +import { Draft } from 'immer' +import { Subscrible } from './shared/subscrible' + +export type FormLifeCycleHandler = (payload: T, context: any) => void + +export type FormHeartSubscriber = ({ + type, + payload +}: { + type: string + payload: any +}) => void + +export enum LifeCycleTypes { + /** + * Form LifeCycle + **/ + + ON_FORM_WILL_INIT = 'onFormWillInit', + ON_FORM_INIT = 'onFormInit', + ON_FORM_CHANGE = 'onFormChange', //ChangeType精准控制响应 + ON_FORM_MOUNT = 'onFormMount', + ON_FORM_UNMOUNT = 'onFormUnmount', + ON_FORM_SUBMIT = 'onFormSubmit', + ON_FORM_RESET = 'onFormReset', + ON_FORM_SUBMIT_START = 'onFormSubmitStart', + ON_FORM_SUBMIT_END = 'onFormSubmitEnd', + ON_FORM_VALUES_CHANGE = 'onFormValuesChange', + ON_FORM_INITIAL_VALUES_CHANGE = 'onFormInitialValueChange', + ON_FORM_VALIDATE_START = 'onFormValidateStart', + ON_FORM_VALIDATE_END = 'onFormValidateEnd', + ON_FORM_INPUT_CHANGE = 'onFormInputChange', + /** + * FormGraph LifeCycle + **/ + ON_FORM_GRAPH_CHANGE = 'onFormGraphChange', + + /** + * Field LifeCycle + **/ + + ON_FIELD_WILL_INIT = 'onFieldWillInit', + ON_FIELD_INIT = 'onFieldInit', + ON_FIELD_CHANGE = 'onFieldChange', + ON_FIELD_INPUT_CHANGE = 'onFieldInputChange', + ON_FIELD_VALUE_CHANGE = 'onFieldValueChange', + ON_FIELD_INITIAL_VALUE_CHANGE = 'onFieldInitialValueChange', + ON_FIELD_MOUNT = 'onFieldMount', + ON_FIELD_UNMOUNT = 'onFieldUnmount' +} + +export type FormGraphNodeMap = { + [key in string]: T +} + +export interface FormGraphVisitorOptions { + path: FormPath + exsist: boolean + append: (node: T) => void +} + +export type FormGraph = ( + node: T, + options: FormGraphVisitorOptions +) => void + +export interface FormGraphNodeRef { + parent?: FormGraphNodeRef + path: FormPath + children: FormPath[] +} + +export type FormGraphMatcher = (node: T, path: FormPath) => void | boolean + +export type FormGraphEacher = (node: T, path: FormPath) => void + +export type FormLifeCyclePayload = ( + params: { + type: string + payload: T + }, + context: any +) => void + +export type StateDirtyMap

    = { + [key in keyof P]?: boolean +} + +export interface StateModel

    { + publishState?: (state: P) => P + dirtyCheck?: (dirtys: StateDirtyMap

    ) => StateDirtyMap

    | void + computeState?: (state: Draft

    , preState?: P) => Draft

    | void +} + +export interface IStateModelFactory { + new (state: S, props: P): StateModel + defaultState?: S + defaultProps?: P + displayName?: string +} + +export interface IFieldState { + displayName?: string + name: string + path: string + initialized: boolean + pristine: boolean + valid: boolean + touched: boolean + invalid: boolean + visible: boolean + display: boolean + editable: boolean + selfEditable: boolean + formEditable: boolean | ((name: string) => boolean) + loading: boolean + modified: boolean + active: boolean + visited: boolean + validating: boolean + values: any[] + errors: string[] + effectErrors: string[] + ruleErrors: string[] + warnings: string[] + effectWarnings: string[] + ruleWarnings: string[] + value: any + initialValue: any + rules: ValidatePatternRules[] + required: boolean + mounted: boolean + unmounted: boolean + props: {} +} +export type FieldStateDirtyMap = StateDirtyMap + +export interface IFieldStateProps { + path?: FormPathPattern + nodePath?: FormPathPattern + dataPath?: FormPathPattern + name?: string + value?: any + values?: any[] + initialValue?: any + props?: {} + rules?: ValidatePatternRules[] + required?: boolean + editable?: boolean + onChange?: (fieldState: IField) => void +} + +export const isField = (target: any): target is IField => + target && target.displayName === 'FieldState' + +export const isFieldState = (target: any): target is IFieldState => + target && target.displayName === 'FieldState' + +export const isVirtualField = (target: any): target is IVirtualField => + target && target.displayName === 'VirtualFieldState' + +export const isVirtualFieldState = ( + target: any +): target is IVirtualFieldState => + target && target.displayName === 'VirtualFieldState' + +export const isStateModel = (target: any): target is IModel => + target && isFn(target.getState) + +export interface IFormState { + pristine: boolean + valid: boolean + invalid: boolean + loading: boolean + validating: boolean + submitting: boolean + initialized: boolean + editable: boolean | ((name: string) => boolean) + errors: string[] + warnings: string[] + values: {} + initialValues: {} + mounted: boolean + unmounted: boolean + props: {} +} + +export type FormStateDirtyMap = StateDirtyMap + +export interface IFormStateProps { + initialValues?: {} + values?: {} + lifecycles?: FormLifeCycle[] + editable?: boolean | ((name: string) => boolean) +} + +export interface IFormCreatorOptions extends IFormStateProps { + useDirty?: boolean + validateFirst?: boolean + editable?: boolean + onSubmit?: (values: IFormState['values']) => any | Promise + onReset?: () => void + onValidateFailed?: (validated: IFormValidateResult) => void +} + +export interface IVirtualFieldState { + name: string + path: string + displayName?: string + initialized: boolean + visible: boolean + display: boolean + mounted: boolean + unmounted: boolean + props: {} +} +export type VirtualFieldStateDirtyMap = StateDirtyMap + +export interface IVirtualFieldStateProps { + path?: FormPathPattern + nodePath?: FormPathPattern + name?: string + props?: {} + onChange?: (fieldState: IVirtualField) => void +} + +export type IFormValidateResult = ValidateNodeResult + +export interface IFormSubmitResult { + validated: IFormValidateResult + payload: any +} + +export interface IFormResetOptions { + forceClear?: boolean + validate?: boolean +} + +export interface IFormGraph { + [path: string]: IFormState | IFieldState | IVirtualFieldState +} + +export interface IMutators { + change(...values: any[]): any + focus(): void + blur(): void + push(value: any): any[] + pop(): any[] + insert(index: number, value: any): any[] + remove(index: number | string): any + unshift(value: any): any[] + shift(): any[] + move($from: number, $to: number): any + moveDown(index: number): any + moveUp(index: number): any + validate(): Promise + exist(index?: number | string): boolean +} + +export type Subscriber = (payload: S) => void + +export interface IModel extends Subscrible { + state: S + props: P + displayName?: string + dirtyNum: number + dirtyMap: StateDirtyMap + subscribers: Subscriber[] + batching: boolean + controller: StateModel + batch: (callback?: () => void) => void + getState: (callback?: (state: S) => any) => any + setState: (callback?: (state: S | Draft) => void, silent?: boolean) => void + unsafe_getSourceState: (callback?: (state: S) => any) => any + unsafe_setSourceState: (callback?: (state: S) => void) => void + hasChanged: (key?: string) => boolean + getChanged: () => StateDirtyMap +} + +export type IField = IModel + +export type IVirtualField = IModel + +export type IFormInternal = IModel + +export interface IForm { + submit( + onSubmit?: (values: IFormState['values']) => any | Promise + ): Promise + clearErrors: (pattern?: FormPathPattern) => void + reset(options?: IFormResetOptions): Promise + validate(path?: FormPathPattern, options?: {}): Promise + setFormState(callback?: (state: IFormState) => any): void + getFormState(callback?: (state: IFormState) => any): any + setFieldState( + path: FormPathPattern, + callback?: (state: IFieldState) => void + ): void + getFieldState( + path: FormPathPattern, + callback?: (state: IFieldState) => any + ): any + unsafe_do_not_use_transform_data_path(path: FormPathPattern): FormPathPattern //eslint-disable-line + registerField(props: IFieldStateProps): IField + registerVirtualField(props: IVirtualFieldStateProps): IVirtualField + createMutators(field: IField): IMutators + getFormGraph(): IFormGraph + setFormGraph(graph: IFormGraph): void + subscribe(callback?: FormHeartSubscriber): void + unsubscribe(callback?: FormHeartSubscriber): void + notify: (type: string, payload?: T) => void + setFieldValue(path?: FormPathPattern, value?: any): void + getFieldValue(path?: FormPathPattern): any + setFieldInitialValue(path?: FormPathPattern, value?: any): void + getFieldInitialValue(path?: FormPathPattern): any +} diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts deleted file mode 100644 index 837927628d7..00000000000 --- a/packages/core/src/utils.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { Path, IFormPathMatcher } from '@uform/types' -import { - isArr, - isStr, - getPathSegments, - isEqual, - toArr, - clone, - isFn, - globalThisPolyfill -} from '@uform/utils' - -export * from '@uform/utils' - -const self = globalThisPolyfill - -const compactScheduler = ([raf, caf, priority], fresh: boolean) => { - return [fresh ? callback => raf(priority, callback) : raf, caf] -} - -const getScheduler = () => { - if (!self.requestAnimationFrame) { - return [self.setTimeout, self.clearTimeout] - } - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const scheduler = require('scheduler') as any - return compactScheduler( - [ - scheduler.scheduleCallback || scheduler.unstable_scheduleCallback, - scheduler.cancelCallback || scheduler.unstable_cancelCallback, - scheduler.NormalPriority || scheduler.unstable_NormalPriority - ], - !!scheduler.unstable_requestPaint - ) - } catch (err) { - return [self.requestAnimationFrame, self.cancelAnimationFrame] - } -} - -export const [raf, caf] = getScheduler() - -export const resolveFieldPath = (path: Path | IFormPathMatcher): string[] => { - if (!isArr(path)) { - return isStr(path) ? resolveFieldPath(getPathSegments(path)) : undefined - } - return path.reduce((buf, key) => { - return buf.concat(getPathSegments(key)) - }, []) -} - -export const isChildField = (field, parent) => { - if (field && parent && field.path && parent.path) { - for (let i = 0; i < parent.path.length; i++) { - if (field.path[i] !== parent.path[i]) { - return false - } - } - return parent.path.length < field.path.length - } - return false -} - -export const hasRequired = rules => { - return toArr(rules).some(rule => { - return rule && rule.required - }) -} - -export const publishFormState = state => { - const { - values, - valid, - invalid, - initialValues, - errors, - pristine, - dirty - } = state - return { - values: clone(values), - valid, - invalid, - errors, - pristine, - dirty, - initialValues: clone(initialValues) - } -} - -export const publishFieldState = state => { - const { - value, - valid, - invalid, - errors, - visible, - display, - editable, - initialValue, - name, - path, - props, - effectErrors, - loading, - pristine, - required, - rules - } = state - return { - value: clone(value), - valid, - invalid, - editable, - visible, - display, - loading, - errors: errors.concat(effectErrors), - pristine, - initialValue: clone(initialValue), - name, - path, - props, - required, - rules - } -} - -export class BufferList { - public data = [] - public indexes = {} - public push(key: string, value: any, extra: any) { - if (!this.indexes[key]) { - const index = this.data.length - this.data.push({ ...extra, key, values: [value] }) - this.indexes[key] = index - } else { - const item = this.data[this.indexes[key]] - if (!item.values.some(callback => isEqual(callback, value))) { - item.values.push(value) - } - } - } - - public forEach(callback) { - for (let i = 0; i < this.data.length; i++) { - if (isFn(callback)) { - callback(this.data[i], this.data[i].key) - } - } - } - - public remove(key: string, value?: any) { - this.data = this.data.reduce((buf, item, index) => { - if (item.key === key) { - delete this.indexes[key] - return buf - } else { - this.indexes[key] = buf.length - return buf.concat(item) - } - }, []) - } -} diff --git a/packages/next/README.md b/packages/next/README.md index 3eb267bdc52..f7b7d78e396 100644 --- a/packages/next/README.md +++ b/packages/next/README.md @@ -1,2 +1,77 @@ # @uform/next -> UForm Fusion Next组件插件包 \ No newline at end of file + +> UForm Fusion Next 组件插件包 + +```jsx +import { + SchemaForm, + Field, + FormButtonGroup, + Submit, + FormEffectHooks, + createFormActions, + FormGridRow, + FormItemGrid, + FormGridCol, + FormPath, + FormLayout, + FormBlock, + FormCard, + FormTextBox, + FormStep +} from './src/index' +import { Button } from '@alifd/next' +import '@alifd/next/dist/next.css' + +const { onFormInit$ } = FormEffectHooks + +const actions = createFormActions() + +export default () => ( + { + console.log('提交') + console.log(values) + }} + actions={actions} + labelCol={{ span: 8 }} + wrapperCol={{ span: 6 }} + validateFirst + effects={({ setFieldState, getFormGraph }) => { + onFormInit$().subscribe(() => { + setFieldState('col1', state => { + state.visible = false + }) + }) + FormStep.effects(['step-1', 'step-2', 'step-3']) + }} + > + + + + + + + + + + + + 提交 + + + + +) +``` diff --git a/packages/next/build.ts b/packages/next/build.ts index 9ee3d0d8514..4fa9dc8329f 100644 --- a/packages/next/build.ts +++ b/packages/next/build.ts @@ -1,19 +1,19 @@ import { compile, getCompileConfig } from '../../scripts/build' import ts from 'typescript' -import tsImportPluginFactory from 'ts-import-plugin' +//import tsImportPluginFactory from 'ts-import-plugin' import glob from 'glob' -const transformer = tsImportPluginFactory({ - libraryName: '@alifd/next', - //style: importPath => `${importPath}/style`, -}) +// const transformer = tsImportPluginFactory({ +// libraryName: '@alifd/next', +// style: importPath => `${importPath}/style`, +// }) function buildESM() { const { fileNames, options } = getCompileConfig(require.resolve('./tsconfig.json'), { outDir: './esm', module: ts.ModuleKind.ESNext }) - compile(fileNames, options, { before: [transformer] }) + compile(fileNames, options) console.log('esm build successfully') } diff --git a/packages/next/package.json b/packages/next/package.json index 1309dc647e8..e40e33414ae 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@uform/next", - "version": "0.4.3", + "version": "0.4.0", "license": "MIT", "main": "lib", "module": "esm", @@ -21,24 +21,31 @@ }, "peerDependencies": { "@alifd/next": "^1.13.1", + "@types/classnames": "^2.2.9", + "@types/styled-components": "^4.1.19", "@babel/runtime": "^7.4.4", "react": ">=16.8.0", "react-dom": ">=16.8.0" }, "dependencies": { - "@uform/react": "^0.4.3", - "@uform/types": "^0.4.3", - "@uform/utils": "^0.4.3", + "@uform/react-schema-renderer": "^0.4.0", + "@uform/react-shared-components":"^0.4.0", + "@uform/types": "^0.4.0", + "@uform/shared": "^0.4.0", "classnames": "^2.2.6", "moveto": "^1.7.4", "react-stikky": "^0.1.15", - "styled-components": "^4.1.1" + "styled-components": "^4.1.1", + "react-eva": "^1.0.0", + "rxjs": "^6.5.1" }, "devDependencies": { - "@alifd/next": "^1.13.1" + "@alifd/next": "^1.13.1", + "@types/classnames": "^2.2.9", + "@types/styled-components": "^4.1.19" }, "publishConfig": { "access": "public" }, "gitHead": "4d068dad6183e8da294a4c899a158326c0b0b050" -} +} \ No newline at end of file diff --git a/packages/next/src/compat/Form.tsx b/packages/next/src/compat/Form.tsx new file mode 100644 index 00000000000..3ea7255f778 --- /dev/null +++ b/packages/next/src/compat/Form.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { Form } from '@alifd/next' +import { FormProps } from '@alifd/next/types/form' +import { IFormItemTopProps } from '../types' +import { FormItemProvider } from './context' +import { normalizeCol } from '../shared' + +export const CompatNextForm: React.FC< + FormProps & IFormItemTopProps +> = props => { + return ( + + + + ) +} diff --git a/packages/next/src/compat/FormItem.tsx b/packages/next/src/compat/FormItem.tsx new file mode 100644 index 00000000000..db71dc8d82a --- /dev/null +++ b/packages/next/src/compat/FormItem.tsx @@ -0,0 +1,109 @@ +import React, { createContext } from 'react' +import { Form } from '@alifd/next' +import { useFormItem } from './context' +import { IFormItemTopProps, ICompatItemProps } from '../types' +import { normalizeCol } from '../shared' +import { useContext } from 'react' + +const computeStatus = (props: ICompatItemProps) => { + if (props.loading) { + return 'loading' + } + if (props.invalid) { + return 'error' + } + //todo:暂时不支持 + // if (props.warnings.length) { + // return 'warning' + // } +} + +const computeHelp = (props: ICompatItemProps) => { + if (props.help) return props.help + const messages = [].concat(props.errors || [], props.warnings || []) + return messages.length ? messages : props.schema && props.schema.description +} + +const computeLabel = (props: ICompatItemProps) => { + if (props.label) return props.label + if (props.schema && props.schema.title) { + return props.schema.title + } +} + +const computeExtra = (props: ICompatItemProps) => { + if (props.extra) return props.extra +} + +function pickProps(obj: T, ...keys: (keyof T)[]): Pick { + const result: Pick = {} as any + for (let i = 0; i < keys.length; i++) { + if (obj[keys[i]] !== undefined) { + result[keys[i]] = obj[keys[i]] + } + } + return result +} + +const computeSchemaExtendProps = ( + props: ICompatItemProps +): IFormItemTopProps => { + if (props.schema) { + return pickProps( + { + ...props.schema.getExtendsItemProps(), + ...props.schema.getExtendsProps() + }, + 'prefix', + 'labelAlign', + 'labelTextAlign', + 'size', + 'labelCol', + 'wrapperCol' + ) + } +} + +const FormItemPropsContext = createContext({}) + +export const FormItemProps = ({ children, ...props }) => ( + + {children} + +) + +export const CompatNextFormItem: React.FC = props => { + const { + prefix, + labelAlign, + labelCol, + labelTextAlign, + wrapperCol, + size + } = useFormItem() + const formItemProps = useContext(FormItemPropsContext) + const help = computeHelp(props) + const label = computeLabel(props) + const status = computeStatus(props) + const extra = computeExtra(props) + const itemProps = computeSchemaExtendProps(props) + return ( + {extra}

    } + {...itemProps} + {...formItemProps} + > + {props.children} + + ) +} diff --git a/packages/next/src/compat/context.tsx b/packages/next/src/compat/context.tsx new file mode 100644 index 00000000000..e3f38383c73 --- /dev/null +++ b/packages/next/src/compat/context.tsx @@ -0,0 +1,35 @@ +import React, { createContext, useContext } from 'react' +import { IFormItemTopProps } from '../types' + +const FormItemContext = createContext({}) + +export const FormItemProvider: React.FC = ({ + children, + prefix, + size, + labelAlign, + labelCol, + inline, + labelTextAlign, + wrapperCol +}) => ( + + {children} + +) + +FormItemProvider.displayName = 'FormItemProvider' + +export const useFormItem = () => { + return useContext(FormItemContext) +} diff --git a/packages/next/src/compat/index.ts b/packages/next/src/compat/index.ts new file mode 100644 index 00000000000..1eca2fb65ed --- /dev/null +++ b/packages/next/src/compat/index.ts @@ -0,0 +1,10 @@ +import { + registerFormComponent, + registerFormItemComponent +} from '@uform/react-schema-renderer' +import { CompatNextForm } from './Form' +import { CompatNextFormItem } from './FormItem' + +registerFormComponent(CompatNextForm) + +registerFormItemComponent(CompatNextFormItem) diff --git a/packages/next/src/components/FormBlock.tsx b/packages/next/src/components/FormBlock.tsx new file mode 100644 index 00000000000..6468e5b03c6 --- /dev/null +++ b/packages/next/src/components/FormBlock.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import { createVirtualBox } from '@uform/react-schema-renderer' +import { Card } from '@alifd/next' +import { CardProps } from '@alifd/next/types/card' +import styled from 'styled-components' + +export const FormBlock = createVirtualBox( + 'block', + styled(({ children, className, ...props }) => { + return ( + + {children} + + ) + })` + margin-bottom: 0px; + .next-card-body { + padding-top: 20px; + padding-bottom: 0 !important; + } + &.next-card { + border: none; + padding-bottom: 15px; + } + ` +) diff --git a/packages/next/src/components/FormCard.tsx b/packages/next/src/components/FormCard.tsx new file mode 100644 index 00000000000..71cb0fbe68d --- /dev/null +++ b/packages/next/src/components/FormCard.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import { createVirtualBox } from '@uform/react-schema-renderer' +import { Card } from '@alifd/next' +import { CardProps } from '@alifd/next/types/card' +import styled from 'styled-components' + +export const FormCard = createVirtualBox( + 'card', + styled(({ children, className, ...props }) => { + return ( + + {children} + + ) + })` + margin-bottom: 30px; + .next-card-body { + padding-top: 30px; + padding-bottom: 0 !important; + } + ` +) diff --git a/packages/next/src/components/FormItemGrid.tsx b/packages/next/src/components/FormItemGrid.tsx new file mode 100644 index 00000000000..fbe30387150 --- /dev/null +++ b/packages/next/src/components/FormItemGrid.tsx @@ -0,0 +1,89 @@ +import React, { Fragment } from 'react' +import { CompatNextFormItem } from '../compat/FormItem' +import { createVirtualBox } from '@uform/react-schema-renderer' +import { toArr } from '@uform/shared' +import { Grid } from '@alifd/next' +import { RowProps, ColProps } from '@alifd/next/types/grid' +import { ItemProps } from '@alifd/next/types/form' +import { IFormItemGridProps, IItemProps } from '../types' +import { normalizeCol } from '../shared' +const { Row, Col } = Grid + +export const FormItemGrid = createVirtualBox( + 'grid', + props => { + const { + cols: rawCols, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + title, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + description, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + help, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + extra, + ...selfProps + } = props + const children = toArr(props.children) + const cols = toArr(rawCols).map(col => normalizeCol(col)) + const childNum = children.length + + if (cols.length < childNum) { + let offset: number = childNum - cols.length + let lastSpan: number = + 24 - + cols.reduce((buf, col) => { + return ( + buf + + Number(col.span ? col.span : 0) + + Number(col.offset ? col.offset : 0) + ) + }, 0) + for (let i = 0; i < offset; i++) { + cols.push({ span: Math.floor(lastSpan / offset) }) + } + } + const grids = ( + + {children.reduce((buf, child, key) => { + return child + ? buf.concat( + + {child} + + ) + : buf + }, [])} + + ) + + if (title) { + return ( + + {grids} + + ) + } + return {grids} + } +) + +export const FormGridRow = createVirtualBox( + 'grid-row', + props => { + const { title, description, extra } = props + const grids = {props.children} + if (title) { + return ( + + {grids} + + ) + } + return grids + } +) + +export const FormGridCol = createVirtualBox('grid-col', props => { + return {props.children} +}) diff --git a/packages/next/src/components/FormLayout.tsx b/packages/next/src/components/FormLayout.tsx new file mode 100644 index 00000000000..eb89eab6c43 --- /dev/null +++ b/packages/next/src/components/FormLayout.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { FormItemProvider, useFormItem } from '../compat/context' +import { createVirtualBox } from '@uform/react-schema-renderer' +import cls from 'classnames' +import { IFormItemTopProps } from '../types' + +export const FormLayout = createVirtualBox( + 'layout', + props => { + const { inline } = useFormItem() + const isInline = props.inline || inline + const children = + isInline || props.className || props.style ? ( +
    + {props.children} +
    + ) : ( + props.children + ) + return {children} + } +) diff --git a/packages/next/src/components/FormStep.tsx b/packages/next/src/components/FormStep.tsx new file mode 100644 index 00000000000..1de88681f35 --- /dev/null +++ b/packages/next/src/components/FormStep.tsx @@ -0,0 +1,98 @@ +import React, { useState, useMemo, useRef } from 'react' +import { + createControllerBox, + ISchemaVirtualFieldComponentProps, + FormPathPattern, + createEffectHook, + createFormActions +} from '@uform/react-schema-renderer' +import { toArr } from '@uform/shared' +import { Observable } from 'rxjs/internal/Observable' +import { Step } from '@alifd/next' +import { IFormStep } from '../types' + +enum StateMap { + ON_FORM_STEP_NEXT = 'onFormStepNext', + ON_FORM_STEP_PREVIOUS = 'onFormStepPrevious', + ON_FORM_STEP_GO_TO = 'onFormStepGoto', + ON_FORM_STEP_CURRENT_CHANGE = 'onFormStepCurrentChange' +} +const EffectHooks = { + onStepNext$: createEffectHook(StateMap.ON_FORM_STEP_NEXT), + onStepPrevious$: createEffectHook(StateMap.ON_FORM_STEP_PREVIOUS), + onStepGoto$: createEffectHook(StateMap.ON_FORM_STEP_GO_TO), + onStepCurrentChange$: createEffectHook<{ + value: number + preValue: number + }>(StateMap.ON_FORM_STEP_CURRENT_CHANGE) +} + +const effects = (relations: FormPathPattern[]) => { + const actions = createFormActions() + return EffectHooks.onStepCurrentChange$().subscribe(({ value }) => { + relations.forEach((pattern, index) => { + actions.setFieldState(pattern, (state: any) => { + state.display = index === value + }) + }) + }) +} + +type StepComponentExtendsProps = StateMap & { + getEffects: ( + relations: FormPathPattern[] + ) => Observable<{ + value: number + preValue: number + }> +} + +export const FormStep: React.FC & + StepComponentExtendsProps = createControllerBox( + 'step', + ({ props, form }: ISchemaVirtualFieldComponentProps) => { + const [current, setCurrent] = useState(0) + const ref = useRef(current) + const { dataSource, ...stepProps } = props['x-component-props'] || {} + const items = toArr(dataSource) + const update = (cur: number) => { + form.notify(StateMap.ON_FORM_STEP_CURRENT_CHANGE, { + value: cur, + preValue: current + }) + setCurrent(cur) + } + useMemo(() => { + update(ref.current) + form.subscribe(({ type, payload }) => { + switch (type) { + case StateMap.ON_FORM_STEP_NEXT: + update( + ref.current + 1 > items.length - 1 ? ref.current : ref.current + 1 + ) + break + case StateMap.ON_FORM_STEP_PREVIOUS: + update(ref.current - 1 < 0 ? ref.current : ref.current - 1) + break + case StateMap.ON_FORM_STEP_GO_TO: + if (!(payload < 0 || payload > items.length)) { + update(payload) + } + break + } + }) + }, []) + ref.current = current + return ( + + {items.map((props, key) => { + return + })} + + ) + } +) as any + +Object.assign(FormStep, StateMap, EffectHooks, { + effects +}) diff --git a/packages/next/src/components/FormTextBox.tsx b/packages/next/src/components/FormTextBox.tsx new file mode 100644 index 00000000000..45cf18233ee --- /dev/null +++ b/packages/next/src/components/FormTextBox.tsx @@ -0,0 +1,104 @@ +import React, { useRef, useEffect } from 'react' +import { createControllerBox } from '@uform/react-schema-renderer' +import { IFormTextBox } from '../types' +import { toArr } from '@uform/shared' +import { CompatNextFormItem } from '../compat/FormItem' +import styled from 'styled-components' + +export const FormTextBox = createControllerBox( + 'text-box', + styled(({ props, className, children }) => { + const { + title, + help, + text, + name, + extra, + gutter, + style, + ...componentProps + } = Object.assign( + { + gutter: 5 + }, + props['x-component-props'] + ) + const ref: React.RefObject = useRef() + const arrChildren = toArr(children) + const split = text.split('%s') + let index = 0 + useEffect(() => { + if (ref.current) { + const eles = ref.current.querySelectorAll('.text-box-field') + eles.forEach((el: HTMLElement) => { + const ctrl = el.querySelector('.next-form-item-control:first-child') + if (ctrl) { + el.style.width = getComputedStyle(ctrl).width + } + }) + } + }, []) + const newChildren = split.reduce((buf, item, key) => { + return buf.concat( + item ? ( +

    + {item} +

    + ) : null, + arrChildren[key] ? ( +
    + {arrChildren[key]} +
    + ) : null + ) + }, []) + + const textChildren = ( +
    + {newChildren} +
    + ) + + if (!title) return textChildren + + return ( + + {textChildren} + + ) + })` + display: flex; + .text-box-words:nth-child(1) { + margin-left: 0; + } + .text-box-field { + display: inline-block; + } + .next-form-item { + margin-bottom: 0 !important; + } + .preview-text { + text-align: center !important; + } + ` +) diff --git a/packages/next/src/components/button.tsx b/packages/next/src/components/button.tsx index 7cbc4eb1d69..9a3b67750b4 100644 --- a/packages/next/src/components/button.tsx +++ b/packages/next/src/components/button.tsx @@ -1,25 +1,77 @@ import React from 'react' -import { FormConsumer } from '@uform/react' +import { FormSpy, LifeCycleTypes } from '@uform/react-schema-renderer' import { Button } from '@alifd/next' -import { ISubmitProps } from '../type' +import { ButtonProps } from '@alifd/next/types/button' +import { ISubmitProps, IResetProps } from '../types' +import styled from 'styled-components' -export const Submit = ({ showLoading, ...props }: ISubmitProps) => { +export const TextButton: React.FC = props => ( + + ) +})` + border-radius: 50% !important; + padding: 0 !important; + min-width: 28px; + &.next-large { + min-width: 40px; + } + &.next-small { + min-width: 20px; + } + &.has-text { + .next-icon { + margin-right: 5px; + } + background: none !important; + border: none !important; + } +` + +export const Submit = ({ showLoading, onSubmit, ...props }: ISubmitProps) => { return ( - - {({ status }) => { + { + switch (action.type) { + case LifeCycleTypes.ON_FORM_SUBMIT_START: + return { + ...state, + submitting: true + } + case LifeCycleTypes.ON_FORM_SUBMIT_END: + return { + ...state, + submitting: false + } + default: + return state + } + }} + > + {({ state, form }) => { return ( ) }} - + ) } @@ -27,16 +79,24 @@ Submit.defaultProps = { showLoading: true } -export const Reset: React.FC> = props => { +export const Reset: React.FC = ({ + children, + forceClear, + validate, + ...props +}) => { return ( - - {({ reset }) => { + + {({ form }) => { return ( - ) }} - + ) } diff --git a/packages/next/src/components/formButtonGroup.tsx b/packages/next/src/components/formButtonGroup.tsx index 0af76eead94..2faea71c2a4 100644 --- a/packages/next/src/components/formButtonGroup.tsx +++ b/packages/next/src/components/formButtonGroup.tsx @@ -1,12 +1,10 @@ -import React, { Component } from 'react' -import ReactDOM from 'react-dom' +import React, { useRef } from 'react' import { Grid } from '@alifd/next' import Sticky from 'react-stikky' import cls from 'classnames' import styled from 'styled-components' - -import { FormLayoutConsumer } from '../form' -import { IFormButtonGroupProps } from '../type' +import { useFormItem } from '../compat/context' +import { IFormButtonGroupProps } from '../types' const { Row, Col } = Grid @@ -59,17 +57,22 @@ const isElementInViewport = ( ) } -export const FormButtonGroup: React.FC = styled( - class FormButtonGroup extends Component { - static defaultProps = { - span: 24, - zIndex: 100 - } - - private formNode: HTMLElement - - private renderChildren() { - const { children, itemStyle, offset, span } = this.props +export const FormButtonGroup = styled( + (props: React.PropsWithChildren) => { + const { + span, + zIndex, + sticky, + style, + offset, + className, + children, + triggerDistance, + itemStyle + } = props + const { inline } = useFormItem() + const selfRef = useRef() + const renderChildren = () => { return (
    @@ -84,69 +87,53 @@ export const FormButtonGroup: React.FC = styled(
    ) } - - getStickyBoundaryHandler(ref) { + const getStickyBoundaryHandler = () => { return () => { - this.formNode = this.formNode || ReactDOM.findDOMNode(ref.current) - if (this.formNode) { - return isElementInViewport(this.formNode.getBoundingClientRect()) + if (selfRef.current && selfRef.current.parentElement) { + const container = selfRef.current.parentElement + return isElementInViewport(container.getBoundingClientRect()) } return true } } - render() { - const { sticky, style, className } = this.props + const content = ( +
    + {renderChildren()} +
    + ) - const content = ( - - {({ inline } = {}) => ( -
    - {this.renderChildren()} + if (sticky) { + return ( +
    + +
    + {content}
    - )} - +
    +
    ) - - if (sticky) { - return ( -
    - - {({ FormRef } = {}) => { - if (!FormRef) return - return ( - -
    - {content} -
    -
    - ) - }} -
    -
    - ) - } - - return content } + + return content } -)` - ${props => +)` + ${(props: IFormButtonGroupProps) => props.align ? `display:flex;justify-content: ${getAlign(props.align)}` : ''} &.is-inline { display: inline-block; diff --git a/packages/next/src/components/index.ts b/packages/next/src/components/index.ts new file mode 100644 index 00000000000..1015143d9c7 --- /dev/null +++ b/packages/next/src/components/index.ts @@ -0,0 +1,8 @@ +export * from './Button' +export * from './FormButtonGroup' +export * from './FormLayout' +export * from './FormItemGrid' +export * from './FormCard' +export * from './FormBlock' +export * from './FormTextBox' +export * from './FormStep' diff --git a/packages/next/src/components/layout.tsx b/packages/next/src/components/layout.tsx deleted file mode 100644 index 08c19608d79..00000000000 --- a/packages/next/src/components/layout.tsx +++ /dev/null @@ -1,312 +0,0 @@ -import React, { Component, useEffect, useRef } from 'react' -import { createVirtualBox, createControllerBox } from '@uform/react' -import { toArr } from '@uform/utils' -import { Grid } from '@alifd/next' -import Card from '@alifd/next/lib/card' -import styled from 'styled-components' -import cls from 'classnames' -import { IFormItemGridProps, IFormItemProps } from '@uform/types' - -import { FormLayoutConsumer, FormItem, FormLayoutProvider } from '../form' -import { - IFormTextBox, - IFormCardProps, - IFormBlockProps, - IFormLayoutProps, - TFormCardOrFormBlockProps, - IFormItemGridProps as IFormItemGridPropsAlias -} from '../type' - -const { Row, Col } = Grid - -const normalizeCol = ( - col: { span: number; offset?: number } | number, - defaultValue: { span: number } = { span: 0 } -): { span: number; offset?: number } => { - if (!col) { - return defaultValue - } else { - return typeof col === 'object' ? col : { span: col } - } -} - -export const FormLayout = createVirtualBox( - 'layout', - ({ children, ...props }) => { - return ( - - {value => { - let newValue = { ...value, ...props } - let child = - newValue.inline || newValue.className || newValue.style ? ( -
    - {children} -
    - ) : ( - children - ) - return ( - {child} - ) - }} -
    - ) - } -) - -export const FormLayoutItem: React.FC = props => - React.createElement( - FormLayoutConsumer, - {}, - ({ - labelAlign, - labelTextAlign, - labelCol, - wrapperCol, - size, - autoAddColon - }) => { - return React.createElement( - FormItem, - { - labelAlign, - labelTextAlign, - labelCol, - wrapperCol, - autoAddColon, - size, - ...props - }, - props.children - ) - } - ) - -export const FormItemGrid = createVirtualBox( - 'grid', - class extends Component { - renderFormItem(children) { - const { title, help, name, extra, ...props } = this.props - return React.createElement( - FormLayoutItem, - { - label: title, - noMinHeight: true, - id: name, - extra, - help, - ...props - } as IFormItemGridProps, - children - ) - } - - renderGrid() { - const { - children: rawChildren, - cols: rawCols, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - title, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - description, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - help, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - extra, - ...props - } = this.props - - const children = toArr(rawChildren) - const cols = toArr(rawCols).map(col => normalizeCol(col)) - const childNum = children.length - - if (cols.length < childNum) { - let offset: number = childNum - cols.length - let lastSpan: number = - 24 - - cols.reduce((buf, col) => { - return ( - buf + - Number(col.span ? col.span : 0) + - Number(col.offset ? col.offset : 0) - ) - }, 0) - for (let i = 0; i < offset; i++) { - cols.push({ span: Math.floor(lastSpan / offset) }) - } - } - - return ( - - {children.reduce((buf, child, key) => { - return child - ? buf.concat( - - {child} - - ) - : buf - }, [])} - - ) - } - - render() { - const { title } = this.props - if (title) { - return this.renderFormItem(this.renderGrid()) - } else { - return this.renderGrid() - } - } - } -) - -export const FormCard = createVirtualBox( - 'card', - styled( - class extends Component { - static defaultProps = { - contentHeight: 'auto' - } - render() { - const { children, className, ...props } = this.props - return ( - - {children} - - ) - } - } - )` - margin-bottom: 30px; - .next-card-body { - padding-top: 30px; - padding-bottom: 0 !important; - } - ` -) - -export const FormBlock = createVirtualBox( - 'block', - styled( - class extends Component { - static defaultProps = { - contentHeight: 'auto' - } - render() { - const { children, className, ...props } = this.props - return ( - - {children} - - ) - } - } - )` - margin-bottom: 0px; - .next-card-body { - padding-top: 20px; - padding-bottom: 0 !important; - } - &.next-card { - border: none; - padding: 0 15px; - padding-bottom: 15px; - } - ` -) - -export const FormTextBox = createControllerBox( - 'text-box', - styled(({ children, schema, className }) => { - const { title, help, text, name, extra, ...props } = schema['x-props'] - const ref: React.RefObject = useRef() - const arrChildren = toArr(children) - const split = String(text).split('%s') - let index = 0 - useEffect(() => { - if (ref.current) { - const eles = ref.current.querySelectorAll('.text-box-field') - eles.forEach((el: HTMLElement) => { - const ctrl = el.querySelector( - '.next-form-item-control>*:not(.next-form-item-space)' - ) - if (ctrl) { - el.style.width = getComputedStyle(ctrl).width - } - }) - } - }, []) - const newChildren = split.reduce((buf, item, key) => { - return buf.concat( - item ? ( - - {item} - - ) : ( - undefined - ), - arrChildren[key] ? ( -
    - {arrChildren[key]} -
    - ) : ( - undefined - ) - ) - }, []) - - if (!title) - return ( -
    - {newChildren} -
    - ) - - return React.createElement( - FormLayoutItem, - { - label: title, - noMinHeight: true, - id: name, - extra, - help, - ...props - }, -
    - {newChildren} -
    - ) - })` - display: flex; - .text-box-words { - font-size: 12px; - line-height: 28px; - color: #333; - ${props => { - const { editable, schema } = props - const { gutter } = schema['x-props'] - if (!editable) { - return { - margin: 0 - } - } - return { - margin: `0 ${gutter === 0 || gutter ? gutter : 10}px` - } - }} - } - .text-box-words:nth-child(1) { - margin-left: 0; - } - .text-box-field { - display: inline-block; - } - ` -) diff --git a/packages/next/src/fields/array.tsx b/packages/next/src/fields/array.tsx deleted file mode 100644 index 3221814b335..00000000000 --- a/packages/next/src/fields/array.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from 'react' -import { registerFormField, createArrayField } from '@uform/react' -import { Button, Icon } from '@alifd/next' -import styled from 'styled-components' - -export const CircleButton = styled['div'].attrs({ className: 'cricle-btn' })` - ${props => - !props.hasText - ? `width:30px; - height:30px;` - : ''} - margin-right:10px; - border-radius: ${props => (!props.hasText ? '100px' : 'none')}; - border: ${props => (!props.hasText ? '1px solid #eee' : 'none')}; - margin-bottom:20px; - cursor:pointer; - display: flex; - align-items: center; - justify-content: center; - line-height: 1.3; - ${props => - !props.hasText - ? `&:hover{ - background:#f7f4f4; - }` - : ''} - .next-icon{ - display:flex; - align-items:'center' - } - .op-name{ - margin-left:3px; - } -} -` - -export const ArrayField = createArrayField({ - CircleButton, - TextButton: props => ( - - ), - AddIcon: () => , - RemoveIcon: () => ( - - ), - MoveDownIcon: () => ( - - ), - MoveUpIcon: () => ( - - ) -}) - -registerFormField( - 'array', - styled( - class extends ArrayField { - render() { - const { className, name, value, renderField } = this.props - const cls = this.getProps('className') - const style = this.getProps('style') - return ( -
    - {value.map((item, index) => { - return ( -
    -
    - {index + 1} -
    -
    {renderField(index)}
    -
    - {this.renderRemove(index, item)} - {this.renderMoveDown(index, item)} - {this.renderMoveUp(index)} -
    -
    - ) - })} - {this.renderEmpty()} - {value.length > 0 && this.renderAddition()} -
    - ) - } - } - )` - border: 1px solid #eee; - min-width: 400px; - .array-item { - padding: 20px; - padding-bottom: 0; - padding-top: 30px; - border-bottom: 1px solid #eee; - position: relative; - &:nth-child(even) { - background: #fafafa; - } - .array-index { - position: absolute; - top: 0; - left: 0; - display: block; - span { - position: absolute; - color: #fff; - z-index: 1; - font-size: 12px; - top: 3px; - left: 3px; - } - &::after { - content: ''; - display: block; - border-top: 20px solid transparent; - border-left: 20px solid transparent; - border-bottom: 20px solid transparent; - border-right: 20px solid #888; - transform: rotate(45deg); - position: absolute; - z-index: 0; - top: -20px; - left: -20px; - } - } - .array-item-operator { - display: flex; - border-top: 1px solid #eee; - padding-top: 20px; - } - } - .array-empty-wrapper { - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - &.disabled { - cursor: default; - } - .array-empty { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - margin: 20px; - img { - display: block; - height: 80px; - } - .next-btn-text { - color: #999; - .next-icon:before { - width: 16px !important; - font-size: 16px !important; - margin-right: 3px; - } - } - } - } - .array-item-wrapper { - margin: 0 -20px; - } - .array-item-addition { - padding: 10px 20px; - background: #fbfbfb; - .next-btn-text { - color: #888; - .next-icon:before { - width: 16px !important; - font-size: 16px !important; - margin-right: 3px; - } - } - } - ` -) diff --git a/packages/next/src/fields/boolean.ts b/packages/next/src/fields/boolean.ts index c742488cbbb..db32fcda84d 100644 --- a/packages/next/src/fields/boolean.ts +++ b/packages/next/src/fields/boolean.ts @@ -1,5 +1,5 @@ -import { connect, registerFormField } from '@uform/react' -import { acceptEnum, mapStyledProps } from '../utils' +import { connect, registerFormField } from '@uform/react-schema-renderer' +import { acceptEnum, mapStyledProps } from '../shared' import { Switch } from '@alifd/next' registerFormField( diff --git a/packages/next/src/fields/cards.tsx b/packages/next/src/fields/cards.tsx index 00b886f03e4..dc62ae1d27d 100644 --- a/packages/next/src/fields/cards.tsx +++ b/packages/next/src/fields/cards.tsx @@ -1,86 +1,131 @@ import React, { Fragment } from 'react' -import { registerFormField } from '@uform/react' -import { toArr } from '@uform/utils' -import { ArrayField } from './array' +import { Icon } from '@alifd/next' +import { + registerFormField, + ISchemaFieldComponentProps, + SchemaField +} from '@uform/react-schema-renderer' +import { toArr, isFn, FormPath } from '@uform/shared' +import { ArrayList } from '@uform/react-shared-components' +import { CircleButton, TextButton } from '../components/Button' import { Card } from '@alifd/next' import styled from 'styled-components' -const FormCardsField = styled( - class extends ArrayField { - renderOperations(item, index) { - return ( - - {this.renderRemove(index, item)} - {this.renderMoveDown(index, item)} - {this.renderMoveUp(index)} - {this.renderExtraOperations(index)} - - ) - } +const ArrayComponents = { + CircleButton, + TextButton, + AdditionIcon: () => , + RemoveIcon: () => ( + + ), + MoveDownIcon: () => ( + + ), + MoveUpIcon: () => ( + + ) +} - renderCardEmpty(title) { - return ( - - {this.renderEmpty()} - - ) +const FormCardsField = styled( + (props: ISchemaFieldComponentProps & { className: string }) => { + const { value, schema, className, editable, path, mutators } = props + const { + renderAddition, + renderRemove, + renderMoveDown, + renderMoveUp, + renderEmpty, + renderExtraOperations, + ...componentProps + } = schema.getExtendsComponentProps() || {} + const onAdd = () => { + const items = Array.isArray(schema.items) + ? schema.items[schema.items.length - 1] + : schema.items + mutators.push(items.getEmptyValue()) } - - render() { - const { value, className, schema, renderField } = this.props - /* eslint-disable @typescript-eslint/no-unused-vars */ - const { - title, - style, - className: cls, - renderAddition, - renderRemove, - renderEmpty, - renderMoveDown, - renderMoveUp, - renderOperations, - ...others - } = this.getProps() || ({} as any) - - /* eslint-enable @typescript-eslint/no-unused-vars */ - return ( -
    + {toArr(value).map((item, index) => { return ( - {index + 1}. {title || schema.title} + {index + 1}. {componentProps.title || schema.title} } - className="card-list" - key={index} - contentHeight="auto" - extra={this.renderOperations(item, index)} + extra={ + + mutators.remove(index)} + /> + mutators.moveDown(index)} + /> + mutators.moveUp(index)} + /> + {isFn(renderExtraOperations) + ? renderExtraOperations(index) + : renderExtraOperations} + + } > - {renderField(index)} + ) })} - {value.length === 0 && this.renderCardEmpty(title)} -
    - {value.length > 0 && this.renderAddition()} -
    -
    - ) - } + + {({ children }) => { + return ( + +
    {children}
    +
    + ) + }} +
    + + {({ children, isEmpty }) => { + if (!isEmpty) { + return ( +
    + {children} +
    + ) + } + }} +
    + +
    + ) } -)` +)` .next-card-body { padding-top: 30px; padding-bottom: 0 !important; @@ -94,42 +139,15 @@ const FormCardsField = styled( display: block; margin-bottom: 0px; background: #fff; - .array-empty-wrapper { - display: flex; - justify-content: center; - cursor: pointer; - margin-bottom: 0px; - &.disabled { - cursor: default; - } - .array-empty { - display: flex; - flex-direction: column; - margin-bottom: 20px; - img { - margin-bottom: 16px; - height: 85px; - } - .next-btn-text { - color: #888; - } - .next-icon:before { - width: 16px !important; - font-size: 16px !important; - margin-right: 5px; - } - } - } .next-card { box-shadow: none; } - .card-list { + .card-list-item { box-shadow: none; border: 1px solid #eee; } - - .array-item-addition { + .array-cards-addition { box-shadow: none; border: 1px solid #eee; transition: all 0.35s ease-in-out; @@ -137,22 +155,41 @@ const FormCardsField = styled( border: 1px solid #ccc; } } + .empty-wrapper { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 20px; + img { + margin-bottom: 16px; + height: 85px; + } + .next-btn-text { + color: #888; + } + .next-icon:before { + width: 16px !important; + font-size: 16px !important; + margin-right: 5px; + } + } } - .next-card.card-list { - margin-top: 20px; + .card-list-empty.card-list-item { + cursor: pointer; } - - .addition-wrapper .array-item-addition { + .next-card.card-list-item { margin-top: 20px; - margin-bottom: 3px; - } - .cricle-btn { - margin-bottom: 0; } + .next-card-extra { display: flex; + button { + margin-right: 8px; + } } - .array-item-addition { + .array-cards-addition { + margin-top: 20px; + margin-bottom: 3px; background: #fff; display: flex; cursor: pointer; @@ -168,9 +205,10 @@ const FormCardsField = styled( margin-right: 5px; } } - .card-list:first-child { + .card-list-item:first-child { margin-top: 0 !important; } ` registerFormField('cards', FormCardsField) +registerFormField('array', FormCardsField) diff --git a/packages/next/src/fields/checkbox.ts b/packages/next/src/fields/checkbox.ts index bdacbe002d6..0a449eca07c 100644 --- a/packages/next/src/fields/checkbox.ts +++ b/packages/next/src/fields/checkbox.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Checkbox } from '@alifd/next' -import { mapStyledProps, mapTextComponent } from '../utils' +import { mapStyledProps, mapTextComponent } from '../shared' const { Group: CheckboxGroup } = Checkbox diff --git a/packages/next/src/fields/date.tsx b/packages/next/src/fields/date.ts similarity index 90% rename from packages/next/src/fields/date.tsx rename to packages/next/src/fields/date.ts index 823ea9796ac..b0f75d94cf8 100644 --- a/packages/next/src/fields/date.tsx +++ b/packages/next/src/fields/date.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { DatePicker } from '@alifd/next' -import { mapStyledProps, mapTextComponent } from '../utils' +import { mapStyledProps, mapTextComponent } from '../shared' const { RangePicker, MonthPicker, YearPicker } = DatePicker diff --git a/packages/next/src/fields/index.ts b/packages/next/src/fields/index.ts new file mode 100644 index 00000000000..59c23266c82 --- /dev/null +++ b/packages/next/src/fields/index.ts @@ -0,0 +1,15 @@ +import './string' +import './number' +import './boolean' +import './date' +import './time' +import './range' +import './upload' +import './checkbox' +import './radio' +import './rating' +import './transfer' +import './cards' +import './table' +import './textarea' +import './password' \ No newline at end of file diff --git a/packages/next/src/fields/number.ts b/packages/next/src/fields/number.ts index a346a1cf4cc..ab126e4e095 100644 --- a/packages/next/src/fields/number.ts +++ b/packages/next/src/fields/number.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { NumberPicker } from '@alifd/next' -import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils' +import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' registerFormField( 'number', diff --git a/packages/next/src/fields/password.tsx b/packages/next/src/fields/password.tsx index 5664e29b93d..018287bd43b 100644 --- a/packages/next/src/fields/password.tsx +++ b/packages/next/src/fields/password.tsx @@ -1,154 +1,10 @@ import React from 'react' -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Input } from '@alifd/next' import { InputProps } from '@alifd/next/types/input' +import { PasswordStrength } from '@uform/react-shared-components' import styled from 'styled-components' -import { mapStyledProps } from '../utils' - -var isNum = function(c) { - return c >= 48 && c <= 57 -} -var isLower = function(c) { - return c >= 97 && c <= 122 -} -var isUpper = function(c) { - return c >= 65 && c <= 90 -} -var isSymbol = function(c) { - return !(isLower(c) || isUpper(c) || isNum(c)) -} -var isLetter = function(c) { - return isLower(c) || isUpper(c) -} - -const getStrength = val => { - if (!val) return 0 - let num = 0 - let lower = 0 - let upper = 0 - let symbol = 0 - let MNS = 0 - let rep = 0 - let repC = 0 - let consecutive = 0 - let sequential = 0 - const len = () => num + lower + upper + symbol - const callme = () => { - var re = num > 0 ? 1 : 0 - re += lower > 0 ? 1 : 0 - re += upper > 0 ? 1 : 0 - re += symbol > 0 ? 1 : 0 - if (re > 2 && len() >= 8) { - return re + 1 - } else { - return 0 - } - } - for (var i = 0; i < val.length; i++) { - var c = val.charCodeAt(i) - if (isNum(c)) { - num++ - if (i !== 0 && i !== val.length - 1) { - MNS++ - } - if (i > 0 && isNum(val.charCodeAt(i - 1))) { - consecutive++ - } - } else if (isLower(c)) { - lower++ - if (i > 0 && isLower(val.charCodeAt(i - 1))) { - consecutive++ - } - } else if (isUpper(c)) { - upper++ - if (i > 0 && isUpper(val.charCodeAt(i - 1))) { - consecutive++ - } - } else { - symbol++ - if (i !== 0 && i !== val.length - 1) { - MNS++ - } - } - var exists = false - for (var j = 0; j < val.length; j++) { - if (val[i] === val[j] && i !== j) { - exists = true - repC += Math.abs(val.length / (j - i)) - } - } - if (exists) { - rep++ - var unique = val.length - rep - repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC) - } - if (i > 1) { - var last1 = val.charCodeAt(i - 1) - var last2 = val.charCodeAt(i - 2) - if (isLetter(c)) { - if (isLetter(last1) && isLetter(last2)) { - var v = val.toLowerCase() - var vi = v.charCodeAt(i) - var vi1 = v.charCodeAt(i - 1) - var vi2 = v.charCodeAt(i - 2) - if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) { - sequential++ - } - } - } else if (isNum(c)) { - if (isNum(last1) && isNum(last2)) { - if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { - sequential++ - } - } - } else { - if (isSymbol(last1) && isSymbol(last2)) { - if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { - sequential++ - } - } - } - } - } - let sum = 0 - let length = len() - sum += 4 * length - if (lower > 0) { - sum += 2 * (length - lower) - } - if (upper > 0) { - sum += 2 * (length - upper) - } - if (num !== length) { - sum += 4 * num - } - sum += 6 * symbol - sum += 2 * MNS - sum += 2 * callme() - if (length === lower + upper) { - sum -= length - } - if (length === num) { - sum -= num - } - sum -= repC - sum -= 2 * consecutive - sum -= 3 * sequential - sum = sum < 0 ? 0 : sum - sum = sum > 100 ? 100 : sum - - if (sum >= 80) { - return 100 - } else if (sum >= 60) { - return 80 - } else if (sum >= 40) { - return 60 - } else if (sum >= 20) { - return 40 - } else { - return 20 - } -} +import { mapStyledProps } from '../shared' export interface IPasswordProps extends InputProps { checkStrength: boolean @@ -158,7 +14,6 @@ const Password = styled( class Password extends React.Component { state = { value: this.props.value || this.props.defaultValue, - strength: 0, eye: false } @@ -168,8 +23,7 @@ const Password = styled( this.props.value !== this.state.value ) { this.setState({ - value: this.props.value, - strength: getStrength(this.props.value) + value: this.props.value }) } } @@ -177,8 +31,7 @@ const Password = styled( onChangeHandler = (value, e) => { this.setState( { - value, - strength: getStrength(value) + value }, () => { if (this.props.onChange) { @@ -189,20 +42,25 @@ const Password = styled( } renderStrength() { - const { strength } = this.state return ( -
    -
    -
    -
    -
    -
    -
    + + {score => { + return ( +
    +
    +
    +
    +
    +
    +
    + ) + }} + ) } diff --git a/packages/next/src/fields/radio.ts b/packages/next/src/fields/radio.ts index 5057f842427..319a718f1f7 100644 --- a/packages/next/src/fields/radio.ts +++ b/packages/next/src/fields/radio.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Radio } from '@alifd/next' -import { mapStyledProps, mapTextComponent } from '../utils' +import { mapStyledProps, mapTextComponent } from '../shared' const { Group: RadioGroup } = Radio diff --git a/packages/next/src/fields/range.ts b/packages/next/src/fields/range.ts index e22a7780209..5398fa152ba 100644 --- a/packages/next/src/fields/range.ts +++ b/packages/next/src/fields/range.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Range } from '@alifd/next' -import { mapStyledProps } from '../utils' +import { mapStyledProps } from '../shared' registerFormField( 'range', diff --git a/packages/next/src/fields/rating.ts b/packages/next/src/fields/rating.ts index bfc8be1771d..6b0473a63a3 100644 --- a/packages/next/src/fields/rating.ts +++ b/packages/next/src/fields/rating.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Rating } from '@alifd/next' -import { mapStyledProps } from '../utils' +import { mapStyledProps } from '../shared' registerFormField( 'rating', diff --git a/packages/next/src/fields/string.ts b/packages/next/src/fields/string.ts index ab3e0bde5f6..e0139740ed1 100644 --- a/packages/next/src/fields/string.ts +++ b/packages/next/src/fields/string.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Input } from '@alifd/next' -import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils' +import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' registerFormField( 'string', diff --git a/packages/next/src/fields/table.tsx b/packages/next/src/fields/table.tsx index d090ce7adc4..131a09bfaa8 100644 --- a/packages/next/src/fields/table.tsx +++ b/packages/next/src/fields/table.tsx @@ -1,358 +1,181 @@ -import React, { Component } from 'react' -import { registerFormField } from '@uform/react' -import { isFn, toArr } from '@uform/utils' -import { ArrayField } from './array' +import React from 'react' +import { Icon } from '@alifd/next' +import { + registerFormField, + ISchemaFieldComponentProps, + SchemaField, + Schema +} from '@uform/react-schema-renderer' +import { toArr, isFn, isArr, FormPath } from '@uform/shared' +import { ArrayList } from '@uform/react-shared-components' +import { CircleButton, TextButton } from '../components/Button' +import { Table, Form } from '@alifd/next' import styled from 'styled-components' - -/** - * 轻量级Table,用next table实在太重了 - **/ - -export interface IColumnProps { - title?: string - dataIndex?: string - width?: string | number - cell: (item?: any, index?: number) => React.ReactElement +import { FormItemProps } from '../compat/FormItem' +const ArrayComponents = { + CircleButton, + TextButton, + AdditionIcon: () => , + RemoveIcon: () => ( + + ), + MoveDownIcon: () => ( + + ), + MoveUpIcon: () => ( + + ) } -export interface ITableProps { - className?: string - dataSource: any[] -} - -class Column extends Component { - static displayName = '@schema-table-column' - render() { - return this.props.children - } -} - -const Table = styled( - class Table extends Component { - renderCell({ record, col, rowIndex }) { - return ( -
    - {isFn(col.cell) - ? col.cell( - record ? record[col.dataIndex] : undefined, - rowIndex, - record +const FormTableField = styled( + (props: ISchemaFieldComponentProps & { className: string }) => { + const { value, schema, className, editable, path, mutators } = props + const { + renderAddition, + renderRemove, + renderMoveDown, + renderMoveUp, + renderEmpty, + renderExtraOperations, + operations, + ...componentProps + } = schema.getExtendsComponentProps() || {} + const onAdd = () => { + const items = Array.isArray(schema.items) + ? schema.items[schema.items.length - 1] + : schema.items + mutators.push(items.getEmptyValue()) + } + const renderColumns = (items: Schema) => { + return items.mapProperties((props, key) => { + const itemProps = { + ...props.getExtendsItemProps(), + ...props.getExtendsProps() + } + return ( + { + return ( + + + ) - : record - ? record[col.dataIndex] - : undefined} -
    - ) + }} + /> + ) + }) + return [] } - - renderTable(columns, dataSource) { - return ( -
    - - - - {columns.map((col, index) => { - return ( - - ) - })} - - - - {dataSource.map((record, rowIndex) => { + return ( +
    + +
    -
    {col.title}
    -
    + {isArr(schema.items) + ? schema.items.reduce((buf, items) => { + return buf.concat(renderColumns(items)) + }, []) + : renderColumns(schema.items)} + { return ( - - {columns.map((col, colIndex) => { - return ( - - ) - })} - + +
    + mutators.remove(index)} + /> + mutators.moveDown(index)} + /> + mutators.moveUp(index)} + /> + {isFn(renderExtraOperations) + ? renderExtraOperations(index) + : renderExtraOperations} +
    +
    ) - })} - {this.renderPlacehodler(dataSource, columns)} - -
    - {this.renderCell({ - record, - col, - rowIndex - })} -
    -
    - ) - } - - renderPlacehodler(dataSource, columns) { - if (dataSource.length === 0) { - return ( - - -
    - -
    - - - ) + }} + /> + + + {({ children }) => { + return ( +
    + {children} +
    + ) + }} +
    + +
    + ) + } +)` + display: inline-block; + min-width: 600px; + max-width: 100%; + overflow: scroll; + table { + margin-bottom: 0 !important; + th, + td { + padding: 0 !important; + vertical-align: top; + .next-form-item { + margin-bottom: 0 !important; } } - - getColumns(children) { - const columns: IColumnProps[] = [] - React.Children.forEach>( - children, - child => { - if (React.isValidElement(child)) { - if ( - child.type === Column || - child.type.displayName === '@schema-table-column' - ) { - columns.push(child.props) - } - } - } - ) - - return columns + } + .array-table-addition { + padding: 10px; + background: #fbfbfb; + border-left: 1px solid #dcdee3; + border-right: 1px solid #dcdee3; + border-bottom: 1px solid #dcdee3; + .next-btn-text { + color: #888; } - - render() { - const columns = this.getColumns(this.props.children) - const dataSource = toArr(this.props.dataSource) - return ( -
    -
    -
    - {this.renderTable(columns, dataSource)} -
    -
    -
    - ) + .next-icon:before { + width: 16px !important; + font-size: 16px !important; + margin-right: 5px; } - } -)` - .next-table { - position: relative; + margin-bottom: 10px; } - .next-table, - .next-table *, - .next-table :after, - .next-table :before { - -webkit-box-sizing: border-box; - box-sizing: border-box; - } - - .next-table table { - border-collapse: collapse; - border-spacing: 0; - width: 100%; - background: #fff; - display: table !important; - margin: 0 !important; - } - - .next-table table tr:first-child td { - border-top-width: 0; - } - - .next-table th { - padding: 0; - background: #ebecf0; - color: #333; - text-align: left; - font-weight: 400; - min-width: 200px; - border: 1px solid #dcdee3; - } - - .next-table th .next-table-cell-wrapper { - padding: 12px 16px; - overflow: hidden; - text-overflow: ellipsis; - word-break: break-all; - } - - .next-table td { - padding: 0; - border: 1px solid #dcdee3; - } - - .next-table td .next-table-cell-wrapper { - padding: 12px 16px; - overflow: hidden; - text-overflow: ellipsis; - word-break: break-all; + .array-item-operator { display: flex; - } - - .next-table.zebra tr:nth-child(odd) td { - background: #fff; - } - - .next-table.zebra tr:nth-child(2n) td { - background: #f7f8fa; - } - - .next-table-empty { - color: #a0a2ad; - padding: 32px 0; - text-align: center; - } - - .next-table-row { - -webkit-transition: all 0.3s ease; - transition: all 0.3s ease; - background: #fff; - color: #333; - border: none !important; - } - - .next-table-row.hidden { - display: none; - } - - .next-table-row.hovered, - .next-table-row.selected { - background: #f2f3f7; - color: #333; - } - - .next-table-body, - .next-table-header { - overflow: auto; - font-size: 12px; - } - - .next-table-body { - font-size: 12px; + align-items: center; + button { + margin-right: 8px; + } } ` -registerFormField( - 'table', - styled( - class extends ArrayField { - createFilter(key, payload) { - const { schema } = this.props - const columnFilter: (key: string, payload: any) => boolean = - schema['x-props'] && schema['x-props'].columnFilter - - return (render, otherwise) => { - if (isFn(columnFilter)) { - return columnFilter(key, payload) - ? isFn(render) - ? render() - : render - : isFn(otherwise) - ? otherwise() - : otherwise - } else { - return render() - } - } - } - - render() { - const { - value, - schema, - locale, - className, - renderField, - getOrderProperties - } = this.props - const cls = this.getProps('className') - const style = this.getProps('style') - const operationsWidth = this.getProps('operationsWidth') - return ( -
    -
    - - {getOrderProperties(schema.items).reduce( - (buf, { key, schema }) => { - const filter = this.createFilter(key, schema) - const res = filter( - () => { - return buf.concat( - { - return renderField([index, key]) - }} - /> - ) - }, - () => { - return buf - } - ) - return res - }, - [] - )} - { - return ( -
    - {this.renderRemove(index, item)} - {this.renderMoveDown(index, item)} - {this.renderMoveUp(index)} - {this.renderExtraOperations(index)} -
    - ) - }} - /> -
    - {this.renderAddition()} -
    -
    - ) - } - } - )` - display: inline-block; - .array-item-addition { - padding: 10px; - background: #fbfbfb; - border-left: 1px solid #dcdee3; - border-right: 1px solid #dcdee3; - border-bottom: 1px solid #dcdee3; - .next-btn-text { - color: #888; - } - .next-icon:before { - width: 16px !important; - font-size: 16px !important; - margin-right: 5px; - } - } - - .next-table-cell-wrapper > .next-form-item { - margin-bottom: 0; - } - .array-item-operator { - display: flex; - } - ` -) +registerFormField('table', FormTableField) diff --git a/packages/next/src/fields/textarea.ts b/packages/next/src/fields/textarea.ts index c65ce5616f2..ed33de7a648 100644 --- a/packages/next/src/fields/textarea.ts +++ b/packages/next/src/fields/textarea.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Input } from '@alifd/next' -import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils' +import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' const { TextArea } = Input diff --git a/packages/next/src/fields/time.ts b/packages/next/src/fields/time.ts index 6a046c54a39..dd15c25b647 100644 --- a/packages/next/src/fields/time.ts +++ b/packages/next/src/fields/time.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { TimePicker } from '@alifd/next' -import { mapStyledProps, mapTextComponent } from '../utils' +import { mapStyledProps, mapTextComponent } from '../shared' const transformMoment = (value, format = 'YYYY-MM-DD HH:mm:ss') => { return value && value.format ? value.format(format) : value diff --git a/packages/next/src/fields/transfer.ts b/packages/next/src/fields/transfer.ts index 006f5473cab..b38c0a9842e 100644 --- a/packages/next/src/fields/transfer.ts +++ b/packages/next/src/fields/transfer.ts @@ -1,6 +1,6 @@ -import { connect, registerFormField } from '@uform/react' +import { connect, registerFormField } from '@uform/react-schema-renderer' import { Transfer } from '@alifd/next' -import { mapStyledProps } from '../utils' +import { mapStyledProps } from '../shared' registerFormField( 'transfer', diff --git a/packages/next/src/fields/upload.tsx b/packages/next/src/fields/upload.tsx index 887c816a759..cb9f9905cb9 100644 --- a/packages/next/src/fields/upload.tsx +++ b/packages/next/src/fields/upload.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { connect, registerFormField } from '@uform/react' -import { toArr, isArr, isEqual, mapStyledProps } from '../utils' +import { connect, registerFormField } from '@uform/react-schema-renderer' +import { toArr, isArr, isEqual, mapStyledProps } from '../shared' import { Button, Upload } from '@alifd/next' import { UploadProps, CardProps } from '@alifd/next/types/upload' const { Card: UploadCard, Dragger: UploadDragger } = Upload diff --git a/packages/next/src/form.tsx b/packages/next/src/form.tsx deleted file mode 100644 index 50cc06be6dc..00000000000 --- a/packages/next/src/form.tsx +++ /dev/null @@ -1,434 +0,0 @@ -import React from 'react' -import classNames from 'classnames' -import styled from 'styled-components' -import { ConfigProvider, Balloon, Icon, Grid } from '@alifd/next' -import { registerFormWrapper, registerFieldMiddleware } from '@uform/react' -import { IFormItemProps, IFormProps } from '@uform/types' - -import LOCALE from './locale' -import { isFn, moveTo, isStr, stringLength } from './utils' - -/** - * 轻量级Next Form,不包含任何数据管理能力 - */ - -const { Row, Col } = Grid - -export const { - Provider: FormLayoutProvider, - Consumer: FormLayoutConsumer -} = React.createContext(undefined) - -const normalizeCol = col => { - return typeof col === 'object' ? col : { span: col } -} - -const getParentNode = (node, selector) => { - if (!node || (node && !node.matches)) return - if (node.matches(selector)) return node - else { - return getParentNode(node.parentNode || node.parentElement, selector) - } -} - -const isPopDescription = (description, maxTipsNum = 30) => { - if (isStr(description)) { - return stringLength(description) > maxTipsNum - } else { - return React.isValidElement(description) - } -} - -export const FormItem = styled( - class FormItem extends React.Component { - static defaultProps = { - prefix: 'next-' - } - - private getItemLabel() { - const { - id, - required, - label, - labelCol, - wrapperCol, - prefix, - extra, - labelAlign, - labelTextAlign, - autoAddColon, - isTableColItem, - maxTipsNum - } = this.props - - if (!label || isTableColItem) { - return null - } - - const ele = ( - // @ts-ignore - - ) - - const cls = classNames({ - [`${prefix}form-item-label`]: true, - [`${prefix}left`]: labelTextAlign === 'left' - }) - - if ((wrapperCol || labelCol) && labelAlign !== 'top') { - return ( - - {ele} - {isPopDescription(extra, maxTipsNum) && this.renderHelper()} - - ) - } - - return ( -
    - {ele} - {isPopDescription(extra, maxTipsNum) && this.renderHelper()} -
    - ) - } - - private getItemWrapper() { - const { - labelCol, - wrapperCol, - children, - extra, - label, - labelAlign, - help, - size, - prefix, - noMinHeight, - isTableColItem, - maxTipsNum - } = this.props - - const message = ( -
    - {help &&
    {help}
    } - {!help && !isPopDescription(extra, maxTipsNum) && ( -
    {extra}
    - )} -
    - ) - if ( - (wrapperCol || labelCol) && - labelAlign !== 'top' && - !isTableColItem && - label - ) { - return ( - - {React.cloneElement(children, { size })} - {message} - - ) - } - - return ( -
    - {React.cloneElement(children, { size })} - {message} -
    - ) - } - - private renderHelper() { - return ( - } - > - {this.props.extra} - - ) - } - - public render() { - /* eslint-disable @typescript-eslint/no-unused-vars */ - const { - className, - labelAlign, - labelTextAlign, - style, - prefix, - wrapperCol, - labelCol, - size, - help, - extra, - noMinHeight, - isTableColItem, - validateState, - autoAddColon, - required, - maxTipsNum, - type, - schema, - ...others - } = this.props - - /* eslint-enable @typescript-eslint/no-unused-vars */ - const itemClassName = classNames({ - [`${prefix}form-item`]: true, - [`${prefix}${labelAlign}`]: labelAlign, - [`has-${validateState}`]: !!validateState, - [`${prefix}${size}`]: !!size, - [`${className}`]: !!className, - [`field-${type}`]: !!type - }) - - // 垂直模式并且左对齐才用到 - const Tag = (wrapperCol || labelCol) && labelAlign !== 'top' ? Row : 'div' - const label = labelAlign === 'inset' ? null : this.getItemLabel() - - return ( - - {label} - {this.getItemWrapper()} - - ) - } - } -)` - margin-bottom: 4px !important; - &.field-table { - .next-form-item-control { - overflow: auto; - } - } - .next-form-item-msg { - &.next-form-item-space { - min-height: 20px; - .next-form-item-help, - .next-form-item-extra { - margin-top: 0; - } - } - } - .next-form-item-extra { - color: #888; - font-size: 12px; - line-height: 1.7; - } -` - -const toArr = val => (Array.isArray(val) ? val : val ? [val] : []) - -registerFormWrapper(OriginForm => { - OriginForm = styled(OriginForm)` - &.next-inline { - display: flex; - .rs-uform-content { - margin-right: 15px; - } - } - .next-radio-group, - .next-checkbox-group { - line-height: 28px; - & > label { - margin-right: 8px; - } - } - .next-small { - .next-radio-group, - .next-checkbox-group { - line-height: 20px; - } - } - .next-small { - .next-radio-group, - .next-checkbox-group { - line-height: 40px; - } - } - .next-card-head { - background: none; - } - .next-rating-medium { - min-height: 28px; - line-height: 28px; - } - .next-rating-small { - min-height: 20px; - line-height: 20px; - } - .next-rating-large { - min-height: 40px; - line-height: 40px; - } - ` - - return ConfigProvider.config( - class Form extends React.Component { - static defaultProps = { - component: 'form', - prefix: 'next-', - size: 'medium', - labelAlign: 'left', - locale: LOCALE, - autoAddColon: true - } - - static displayName = 'SchemaForm' - - FormRef = React.createRef() - - validateFailedHandler(onValidateFailed) { - return (...args) => { - if (isFn(onValidateFailed)) { - onValidateFailed(...args) - } - const container = this.FormRef.current as HTMLElement - if (container) { - const errors = container.querySelectorAll('.next-form-item-help') - if (errors && errors.length) { - const node = getParentNode(errors[0], '.next-form-item') - if (node) { - moveTo(node) - } - } - } - } - } - - render() { - const { - className, - inline, - size, - labelAlign, - labelTextAlign, - autoAddColon, - children, - labelCol, - wrapperCol, - style, - prefix, - maxTipsNum, - ...others - } = this.props - - const formClassName = classNames({ - [`${prefix}form`]: true, - [`${prefix}inline`]: inline, // 内联 - [`${prefix}${size}`]: size, - [className]: !!className - }) - return ( - - - {children} - - - ) - } - }, - {} - ) -}) - -const isTableColItem = (path, getSchema) => { - const schema = getSchema(path) - return schema && schema.type === 'array' && schema['x-component'] === 'table' -} - -registerFieldMiddleware(Field => { - return props => { - const { - name, - editable, - errors, - path, - schemaPath, - schema, - getSchema, - required - } = props - - if (path.length === 0) { - // 根节点是不需要包FormItem的 - return React.createElement(Field, props) - } - - return React.createElement( - FormLayoutConsumer, - {}, - ({ - labelAlign, - labelTextAlign, - labelCol, - wrapperCol, - maxTipsNum, - size, - autoAddColon - }) => { - return React.createElement( - FormItem, - { - labelAlign, - labelTextAlign, - labelCol, - wrapperCol, - autoAddColon, - maxTipsNum, - size, - ...schema['x-item-props'], - label: schema.title, - noMinHeight: schema.type === 'object' && !schema['x-component'], - isTableColItem: isTableColItem( - schemaPath.slice(0, schemaPath.length - 2), - getSchema - ), - type: schema['x-component'] || schema['type'], - id: name, - validateState: toArr(errors).length ? 'error' : undefined, - required: editable === false ? false : required, - extra: schema.description, - help: - toArr(errors).join(' , ') || - (schema['x-item-props'] && schema['x-item-props'].help) - }, - React.createElement(Field, props) - ) - } - ) - } -}) diff --git a/packages/next/src/index.tsx b/packages/next/src/index.tsx index b47f2e0a123..85d9569b902 100644 --- a/packages/next/src/index.tsx +++ b/packages/next/src/index.tsx @@ -1,45 +1,17 @@ -import './form' -import './fields/string' -import './fields/number' -import './fields/boolean' -import './fields/date' -import './fields/time' -import './fields/range' -import './fields/upload' -import './fields/checkbox' -import './fields/radio' -import './fields/rating' -import './fields/transfer' -import './fields/array' -import './fields/cards' -import './fields/table' -import './fields/textarea' -import './fields/password' - -export * from '@uform/react' -export * from './components/formButtonGroup' -export * from './components/button' -export * from './components/layout' - import React from 'react' import { - SchemaForm as InternalSchemaForm, - Field as InternalField -} from '@uform/react' -import { SchemaFormProps, FieldProps } from './type' - -export { mapStyledProps, mapTextComponent } from './utils' - -export default class SchemaForm extends React.Component> { - render() { - return - } -} - -export class Field extends React.Component< - FieldProps -> { - render() { - return - } -} + SchemaMarkupForm, + SchemaMarkupField +} from '@uform/react-schema-renderer' +import { INextSchemaFormProps, INextSchemaFieldProps } from './types' +import './fields' +import './compat' +export * from '@uform/react-schema-renderer' +export * from './components' +export * from './types' +export { mapStyledProps, mapTextComponent } from './shared' +export const SchemaForm: React.FC< + INextSchemaFormProps +> = SchemaMarkupForm as any +export const Field: React.FC = SchemaMarkupField +export default SchemaForm diff --git a/packages/next/src/locale.ts b/packages/next/src/locale.ts deleted file mode 100644 index aa28ff56c6e..00000000000 --- a/packages/next/src/locale.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -export default { - addItem: '添加', - array_invalid_minItems: '条目数不允许小于%s条', - array_invalid_maxItems: '条目数不允许大于%s条', - operations: '操作' -} diff --git a/packages/next/src/shared.ts b/packages/next/src/shared.ts new file mode 100644 index 00000000000..5d7761ae55a --- /dev/null +++ b/packages/next/src/shared.ts @@ -0,0 +1,66 @@ +import React from 'react' +import { Select } from '@alifd/next' +import { PreviewText } from '@uform/react-shared-components' +import { + MergedFieldComponentProps, + IConnectProps +} from '@uform/react-schema-renderer' +export * from '@uform/shared' + +export const mapTextComponent = ( + Target: React.JSXElementConstructor, + props: any = {}, + fieldProps: any = {} +): React.JSXElementConstructor => { + const { editable } = fieldProps + if (editable !== undefined) { + if (editable === false) { + return PreviewText + } + } + if (Array.isArray(props.dataSource)) { + return Select + } + return Target +} + +export const acceptEnum = (component: React.JSXElementConstructor) => { + return ({ dataSource, ...others }) => { + if (dataSource) { + return React.createElement(Select, { dataSource, ...others }) + } else { + return React.createElement(component, others) + } + } +} + +export const normalizeCol = ( + col: { span: number; offset?: number } | number, + defaultValue: { span: number } +): { span: number; offset?: number } => { + if (!col) { + return defaultValue + } else { + return typeof col === 'object' ? col : { span: Number(col) } + } +} + +export const mapStyledProps = ( + props: IConnectProps, + fieldProps: MergedFieldComponentProps +) => { + const { loading, errors } = fieldProps + if (loading) { + props.state = props.state || 'loading' + } else if (errors && errors.length) { + props.state = 'error' + } +} + +export const compose = (...args: any[]) => { + return (payload: any, ...extra: any[]) => { + return args.reduce((buf, fn) => { + return buf !== undefined ? fn(buf, ...extra) : fn(payload, ...extra) + }, payload) + } +} diff --git a/packages/next/src/type.tsx b/packages/next/src/type.tsx deleted file mode 100644 index 24bae70f004..00000000000 --- a/packages/next/src/type.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { ButtonProps } from '@alifd/next/types/button' -import { CardProps } from '@alifd/next/types/card' -import { RowProps, ColProps } from '@alifd/next/types/grid' -import { - IFormActions, - ISchema, - IEffects, - IFieldError, - Size, - TextAlign, - Layout, - TextEl, - LabelAlign -} from '@uform/types' -import { SwitchProps } from '@alifd/next/types/switch' -import { GroupProps as CheckboxGroupProps } from '@alifd/next/types/checkbox' -import { GroupProps as RadioGroupProps } from '@alifd/next/types/radio' -import { - DatePickerProps, - RangePickerProps, - MonthPickerProps, - YearPickerProps -} from '@alifd/next/types/date-picker' -import { NumberPickerProps } from '@alifd/next/types/number-picker' -import { IPasswordProps } from './fields/password' -import { RangeProps } from '@alifd/next/types/range' -import { RatingProps } from '@alifd/next/types/rating' -import { InputProps, TextAreaProps } from '@alifd/next/types/input' -import { TimePickerProps } from '@alifd/next/types/time-picker' -import { TransferProps } from '@alifd/next/types/transfer' -import { IUploaderProps } from './fields/upload' -import { SelectProps } from '@alifd/next/types/select' - -type ColSpanType = number | string - -export interface ColSize { - span?: ColSpanType - offset?: ColSpanType -} - -export interface ILocaleMessages { - [key: string]: string | ILocaleMessages -} - -export interface IFormLayoutProps { - className?: string - inline?: boolean - labelAlign?: LabelAlign - wrapperCol?: IColProps | number - labelCol?: IColProps | number - labelTextAlign?: TextAlign - size?: Size - style?: React.CSSProperties -} - -export interface IFormItemGridProps { - cols?: Array - description?: TextEl - gutter?: number - title?: TextEl -} - -export type TFormCardOrFormBlockProps = Omit - -export interface IFormTextBox { - text?: string - title?: TextEl - description?: TextEl - gutter?: number -} - -export interface IRowProps extends RowProps { - prefix?: string - pure?: boolean - className?: string - style?: object -} - -export interface IColProps extends ColProps { - prefix?: string - pure?: boolean - className?: string -} - -export interface IFormCardProps extends CardProps { - className?: string -} - -export interface IFormBlockProps extends CardProps { - className?: string -} - -export interface ISubmitProps extends Omit { - showLoading?: boolean -} - -export interface IFormButtonGroupProps { - sticky?: boolean - style?: React.CSSProperties - itemStyle?: React.CSSProperties - className?: string - align?: 'left' | 'right' | 'start' | 'end' | 'top' | 'bottom' | 'center' - triggerDistance?: number - zIndex?: number - span?: ColSpanType - offset?: ColSpanType -} - -export interface SchemaFormProps { - actions?: IFormActions - initialValues?: V - defaultValue?: V - value?: V - editable?: boolean | ((name: string) => boolean) - effects?: IEffects - locale?: ILocaleMessages - schema?: ISchema - onChange?: (values: V) => void - onReset?: (values: V) => void - onSubmit?: (values: V) => void - onValidateFailed?: (fieldErrors: IFieldError[]) => void - autoAddColon?: boolean - className?: string - inline?: boolean - layout?: Layout - maxTipsNum?: number - labelAlign?: LabelAlign - labelTextAlign?: TextAlign - labelCol?: ColSize | number - wrapperCol?: ColSize | number - size?: Size - style?: React.CSSProperties - prefix?: string -} - -interface InternalFieldTypes { - boolean: SwitchProps | SelectProps - checkbox: CheckboxGroupProps - date: DatePickerProps - daterange: RangePickerProps - month: MonthPickerProps - // week: WeekPickerProps - year: YearPickerProps - number: NumberPickerProps | SelectProps - password: IPasswordProps - radio: RadioGroupProps - range: RangeProps - rating: RatingProps - string: InputProps | SelectProps - textarea: TextAreaProps | SelectProps - time: TimePickerProps - transfer: TransferProps - upload: IUploaderProps -} -export interface FieldProps extends ISchema { - type?: T - name?: string - editable?: boolean - ['x-props']?: T extends keyof InternalFieldTypes ? InternalFieldTypes[T] : any -} diff --git a/packages/next/src/types.ts b/packages/next/src/types.ts new file mode 100644 index 00000000000..b93217d9e24 --- /dev/null +++ b/packages/next/src/types.ts @@ -0,0 +1,95 @@ +import { ButtonProps } from '@alifd/next/types/button' +import { FormProps, ItemProps } from '@alifd/next/types/form' +import { StepProps, ItemProps as StepItemProps } from '@alifd/next/types/step' +import { + ISchemaFormProps, + IMarkupSchemaFieldProps, + ISchemaFieldComponentProps +} from '@uform/react-schema-renderer' +import { StyledComponent } from 'styled-components' + +type ColSpanType = number | string + +export type INextSchemaFormProps = ISchemaFormProps & + FormProps & + IFormItemTopProps + +export type INextSchemaFieldProps = IMarkupSchemaFieldProps + +export interface ISubmitProps extends ButtonProps { + onSubmit?: ISchemaFormProps['onSubmit'] + showLoading?: boolean +} + +export interface IResetProps extends ButtonProps { + forceClear?: boolean + validate?: boolean +} + +export type IFormItemTopProps = React.PropsWithChildren< + Exclude< + Pick< + ItemProps, + | 'prefix' + | 'labelCol' + | 'wrapperCol' + | 'labelAlign' + | 'labelTextAlign' + | 'size' + >, + 'labelCol' | 'wrapperCol' + > & { + inline?: boolean + className?: string + style?: React.CSSProperties + labelCol?: number | { span: number; offset?: number } + wrapperCol?: number | { span: number; offset?: number } + } +> + +export interface ICompatItemProps + extends Exclude, + Partial { + labelCol?: number | { span: number; offset?: number } + wrapperCol?: number | { span: number; offset?: number } +} + +export type StyledCP

    = StyledComponent< + (props: React.PropsWithChildren

    ) => React.ReactElement, + any, + {}, + never +> + +export type StyledCC = StyledCP & Statics + +export interface IFormButtonGroupProps { + sticky?: boolean + style?: React.CSSProperties + itemStyle?: React.CSSProperties + className?: string + align?: 'left' | 'right' | 'start' | 'end' | 'top' | 'bottom' | 'center' + triggerDistance?: number + zIndex?: number + span?: ColSpanType + offset?: ColSpanType +} + +export interface IItemProps { + title?: React.ReactText + description?: React.ReactText +} + +export interface IFormItemGridProps extends IItemProps { + cols?: Array + gutter?: number +} + +export interface IFormTextBox extends IItemProps { + text?: string + gutter?: number +} + +export interface IFormStep extends StepProps { + dataSource: StepItemProps[] +} diff --git a/packages/next/src/utils.tsx b/packages/next/src/utils.tsx deleted file mode 100644 index 1da41174248..00000000000 --- a/packages/next/src/utils.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react' -import { Select } from '@alifd/next' -import styled from 'styled-components' -import { isFn } from '@uform/utils' -import { IConnectProps, IFieldProps } from '@uform/react' - -export * from '@uform/utils' - -const MoveTo = typeof window !== 'undefined' ? require('moveto') : null -const Text = styled(props => { - let value - if (props.dataSource && props.dataSource.length) { - let find = props.dataSource.filter(({ value }) => - Array.isArray(props.value) - ? props.value.some(val => val == value) - : props.value == value - ) - value = find.map(item => item.label).join(' , ') - } else { - value = Array.isArray(props.value) - ? props.value.join(' ~ ') - : String( - props.value === undefined || props.value === null ? '' : props.value - ) - } - return ( -

    - {!value ? 'N/A' : value} - {props.innerAfter ? ' ' + props.innerAfter : ''} - {props.addonTextAfter ? ' ' + props.addonTextAfter : ''} - {props.addonAfter ? ' ' + props.addonAfter : ''} -
    - ) -})` - height: 28px; - line-height: 28px; - vertical-align: middle; - font-size: 13px; - color: #333; - &.small { - height: 20px; - line-height: 20px; - } - &.large { - height: 40px; - line-height: 40px; - } -` - -export const acceptEnum = component => { - return ({ dataSource, ...others }) => { - if (dataSource) { - return React.createElement(Select, { dataSource, ...others }) - } else { - return React.createElement(component, others) - } - } -} - -export const mapStyledProps = ( - props: IConnectProps, - { loading, size, errors }: IFieldProps -) => { - if (loading) { - props.state = props.state || 'loading' - } else if (errors && errors.length) { - props.state = 'error' - } - if (size) { - props.size = size - } -} - -export const mapTextComponent = ( - Target: React.ComponentClass, - props, - { - editable, - name - }: { editable: boolean | ((name: string) => boolean); name: string } -): React.ComponentClass => { - if (editable !== undefined) { - if (isFn(editable)) { - if (!editable(name)) { - return Text - } - } else if (editable === false) { - return Text - } - } - return Target -} - -export const compose = (...args) => { - return (payload, ...extra) => { - return args.reduce((buf, fn) => { - return buf !== undefined ? fn(buf, ...extra) : fn(payload, ...extra) - }, payload) - } -} - -export const moveTo = element => { - if (!element || !MoveTo) return - if (element.scrollIntoView) { - element.scrollIntoView({ - behavior: 'smooth', - inline: 'start', - block: 'start' - }) - } else { - new MoveTo().move(element.getBoundingClientRect().top) - } -} diff --git a/packages/next/tsconfig.json b/packages/next/tsconfig.json index 1d669c29c46..7d101da7c66 100644 --- a/packages/next/tsconfig.json +++ b/packages/next/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "outDir": "./lib" }, - "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "include": ["./src/**/*.js", "./src/**/*.ts", "./src/**/*.tsx"], "exclude": ["./src/__tests__/*"] } diff --git a/packages/printer/package.json b/packages/printer/package.json index 33135913ec6..920ca202bed 100644 --- a/packages/printer/package.json +++ b/packages/printer/package.json @@ -1,6 +1,6 @@ { "name": "@uform/printer", - "version": "0.4.3", + "version": "0.4.0", "license": "MIT", "main": "lib", "repository": { @@ -26,7 +26,7 @@ "typescript": "^3.5.2" }, "dependencies": { - "@uform/react": "^0.4.3", + "@uform/react-schema-renderer": "^0.4.0", "react-modal": "^3.8.1", "styled-components": "^4.1.1" }, diff --git a/packages/printer/src/index.js b/packages/printer/src/index.js index 577a44ab55d..a0b9a71ce6a 100644 --- a/packages/printer/src/index.js +++ b/packages/printer/src/index.js @@ -1,6 +1,6 @@ import React, { useState } from 'react' import ReactDOM from 'react-dom' -import { createFormActions } from '@uform/react' +import { createFormActions } from '@uform/react-schema-renderer' import styled from 'styled-components' import Modal from 'react-modal' diff --git a/packages/builder-next/.npmignore b/packages/react-schema-renderer/.npmignore similarity index 100% rename from packages/builder-next/.npmignore rename to packages/react-schema-renderer/.npmignore diff --git a/packages/react/LESENCE.md b/packages/react-schema-renderer/LESENCE.md similarity index 100% rename from packages/react/LESENCE.md rename to packages/react-schema-renderer/LESENCE.md diff --git a/packages/react-schema-renderer/README.md b/packages/react-schema-renderer/README.md new file mode 100644 index 00000000000..0f89496d1c6 --- /dev/null +++ b/packages/react-schema-renderer/README.md @@ -0,0 +1,2 @@ +# @uform/react-schema-renderer +> UForm React实现 \ No newline at end of file diff --git a/packages/react/jest.config.js b/packages/react-schema-renderer/jest.config.js similarity index 100% rename from packages/react/jest.config.js rename to packages/react-schema-renderer/jest.config.js diff --git a/packages/builder-next/package.json b/packages/react-schema-renderer/package.json similarity index 59% rename from packages/builder-next/package.json rename to packages/react-schema-renderer/package.json index a3e15207104..d14d82987e5 100644 --- a/packages/builder-next/package.json +++ b/packages/react-schema-renderer/package.json @@ -1,8 +1,8 @@ { - "name": "@uform/builder-next", - "version": "0.4.3", + "name": "@uform/react-schema-renderer", + "version": "0.4.0", "license": "MIT", - "main": "lib/index.js", + "main": "lib", "repository": { "type": "git", "url": "git+https://github.com/alibaba/uform.git" @@ -14,23 +14,26 @@ "engines": { "npm": ">=3.0.0" }, + "types": "lib/index.d.ts", "scripts": { - "build": "tsc" + "build": "tsc --declaration" }, - "resolutions": { - "@types/react": "16.8.23" + "devDependencies": { + "typescript": "^3.5.2" }, "peerDependencies": { - "@alifd/next": "^1.13.1", "@babel/runtime": "^7.4.4", + "@types/json-schema": "^7.0.3", + "@types/react": "^16.8.23", "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "react-dom": ">=16.8.0", + "react-eva": "^1.1.7" }, "dependencies": { - "@uform/builder": "^0.4.3" - }, - "devDependencies": { - "typescript": "^3.5.2" + "@uform/react": "^0.4.0", + "@uform/shared": "^0.4.0", + "@uform/validator": "^0.4.0", + "pascal-case": "^2.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/react/src/__tests__/actions.spec.js b/packages/react-schema-renderer/src/__old_tests__/actions.spec.js similarity index 100% rename from packages/react/src/__tests__/actions.spec.js rename to packages/react-schema-renderer/src/__old_tests__/actions.spec.js diff --git a/packages/react/src/__tests__/context.spec.js b/packages/react-schema-renderer/src/__old_tests__/context.spec.js similarity index 100% rename from packages/react/src/__tests__/context.spec.js rename to packages/react-schema-renderer/src/__old_tests__/context.spec.js diff --git a/packages/react/src/__tests__/destruct.spec.js b/packages/react-schema-renderer/src/__old_tests__/destruct.spec.js similarity index 97% rename from packages/react/src/__tests__/destruct.spec.js rename to packages/react-schema-renderer/src/__old_tests__/destruct.spec.js index 37a3ec6726f..871b83bef73 100644 --- a/packages/react/src/__tests__/destruct.spec.js +++ b/packages/react-schema-renderer/src/__old_tests__/destruct.spec.js @@ -1,6 +1,6 @@ import React, { Fragment } from 'react' import SchemaForm, { Field, registerFormField, connect } from '../index' -import { toArr } from '@uform/utils' +import { toArr } from '@uform/shared' import { render } from '@testing-library/react' registerFormField('string', connect()(props =>
    {props.value}
    )) diff --git a/packages/react/src/__tests__/display.spec.js b/packages/react-schema-renderer/src/__old_tests__/display.spec.js similarity index 100% rename from packages/react/src/__tests__/display.spec.js rename to packages/react-schema-renderer/src/__old_tests__/display.spec.js diff --git a/packages/react/src/__tests__/dynamic.spec.js b/packages/react-schema-renderer/src/__old_tests__/dynamic.spec.js similarity index 99% rename from packages/react/src/__tests__/dynamic.spec.js rename to packages/react-schema-renderer/src/__old_tests__/dynamic.spec.js index 9379aa7d1bb..00b98d15129 100644 --- a/packages/react/src/__tests__/dynamic.spec.js +++ b/packages/react-schema-renderer/src/__old_tests__/dynamic.spec.js @@ -7,7 +7,7 @@ import SchemaForm, { createFormActions, createVirtualBox } from '../index' -import { toArr } from '@uform/utils' +import { toArr } from '@uform/shared' import { render, fireEvent, act } from '@testing-library/react' let FormCard diff --git a/packages/react/src/__tests__/editable.spec.js b/packages/react-schema-renderer/src/__old_tests__/editable.spec.js similarity index 89% rename from packages/react/src/__tests__/editable.spec.js rename to packages/react-schema-renderer/src/__old_tests__/editable.spec.js index 70c8c71033b..6b29fa53977 100644 --- a/packages/react/src/__tests__/editable.spec.js +++ b/packages/react-schema-renderer/src/__old_tests__/editable.spec.js @@ -8,7 +8,7 @@ import SchemaForm, { FormPath } from '../index' import { render, act, fireEvent } from '@testing-library/react' -import { toArr } from '@uform/utils' +import { toArr } from '@uform/shared' registerFieldMiddleware(Field => { return props => { @@ -375,53 +375,3 @@ test('editable conflicts that x-props editable props with setFieldState', async await sleep(33) expect(queryByTestId('this is bbb')).toBeVisible() }) - -test('fix:#300', async () => { - const beforeValue = { - name: 'completeOrderParam', - protocol: 'dubbo', - rpcGroup: 'HSF', - rpcMethod: 'completeCommodity', - rpcService: 'com.aliyun.lx.spi.babeldemo.TradeService:1.0.1', - status: 'pre' - } - - const afterValue = { - name: 'completeCommodity', - protocol: 'pop', - rpcGroup: 'popGroup', - rpcMethod: 'popMethod', - rpcService: 'popService', - status: 'product' - } - - const TestComponent = () => { - const [values, setValues] = useState({}) - function getValue() { - setValues(afterValue) - } - return ( - - - - - ) - } - - const { queryByText } = render() - await sleep(33) - fireEvent.click(queryByText('GetValue')) - await sleep(33) - expect(queryByText('empty')).toBeVisible() -}) diff --git a/packages/react/src/__tests__/effects.spec.js b/packages/react-schema-renderer/src/__old_tests__/effects.spec.js similarity index 100% rename from packages/react/src/__tests__/effects.spec.js rename to packages/react-schema-renderer/src/__old_tests__/effects.spec.js diff --git a/packages/react/src/__tests__/mutators.spec.js b/packages/react-schema-renderer/src/__old_tests__/mutators.spec.js similarity index 100% rename from packages/react/src/__tests__/mutators.spec.js rename to packages/react-schema-renderer/src/__old_tests__/mutators.spec.js diff --git a/packages/react/src/__tests__/schema_form.spec.js b/packages/react-schema-renderer/src/__old_tests__/schema_form.spec.js similarity index 100% rename from packages/react/src/__tests__/schema_form.spec.js rename to packages/react-schema-renderer/src/__old_tests__/schema_form.spec.js diff --git a/packages/react/src/__tests__/traverse.spec.js b/packages/react-schema-renderer/src/__old_tests__/traverse.spec.js similarity index 100% rename from packages/react/src/__tests__/traverse.spec.js rename to packages/react-schema-renderer/src/__old_tests__/traverse.spec.js diff --git a/packages/react/src/__tests__/utils.spec.js b/packages/react-schema-renderer/src/__old_tests__/utils.spec.js similarity index 100% rename from packages/react/src/__tests__/utils.spec.js rename to packages/react-schema-renderer/src/__old_tests__/utils.spec.js diff --git a/packages/react/src/__tests__/validate.spec.js b/packages/react-schema-renderer/src/__old_tests__/validate.spec.js similarity index 95% rename from packages/react/src/__tests__/validate.spec.js rename to packages/react-schema-renderer/src/__old_tests__/validate.spec.js index 2bd27fcbe7d..b5b57119123 100644 --- a/packages/react/src/__tests__/validate.spec.js +++ b/packages/react-schema-renderer/src/__old_tests__/validate.spec.js @@ -437,26 +437,3 @@ test('async validate side effect', async () => { expect(queryByText('aa is required')).toBeVisible() expect(queryByText('bb is required')).toBeNull() }) - -test('async validate side effect', async () => { - const actions = createFormActions() - const TestComponent = () => { - return ( - - - - - - ) - } - const { queryByText } = render() - await sleep(33) - - fireEvent.click(queryByText('Submit')) - await sleep(33) - actions.setFieldState(FormPath.match('*'), state => { - state.editable = false - }) - await sleep(33) - expect(queryByText('aa is required')).toBeNull() -}) diff --git a/packages/react/src/__tests__/validate_relations.spec.js b/packages/react-schema-renderer/src/__old_tests__/validate_relations.spec.js similarity index 100% rename from packages/react/src/__tests__/validate_relations.spec.js rename to packages/react-schema-renderer/src/__old_tests__/validate_relations.spec.js diff --git a/packages/react/src/__tests__/value.spec.js b/packages/react-schema-renderer/src/__old_tests__/value.spec.js similarity index 97% rename from packages/react/src/__tests__/value.spec.js rename to packages/react-schema-renderer/src/__old_tests__/value.spec.js index 84ee7b50919..d3ef1224b45 100644 --- a/packages/react/src/__tests__/value.spec.js +++ b/packages/react-schema-renderer/src/__old_tests__/value.spec.js @@ -147,9 +147,9 @@ test('controlled with hooks by initalValues', async () => { await actions.reset() await sleep(33) expect(queryByTestId('test-input').value).toEqual('123') - expect(queryByTestId('outer-result').textContent).toEqual('Total is:123') - expect(queryByTestId('inner-result').textContent).toEqual('Total is:123') - expect(onChangeHandler).toHaveBeenCalledTimes(4) + expect(queryByTestId('outer-result').textContent).toEqual('Total is:456') + expect(queryByTestId('inner-result').textContent).toEqual('Total is:456') + expect(onChangeHandler).toHaveBeenCalledTimes(3) }) test('controlled with hooks by static value', async () => { @@ -192,9 +192,9 @@ test('controlled with hooks by static value', async () => { expect(onChangeHandler).toHaveBeenCalledTimes(3) actions.reset() await sleep(33) - expect(queryByTestId('outer-result').textContent).toEqual('Total is:') - expect(queryByTestId('inner-result').textContent).toEqual('Total is:') - expect(onChangeHandler).toHaveBeenCalledTimes(4) + expect(queryByTestId('outer-result').textContent).toEqual('Total is:123') + expect(queryByTestId('inner-result').textContent).toEqual('Total is:123') + expect(onChangeHandler).toHaveBeenCalledTimes(3) await actions.setFieldState('a3', state => { state.value = '456' }) @@ -202,13 +202,13 @@ test('controlled with hooks by static value', async () => { expect(queryByTestId('test-input').value).toEqual('123') expect(queryByTestId('outer-result').textContent).toEqual('Total is:123') expect(queryByTestId('inner-result').textContent).toEqual('Total is:123') - expect(onChangeHandler).toHaveBeenCalledTimes(6) + expect(onChangeHandler).toHaveBeenCalledTimes(5) await actions.reset() await sleep(33) expect(queryByTestId('test-input').value).toEqual('') - expect(queryByTestId('outer-result').textContent).toEqual('Total is:') - expect(queryByTestId('inner-result').textContent).toEqual('Total is:') - expect(onChangeHandler).toHaveBeenCalledTimes(7) + expect(queryByTestId('outer-result').textContent).toEqual('Total is:123') + expect(queryByTestId('inner-result').textContent).toEqual('Total is:123') + expect(onChangeHandler).toHaveBeenCalledTimes(5) }) test('controlled with hooks by dynamic value', async () => { @@ -252,9 +252,9 @@ test('controlled with hooks by dynamic value', async () => { actions.reset() await sleep(33) expect(queryByTestId('test-input').value).toEqual('') - expect(queryByTestId('outer-result').textContent).toEqual('Total is:') - expect(queryByTestId('inner-result').textContent).toEqual('Total is:') - expect(onChangeHandler).toHaveBeenCalledTimes(3) + expect(queryByTestId('outer-result').textContent).toEqual('Total is:333') + expect(queryByTestId('inner-result').textContent).toEqual('Total is:333') + expect(onChangeHandler).toHaveBeenCalledTimes(2) await actions.setFieldState('a3', state => { state.value = '456' }) @@ -262,13 +262,13 @@ test('controlled with hooks by dynamic value', async () => { expect(queryByTestId('test-input').value).toEqual('456') expect(queryByTestId('outer-result').textContent).toEqual('Total is:456') expect(queryByTestId('inner-result').textContent).toEqual('Total is:456') - expect(onChangeHandler).toHaveBeenCalledTimes(4) + expect(onChangeHandler).toHaveBeenCalledTimes(3) await actions.reset() await sleep(33) expect(queryByTestId('test-input').value).toEqual('') - expect(queryByTestId('outer-result').textContent).toEqual('Total is:') - expect(queryByTestId('inner-result').textContent).toEqual('Total is:') - expect(onChangeHandler).toHaveBeenCalledTimes(5) + expect(queryByTestId('outer-result').textContent).toEqual('Total is:456') + expect(queryByTestId('inner-result').textContent).toEqual('Total is:456') + expect(onChangeHandler).toHaveBeenCalledTimes(3) }) test('invariant initialValues will not be changed when form rerender', async () => { diff --git a/packages/react/src/__tests__/virtualbox.spec.js b/packages/react-schema-renderer/src/__old_tests__/virtualbox.spec.js similarity index 100% rename from packages/react/src/__tests__/virtualbox.spec.js rename to packages/react-schema-renderer/src/__old_tests__/virtualbox.spec.js diff --git a/packages/react/src/__tests__/visible.spec.js b/packages/react-schema-renderer/src/__old_tests__/visible.spec.js similarity index 100% rename from packages/react/src/__tests__/visible.spec.js rename to packages/react-schema-renderer/src/__old_tests__/visible.spec.js diff --git a/packages/react/src/__tests__/x-component.spec.js b/packages/react-schema-renderer/src/__old_tests__/x-component.spec.js similarity index 100% rename from packages/react/src/__tests__/x-component.spec.js rename to packages/react-schema-renderer/src/__old_tests__/x-component.spec.js diff --git a/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap b/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap new file mode 100644 index 00000000000..8f8529a73aa --- /dev/null +++ b/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap @@ -0,0 +1,218 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`test all apis markup string 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": "123", + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": true, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": "aa change", + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": "123", + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": true, + "name": "aa", + "pristine": false, + "props": Object { + "default": "123", + "type": "string", + "x-props": Object { + "data-testid": "input", + }, + }, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa change", + "values": Array [ + "aa change", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`test all apis markup virtualbox 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": "123", + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": true, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": "aa change", + }, + "warnings": Array [], + }, + "NO_NAME_FIELD_$0": Object { + "display": true, + "displayName": "VirtualFieldState", + "initialized": true, + "mounted": false, + "name": "NO_NAME_FIELD_$0", + "props": Object { + "type": "object", + "x-component": "card", + "x-props": Object { + "title": "this is card", + }, + }, + "unmounted": false, + "visible": true, + }, + "NO_NAME_FIELD_$0.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": "123", + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": true, + "name": "NO_NAME_FIELD_$0.aa", + "pristine": false, + "props": Object { + "default": "123", + "type": "string", + "x-props": Object { + "data-testid": "input", + }, + }, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa change", + "values": Array [ + "aa change", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": "123", + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": true, + "name": "NO_NAME_FIELD_$0.aa", + "pristine": false, + "props": Object { + "default": "123", + "type": "string", + "x-props": Object { + "data-testid": "input", + }, + }, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa change", + "values": Array [ + "aa change", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`test all apis markup virtualbox 2`] = ` +Object { + "properties": Object { + "NO_NAME_FIELD_$0": Object { + "properties": Object { + "aa": Object { + "default": "123", + "type": "string", + "x-props": Object { + "data-testid": "input", + }, + }, + }, + "type": "object", + "x-component": "card", + "x-props": Object { + "title": "this is card", + }, + }, + }, + "type": "object", +} +`; diff --git a/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap b/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap new file mode 100644 index 00000000000..52f8a58efbc --- /dev/null +++ b/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap @@ -0,0 +1,259 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`test all apis registerFieldMiddleware 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": "123", + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": true, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": "aa change", + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": "123", + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": true, + "name": "aa", + "pristine": false, + "props": Object { + "default": "123", + "type": "string", + "x-props": Object { + "data-testid": "input", + }, + }, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa change", + "values": Array [ + "aa change", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`test all apis registerFormField 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": "123", + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": true, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": "aa change", + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": "123", + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": true, + "name": "aa", + "pristine": false, + "props": Object { + "default": "123", + "type": "string", + "x-props": Object { + "data-testid": "input", + }, + }, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa change", + "values": Array [ + "aa change", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; + +exports[`test all apis registerVirtualBox 1`] = ` +Object { + "": Object { + "displayName": "FormState", + "editable": undefined, + "errors": Array [], + "initialValues": Object { + "aa": "123", + }, + "initialized": true, + "invalid": false, + "loading": false, + "mounted": true, + "pristine": false, + "props": Object {}, + "submitting": false, + "unmounted": false, + "valid": true, + "validating": false, + "values": Object { + "aa": "aa change", + }, + "warnings": Array [], + }, + "aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": "123", + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": true, + "name": "cc.aa", + "pristine": false, + "props": Object { + "default": "123", + "type": "string", + "x-props": Object { + "data-testid": "input", + }, + }, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa change", + "values": Array [ + "aa change", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, + "cc": Object { + "display": true, + "displayName": "VirtualFieldState", + "initialized": true, + "mounted": false, + "name": "cc", + "props": Object { + "type": "object", + "x-component": "box", + }, + "unmounted": false, + "visible": true, + }, + "cc.aa": Object { + "active": false, + "display": true, + "displayName": "FieldState", + "editable": true, + "effectErrors": Array [], + "effectWarnings": Array [], + "errors": Array [], + "formEditable": undefined, + "initialValue": "123", + "initialized": true, + "invalid": false, + "loading": false, + "modified": true, + "mounted": true, + "name": "cc.aa", + "pristine": false, + "props": Object { + "default": "123", + "type": "string", + "x-props": Object { + "data-testid": "input", + }, + }, + "required": false, + "ruleErrors": Array [], + "ruleWarnings": Array [], + "rules": Array [], + "selfEditable": undefined, + "touched": false, + "unmounted": false, + "valid": true, + "validating": false, + "value": "aa change", + "values": Array [ + "aa change", + ], + "visible": true, + "visited": false, + "warnings": Array [], + }, +} +`; diff --git a/packages/react-schema-renderer/src/__tests__/field.spec.tsx b/packages/react-schema-renderer/src/__tests__/field.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react-schema-renderer/src/__tests__/field.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react-schema-renderer/src/__tests__/form.spec.tsx b/packages/react-schema-renderer/src/__tests__/form.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react-schema-renderer/src/__tests__/form.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react-schema-renderer/src/__tests__/json-schema.spec.tsx b/packages/react-schema-renderer/src/__tests__/json-schema.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react-schema-renderer/src/__tests__/json-schema.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react-schema-renderer/src/__tests__/markup.spec.tsx b/packages/react-schema-renderer/src/__tests__/markup.spec.tsx new file mode 100644 index 00000000000..9013675dd70 --- /dev/null +++ b/packages/react-schema-renderer/src/__tests__/markup.spec.tsx @@ -0,0 +1,105 @@ +import React from 'react' +import { + registerFormField, + createVirtualBox, + connect, + SchemaMarkupForm as SchemaForm, + SchemaMarkupField as Field, + createFormActions, + cleanRegistry +} from '../index' +import { render, fireEvent, wait } from '@testing-library/react' + +describe('test all apis', () => { + beforeEach(() => { + registerFormField( + 'string', + connect()(props => { + return + }) + ) + }) + + afterEach(() => { + cleanRegistry() + }) + + test('markup string', async () => { + const actions = createFormActions() + + const { queryByTestId } = render( + + + + ) + expect(queryByTestId('input').getAttribute('value')).toEqual('123') + fireEvent.change(queryByTestId('input'), { + target: { + value: 'aa change' + } + }) + await wait(() => { + expect(queryByTestId('input').getAttribute('value')).toEqual('aa change') + expect(actions.getFormGraph()).toMatchSnapshot() + expect(actions.getFormState(state => state.values)).toEqual({ + aa: 'aa change' + }) + }) + }) + + test('markup virtualbox', async () => { + const Card = createVirtualBox<{ title?: string | React.ReactElement }>( + 'card', + props => { + return ( +
    +
    Title:{props.title}
    +
    {props.children}
    +
    + ) + } + ) + + const actions = createFormActions() + + const { queryByTestId } = render( + + + + + + ) + expect(queryByTestId('input').getAttribute('value')).toEqual('123') + fireEvent.change(queryByTestId('input'), { + target: { + value: 'aa change' + } + }) + await wait(() => { + expect(queryByTestId('input').getAttribute('value')).toEqual('aa change') + expect(actions.getFormGraph()).toMatchSnapshot() + expect(actions.getFormState(state => state.values)).toEqual({ + aa: 'aa change' + }) + expect(actions.getFormSchema()).toMatchSnapshot() + }) + }) +}) + +describe('major scenes',()=>{ + //todo,核心场景回归 +}) + +describe('bugfix',()=>{ + //todo,问题修复回归 +}) \ No newline at end of file diff --git a/packages/react-schema-renderer/src/__tests__/register.spec.tsx b/packages/react-schema-renderer/src/__tests__/register.spec.tsx new file mode 100644 index 00000000000..a72934ce55a --- /dev/null +++ b/packages/react-schema-renderer/src/__tests__/register.spec.tsx @@ -0,0 +1,166 @@ +import React from 'react' +import { + registerFormField, + registerFieldMiddleware, + registerVirtualBox, + connect, + SchemaForm, + createFormActions, + cleanRegistry +} from '../index' +import { render, fireEvent, wait } from '@testing-library/react' + +describe('test all apis', () => { + afterEach(() => { + cleanRegistry() + }) + test('registerFormField', async () => { + registerFormField( + 'string', + connect()(props => { + return + }) + ) + + const actions = createFormActions() + + const { queryByTestId } = render( + + ) + expect(queryByTestId('input').getAttribute('value')).toEqual('123') + fireEvent.change(queryByTestId('input'), { + target: { + value: 'aa change' + } + }) + await wait(() => { + expect(queryByTestId('input').getAttribute('value')).toEqual('aa change') + expect(actions.getFormGraph()).toMatchSnapshot() + }) + }) + + test('registerFieldMiddleware', async () => { + registerFormField( + 'string', + connect()(props => { + return + }) + ) + + registerFieldMiddleware(FieldComponent => props => { + return ( +
    + this is wrapper + +
    + ) + }) + + const actions = createFormActions() + + const { queryByTestId, queryByText } = render( + + ) + expect(queryByTestId('input').getAttribute('value')).toEqual('123') + fireEvent.change(queryByTestId('input'), { + target: { + value: 'aa change' + } + }) + await wait(() => { + expect(queryByTestId('input').getAttribute('value')).toEqual('aa change') + expect(actions.getFormGraph()).toMatchSnapshot() + expect(queryByText('this is wrapper')).toBeTruthy() + }) + }) + + test('registerVirtualBox', async () => { + registerFormField( + 'string', + connect()(props => { + return + }) + ) + + registerVirtualBox('box', props => { + return
    this is VirtualBox.{props.children}
    + }) + + const actions = createFormActions() + + const { queryByTestId, queryByText } = render( + + ) + expect(queryByTestId('input').getAttribute('value')).toEqual('123') + fireEvent.change(queryByTestId('input'), { + target: { + value: 'aa change' + } + }) + await wait(() => { + expect(queryByTestId('input').getAttribute('value')).toEqual('aa change') + expect(actions.getFormGraph()).toMatchSnapshot() + expect(actions.getFormState(state => state.values)).toEqual({ + aa: 'aa change' + }) + expect(queryByText('this is VirtualBox.')).toBeTruthy() + }) + }) +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react-schema-renderer/src/components/SchemaField.tsx b/packages/react-schema-renderer/src/components/SchemaField.tsx new file mode 100644 index 00000000000..ae1b6110bde --- /dev/null +++ b/packages/react-schema-renderer/src/components/SchemaField.tsx @@ -0,0 +1,144 @@ +import React, { useContext, Fragment } from 'react' +import { Field, VirtualField } from '@uform/react' +import { FormPath, isFn, isStr } from '@uform/shared' +import { + ISchemaFieldProps, + ISchemaFieldComponentProps, + ISchemaVirtualFieldComponentProps +} from '../types' +import { Schema } from '../shared/schema' +import SchemaContext, { FormComponentsContext } from '../shared/context' + +export const SchemaField: React.FunctionComponent = ( + props: ISchemaFieldProps +) => { + const path = FormPath.parse(props.path) + const formSchema = useContext(SchemaContext) + const fieldSchema = formSchema.get(path) + const formRegistry = useContext(FormComponentsContext) + if (!fieldSchema) { + throw new Error(`Can not found schema node by ${path.toString()}.`) + } + if (!formRegistry) { + throw new Error(`Can not found any form components.`) + } + const schemaType = fieldSchema.type + const schemaComponent = fieldSchema.getExtendsComponent() + const schemaRenderer = fieldSchema.getExtendsRenderer() + const finalComponentName = schemaComponent || schemaType + const renderField = ( + addtionKey: string | number, + reactKey?: string | number + ) => { + return + } + const renderChildren = ( + callback: (props: ISchemaFieldComponentProps) => React.ReactElement + ) => { + return ( + + {({ state, mutators, form }) => { + const props: ISchemaFieldComponentProps = { + ...state, + schema: fieldSchema, + form, + mutators, + renderField + } + return callback(props) + }} + + ) + } + if (fieldSchema.isObject() && !schemaComponent) { + const properties = fieldSchema.mapProperties( + (schema: Schema, key: string) => { + const childPath = path.concat(key) + return + } + ) + if (path.length == 0) { + return {properties} + } + return renderChildren(props => { + return React.createElement( + formRegistry.formItemComponent, + props, + properties + ) + }) + } else { + if (isFn(finalComponentName)) { + return renderChildren(props => { + return React.createElement( + formRegistry.formItemComponent, + props, + React.createElement(finalComponentName, props) + ) + }) + } else if (isStr(finalComponentName)) { + if (formRegistry.fields[finalComponentName]) { + return renderChildren(props => { + const renderComponent = (): React.ReactElement => + React.createElement( + formRegistry.formItemComponent, + props, + React.createElement( + formRegistry.fields[finalComponentName], + props + ) + ) + if (isFn(schemaRenderer)) { + return schemaRenderer({ ...props, renderComponent }) + } + return renderComponent() + }) + } else if (formRegistry.virtualFields[finalComponentName]) { + return ( + + {({ state, form }) => { + const props: ISchemaVirtualFieldComponentProps = { + ...state, + schema: fieldSchema, + form, + renderField, + children: fieldSchema.mapProperties( + (schema: Schema, key: string) => { + const childPath = path.concat(key) + return ( + + ) + } + ) + } + const renderComponent = () => + React.createElement( + formRegistry.virtualFields[finalComponentName], + props + ) + if (isFn(schemaRenderer)) { + return schemaRenderer({ ...props, renderComponent }) + } + return renderComponent() + }} + + ) + } else { + throw new Error( + `Can not found any custom component in ${path.toString()}.` + ) + } + } + } +} diff --git a/packages/react-schema-renderer/src/components/SchemaForm.tsx b/packages/react-schema-renderer/src/components/SchemaForm.tsx new file mode 100644 index 00000000000..c31870b6e10 --- /dev/null +++ b/packages/react-schema-renderer/src/components/SchemaForm.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import { ISchemaFormProps } from '../types' +import { Form } from '@uform/react' +import { SchemaField } from './SchemaField' +import { useSchemaForm } from '../hooks/useSchemaForm' +import SchemaContext, { FormComponentsContext } from '../shared/context' + +export const SchemaForm: React.FC = props => { + const { + fields, + virtualFields, + formComponent, + formItemComponent, + formComponentProps, + schema, + form, + children + } = useSchemaForm(props) + return ( + + + + {React.createElement( + formComponent, + { + ...formComponentProps, + onSubmit: () => { + form.submit() + }, + onReset: () => { + form.reset({ validate: false, forceClear: false }) + } + }, + , + children + )} + + + + ) +} + +SchemaForm.defaultProps = { + schema: {} +} + +export default SchemaForm diff --git a/packages/react-schema-renderer/src/components/SchemaMarkup.tsx b/packages/react-schema-renderer/src/components/SchemaMarkup.tsx new file mode 100644 index 00000000000..684988bb099 --- /dev/null +++ b/packages/react-schema-renderer/src/components/SchemaMarkup.tsx @@ -0,0 +1,131 @@ +import React, { Fragment, createContext, useContext } from 'react' +import { registerVirtualBox } from '../shared/registry' +import { SchemaForm } from './SchemaForm' +import { Schema } from '../shared/schema' +import { render } from '../shared/virtual-render' +import { + ISchemaFormProps, + IMarkupSchemaFieldProps, + ISchemaVirtualFieldComponentProps +} from '../types' + +const env = { + nonameId: 0 +} + +const MarkupContext = createContext(null) + +const getRadomName = () => { + return `NO_NAME_FIELD_$${env.nonameId++}` +} + +export const SchemaMarkupField: React.FC = ({ + name, + children, + ...props +}) => { + const parentSchema = useContext(MarkupContext) + if (!parentSchema) return + if (parentSchema.isObject()) { + const propName = name || getRadomName() + const schema = parentSchema.setProperty(propName, props) + return ( + {children} + ) + } else if (parentSchema.isArray()) { + const schema = parentSchema.setArrayItems(props) + return ( + {children} + ) + } else { + return (children as React.ReactElement) || + } +} + +SchemaMarkupField.displayName = 'SchemaMarkupField' + +export const SchemaMarkupForm: React.FC = props => { + let alreadyHasSchema = false + let finalSchema: Schema + if (props.schema) { + alreadyHasSchema = true + finalSchema = new Schema(props.schema) + } else { + finalSchema = new Schema({ type: 'object' }) + } + env.nonameId = 0 + return ( + + {!alreadyHasSchema && + render( + + {props.children} + + )} + + + ) +} + +SchemaMarkupForm.displayName = 'SchemaMarkupForm' + +export function createVirtualBox( + key: string, + component?: React.JSXElementConstructor +) { + registerVirtualBox( + key, + component + ? ({ props, children }) => { + return React.createElement(component, { + ...props['x-props'], + ...props['x-component-props'], + children + }) + } + : () => + ) + const VirtualBox: React.FC = ({ + children, + name, + ...props + }) => { + return ( + + {children} + + ) + } + return VirtualBox +} + +export function createControllerBox( + key: string, + component?: React.JSXElementConstructor +) { + registerVirtualBox(key, component ? component : () => ) + const VirtualBox: React.FC = ({ + children, + name, + ...props + }) => { + return ( + + {children} + + ) + } + return VirtualBox +} diff --git a/packages/react-schema-renderer/src/hooks/useSchemaForm.ts b/packages/react-schema-renderer/src/hooks/useSchemaForm.ts new file mode 100644 index 00000000000..1bd1c998523 --- /dev/null +++ b/packages/react-schema-renderer/src/hooks/useSchemaForm.ts @@ -0,0 +1,71 @@ +import { useMemo, useRef } from 'react' +import { useForm } from '@uform/react' +import { Schema } from '../shared/schema' +import { deprecate } from '@uform/shared' +import { useEva } from 'react-eva' +import { ISchemaFormProps } from '../types' +import { createSchemaFormActions } from '../shared/actions' +import { getRegistry } from '../shared/registry' + +const useInternalSchemaForm = (props: ISchemaFormProps) => { + const { + fields, + virtualFields, + formComponent, + formItemComponent, + component, + schema, + value, + initialValues, + actions, + effects, + onChange, + onSubmit, + onReset, + onValidateFailed, + useDirty, + children, + editable, + validateFirst, + ...formComponentProps + } = props + const { implementActions } = useEva({ + actions + }) + const registry = getRegistry() + return { + form: useForm(props), + formComponentProps, + fields: { + ...registry.fields, + ...fields + }, + virtualFields: { + ...registry.virtualFields, + ...virtualFields + }, + formComponent: formComponent ? formComponent : registry.formComponent, + formItemComponent: formItemComponent + ? formItemComponent + : registry.formItemComponent, + schema: useMemo(() => { + const result = new Schema(schema) + implementActions({ + getSchema: deprecate(() => result, 'Please use the getFormSchema.'), + getFormSchema: () => result + }) + return result + }, [schema]), + children + } +} + +export const useSchemaForm = (props: ISchemaFormProps) => { + const actionsRef = useRef(null) + actionsRef.current = + actionsRef.current || props.actions || createSchemaFormActions() + return useInternalSchemaForm({ + ...props, + actions: actionsRef.current + }) +} diff --git a/packages/react-schema-renderer/src/index.tsx b/packages/react-schema-renderer/src/index.tsx new file mode 100644 index 00000000000..118b190a965 --- /dev/null +++ b/packages/react-schema-renderer/src/index.tsx @@ -0,0 +1,16 @@ +import { + createAsyncSchemaFormActions, + createSchemaFormActions +} from './shared/actions' +export * from '@uform/react' +export * from './components/SchemaField' +export * from './components/SchemaForm' +export * from './components/SchemaMarkup' +export * from './hooks/useSchemaForm' +export * from './shared/connect' +export * from './shared/registry' +export * from './shared/schema' +export * from './types' + +export const createFormActions = createSchemaFormActions +export const createAsyncFormActions = createAsyncSchemaFormActions diff --git a/packages/react-schema-renderer/src/shared/actions.ts b/packages/react-schema-renderer/src/shared/actions.ts new file mode 100644 index 00000000000..7f3fff6d0a0 --- /dev/null +++ b/packages/react-schema-renderer/src/shared/actions.ts @@ -0,0 +1,15 @@ +import { createFormActions, createAsyncFormActions } from '@uform/react' +import { mergeActions, createActions, createAsyncActions } from 'react-eva' +import { ISchemaFormActions, ISchemaFormAsyncActions } from '../types' + +export const createSchemaFormActions = (): ISchemaFormActions => + mergeActions( + createFormActions(), + createActions('getSchema', 'getFormSchema') + ) as ISchemaFormActions + +export const createAsyncSchemaFormActions = (): ISchemaFormAsyncActions => + mergeActions( + createAsyncFormActions(), + createAsyncActions('getSchema', 'getFormSchema') + ) as ISchemaFormAsyncActions diff --git a/packages/react-schema-renderer/src/shared/connect.ts b/packages/react-schema-renderer/src/shared/connect.ts new file mode 100644 index 00000000000..159fcac127c --- /dev/null +++ b/packages/react-schema-renderer/src/shared/connect.ts @@ -0,0 +1,123 @@ +import React from 'react' +import { isArr, each, isFn } from '@uform/shared' +import { + ISchema, + IConnectOptions, + ISchemaFieldComponentProps, + IConnectProps +} from '../types' + +const createEnum = (enums: any) => { + if (isArr(enums)) { + return enums.map(item => { + if (typeof item === 'object') { + return { + ...item + } + } else { + return { + ...item, + label: item, + value: item + } + } + }) + } + + return [] +} + +const bindEffects = ( + props: {}, + effect: ISchema['x-effect'], + notify: (type: string, payload?: any) => void +): any => { + each(effect(notify, { ...props }), (event, key) => { + const prevEvent = key === 'onChange' ? props[key] : undefined + props[key] = (...args: any[]) => { + if (isFn(prevEvent)) { + prevEvent(...args) + } + if (isFn(event)) { + return event(...args) + } + } + }) + return props +} + +export const connect = (options?: IConnectOptions) => { + options = { + valueName: 'value', + eventName: 'onChange', + ...options + } + return (Component: React.JSXElementConstructor) => { + return (fieldProps: ISchemaFieldComponentProps) => { + const { + value, + name, + mutators, + form, + schema, + editable, + props + } = fieldProps + let componentProps: IConnectProps = { + ...options.defaultProps, + ...props['x-props'], + ...props['x-component-props'], + [options.valueName]: value, + [options.eventName]: (event: any, ...args: any[]) => { + mutators.change( + options.getValueFromEvent + ? options.getValueFromEvent.call(schema, event, ...args) + : event, + ...args + ) + }, + onBlur: () => mutators.blur(), + onFocus: () => mutators.focus() + } + if (editable !== undefined) { + if (isFn(editable)) { + if (!editable(name)) { + componentProps.disabled = true + componentProps.readOnly = true + } + } else if (editable === false) { + componentProps.disabled = true + componentProps.readOnly = true + } + } + + const extendsEffect = schema.getExtendsEffect() + + if (isFn(extendsEffect)) { + componentProps = bindEffects(componentProps, extendsEffect, form.notify) + } + + if (isFn(options.getProps)) { + const newProps = options.getProps(componentProps, fieldProps) + if (newProps !== undefined) { + componentProps = newProps as any + } + } + + if (isArr(schema.enum) && !componentProps.dataSource) { + componentProps.dataSource = createEnum(schema.enum) + } + + if (componentProps.editable !== undefined) { + delete componentProps.editable + } + + return React.createElement( + isFn(options.getComponent) + ? options.getComponent(Component, props, fieldProps) + : Component, + componentProps + ) + } + } +} diff --git a/packages/react-schema-renderer/src/shared/context.ts b/packages/react-schema-renderer/src/shared/context.ts new file mode 100644 index 00000000000..29b52885b26 --- /dev/null +++ b/packages/react-schema-renderer/src/shared/context.ts @@ -0,0 +1,7 @@ +import { createContext } from 'react' +import { Schema } from './schema' +import { ISchemaFormRegistry } from '../types' + +export const FormComponentsContext = createContext(null) + +export default createContext(null) diff --git a/packages/react-schema-renderer/src/shared/registry.ts b/packages/react-schema-renderer/src/shared/registry.ts new file mode 100644 index 00000000000..5ffcadc5749 --- /dev/null +++ b/packages/react-schema-renderer/src/shared/registry.ts @@ -0,0 +1,134 @@ +import { isFn, lowercase, reduce, each, deprecate } from '@uform/shared' +import { + ComponentWithStyleComponent, + ISchemaFieldWrapper, + ISchemaFormRegistry, + ISchemaFieldComponent, + ISchemaFieldComponentProps, + ISchemaVirtualFieldComponentProps +} from '../types' +import pascalCase from 'pascal-case' + +const registry: ISchemaFormRegistry = { + fields: {}, + virtualFields: {}, + wrappers: [], + formItemComponent: ({ children }) => children, + formComponent: 'form' +} + +export const getRegistry = () => { + return { + fields: registry.fields, + virtualFields: registry.virtualFields, + formItemComponent: registry.formItemComponent, + formComponent: registry.formComponent + } +} + +export const cleanRegistry = () => { + registry.fields = {} + registry.virtualFields = {} + registry.wrappers = [] +} + +export function registerFormComponent( + component: React.JSXElementConstructor +) { + if (isFn(component)) { + registry.formComponent = component + } +} + +function compose(payload: T, args: P[], revert: boolean) { + return reduce( + args, + (buf: T, fn: P) => { + return isFn(fn) ? fn(buf) : buf + }, + payload, + revert + ) +} + +export function registerFormField( + name: string, + component: ComponentWithStyleComponent, + noWrapper: boolean = false +) { + if ( + name && + (isFn(component) || typeof component.styledComponentId === 'string') + ) { + name = lowercase(name) + if (noWrapper) { + registry.fields[name] = component + registry.fields[name].__WRAPPERS__ = [] + } else { + registry.fields[name] = compose( + component, + registry.wrappers, + true + ) + registry.fields[name].__WRAPPERS__ = registry.wrappers + } + registry.fields[name].displayName = pascalCase(name) + } +} + +export function registerFormFields(object: ISchemaFormRegistry['fields']) { + each( + object, + (component, key) => { + registerFormField(key, component) + } + ) +} + +export function registerVirtualBox( + name: string, + component: ComponentWithStyleComponent +) { + if ( + name && + (isFn(component) || typeof component.styledComponentId === 'string') + ) { + name = lowercase(name) + registry.virtualFields[name] = component + registry.virtualFields[name].displayName = pascalCase(name) + } +} + +export function registerFormItemComponent( + component: React.JSXElementConstructor +) { + if (isFn(component)) { + registry.formItemComponent = component + } +} + +type FieldMiddleware = ISchemaFieldWrapper + +export const registerFieldMiddleware = deprecate< + FieldMiddleware, + FieldMiddleware, + FieldMiddleware +>(function registerFieldMiddleware( + ...wrappers: ISchemaFieldWrapper[] +) { + each( + registry.fields, + (component, key) => { + if ( + !component.__WRAPPERS__.some(wrapper => wrappers.indexOf(wrapper) > -1) + ) { + registry.fields[key] = compose( + registry.fields[key], + wrappers, + true + ) + registry.fields[key].__WRAPPERS__ = wrappers + } + } + ) +}) diff --git a/packages/react-schema-renderer/src/shared/schema.ts b/packages/react-schema-renderer/src/shared/schema.ts new file mode 100644 index 00000000000..facc840ba7e --- /dev/null +++ b/packages/react-schema-renderer/src/shared/schema.ts @@ -0,0 +1,392 @@ +import React from 'react' +import { + ValidatePatternRules, + ValidateDescription, + CustomValidator, + getMessage +} from '@uform/validator' +import { + lowercase, + map, + each, + isEmpty, + isEqual, + isArr, + toArr, + isBool, + isValid, + FormPathPattern, + FormPath +} from '@uform/shared' +import { ISchemaFieldComponentProps, SchemaMessage, ISchema } from '../types' + +const numberRE = /^\d+$/ + +type SchemaProperties = { + [key: string]: Schema +} + +export class Schema implements ISchema { + /** base json schema spec**/ + public title?: SchemaMessage + public description?: SchemaMessage + public default?: any + public readOnly?: boolean + public writeOnly?: boolean + public type?: 'string' | 'object' | 'array' | 'number' | string + public enum?: Array + public const?: any + public multipleOf?: number + public maximum?: number + public exclusiveMaximum?: number + public minimum?: number + public exclusiveMinimum?: number + public maxLength?: number + public minLength?: number + public pattern?: string | RegExp + public maxItems?: number + public minItems?: number + public uniqueItems?: boolean + public maxProperties?: number + public minProperties?: number + public required?: string[] | boolean + public format?: string + /** nested json schema spec **/ + public properties?: SchemaProperties + public items?: Schema | Schema[] + public additionalItems?: Schema + public patternProperties?: { + [key: string]: Schema + } + public additionalProperties?: Schema + /** extend json schema specs */ + public editable?: boolean + public ['x-props']?: { [name: string]: any } + public ['x-index']?: number + public ['x-rules']?: ValidatePatternRules + public ['x-component']?: string | React.JSXElementConstructor + public ['x-component-props']?: { [name: string]: any } + public ['x-render']?: ( + props: T & { + renderComponent: () => React.ReactElement + } + ) => React.ReactElement + public ['x-effect']?: ( + dispatch: (type: string, payload: any) => void, + option?: object + ) => { [key: string]: any } + /** schema class self specs**/ + + public parent?: Schema + + public _isJSONSchemaObject = true + + constructor(json: ISchema, parent?: Schema) { + if (parent) { + this.parent = parent + } + return this.fromJSON(json) as any + } + /** + * getters + */ + get(path?: FormPathPattern) { + if (!path) { + return this + } + let res: Schema = this + let suc = 0 + path = FormPath.parse(path) + path.forEach(key => { + if (res && !isEmpty(res.properties)) { + res = res.properties[key] + suc++ + } else if (res && !isEmpty(res.items) && numberRE.test(key as string)) { + res = isArr(res.items) ? res.items[key] : res.items + suc++ + } + }) + return suc === path.length ? res : undefined + } + + getEmptyValue() { + if (this.type === 'string') { + return '' + } + if (this.type === 'array') { + return [] + } + if (this.type === 'object') { + return {} + } + if (this.type === 'number') { + return 0 + } + } + + getSelfProps() { + const { + _isJSONSchemaObject, + properties, + additionalProperties, + additionalItems, + patternProperties, + items, + parent, + ...props + } = this + return props + } + getExtendsRules() { + let rules: Array = [] + if (this.format) { + rules.push({ format: this.format }) + } + if (isValid(this.maxItems)) { + rules.push({ max: this.maxItems }) + } + if (isValid(this.maxLength)) { + rules.push({ max: this.maxLength }) + } + if (isValid(this.maximum)) { + rules.push({ maximum: this.maximum }) + } + if (isValid(this.minimum)) { + rules.push({ minimum: this.minimum }) + } + if (isValid(this.exclusiveMaximum)) { + rules.push({ exclusiveMaximum: this.exclusiveMaximum }) + } + if (isValid(this.exclusiveMinimum)) { + rules.push({ exclusiveMinimum: this.exclusiveMinimum }) + } + if (isValid(this.pattern)) { + rules.push({ pattern: this.pattern }) + } + if (isValid(this.const)) { + rules.push({ + validator: value => { + return value === this.const ? '' : getMessage('schema.const') + } + }) + } + if (isValid(this.multipleOf)) { + rules.push({ + validator: value => { + return value % this.multipleOf === 0 + ? '' + : getMessage('schema.multipleOf') + } + }) + } + if (isValid(this.maxProperties)) { + rules.push({ + validator: value => { + return Object.keys(value || {}).length <= this.maxProperties + ? '' + : getMessage('schema.maxProperties') + } + }) + } + if (isValid(this.minProperties)) { + rules.push({ + validator: value => { + return Object.keys(value || {}).length >= this.minProperties + ? '' + : getMessage('schema.minProperties') + } + }) + } + if (isValid(this.uniqueItems) && this.uniqueItems) { + rules.push({ + validator: value => { + value = toArr(value) + return value.some((item: any, index: number) => { + for (let start = index; start < value.length; start++) { + if (isEqual(value[start], item)) { + return false + } + } + }) + ? getMessage('schema.uniqueItems') + : '' + } + }) + } + /**剩余校验的都是关联型复杂校验,不抹平,让用户自己处理 */ + if (isValid(this['x-rules'])) { + rules = rules.concat(this['x-rules']) + } + + return rules + } + getExtendsRequired() { + if (isBool(this.required)) { + return this.required + } + } + getExtendsEditable() { + if (isValid(this.editable)) { + return this.editable + } else if (isValid(this['x-props'] && this['x-props'].editable)) { + return this['x-props'].editable + } else if (isValid(this.readOnly)) { + return !this.readOnly + } + } + getExtendsTriggerType() { + const itemProps = this.getExtendsItemProps() + const props = this.getExtendsProps() + const componentProps = this.getExtendsComponentProps() + if (itemProps.triggerType) { + return itemProps.triggerType + } else if (props.triggerType) { + return props.triggerType + } else if (componentProps.triggerType) { + return componentProps.triggerType + } + } + getExtendsItemProps() { + return this['x-item-props'] || {} + } + getExtendsComponent() { + return this['x-component'] + } + getExtendsRenderer() { + return this['x-render'] + } + getExtendsEffect() { + return this['x-effect'] + } + getExtendsProps() { + return this['x-props'] || {} + } + getExtendsComponentProps() { + return { ...this['x-props'], ...this['x-component-props'] } + } + /** + * getters + */ + setProperty(key: string, schema: ISchema) { + this.properties = this.properties || {} + this.properties[key] = new Schema(schema, this) + return this.properties[key] + } + setProperties(properties: SchemaProperties) { + each, ISchema>(properties, (schema, key) => { + this.setProperty(key, schema) + }) + return this.properties + } + setArrayItems(schema: ISchema) { + this.items = new Schema(schema, this) + return this.items + } + toJSON() { + const result: ISchema = this.getSelfProps() + if (isValid(this.properties)) { + result.properties = map(this.properties, schema => { + return schema.toJSON() + }) + } + if (isValid(this.items)) { + result.items = isArr(this.items) + ? this.items.map(schema => schema.toJSON()) + : this.items.toJSON() + } + if (isValid(this.additionalItems)) { + result.additionalItems = this.additionalItems.toJSON() + } + if (isValid(this.additionalProperties)) { + result.additionalProperties = this.additionalProperties.toJSON() + } + if (isValid(this.patternProperties)) { + result.patternProperties = map(this.patternProperties, schema => { + return schema.toJSON() + }) + } + return result + } + + fromJSON(json: ISchema = {}) { + if (typeof json === 'boolean') return json + if (json instanceof Schema) return json + Object.assign(this, json) + if (isValid(json.type)) { + this.type = lowercase(String(json.type)) + } + if (isValid(json['x-component'])) { + this['x-component'] = lowercase(json['x-component']) + } + if (!isEmpty(json.properties)) { + this.properties = map(json.properties, item => { + return new Schema(item, this) + }) + if (isValid(json.additionalProperties)) { + this.additionalProperties = new Schema(json.additionalProperties, this) + } + if (isValid(json.patternProperties)) { + this.patternProperties = map(json.patternProperties, item => { + return new Schema(item, this) + }) + } + } else if (!isEmpty(json.items)) { + this.items = isArr(json.items) + ? map(json.items, item => new Schema(item, this)) + : new Schema(json.items) + if (isValid(json.additionalItems)) { + this.additionalItems = new Schema(json.additionalItems, this) + } + } + return this + } + /** + * tools + */ + isObject() { + return this.type === 'object' + } + isArray() { + return this.type === 'array' + } + + mapProperties(callback?: (schema: Schema, key: string) => any) { + return this.getOrderProperties().map(({ schema, key }) => { + return callback(schema, key) + }) + } + + getOrderProperties() { + return Schema.getOrderProperties(this) + } + + getOrderPatternProperties() { + return Schema.getOrderProperties(this, 'patternProperties') + } + + mapPatternProperties(callback?: (schema: Schema, key: string) => any) { + return this.getOrderPatternProperties().map(({ schema, key }) => { + return callback(schema, key) + }) + } + + static getOrderProperties = ( + schema: ISchema = {}, + propertiesName: string = 'properties' + ) => { + const newSchema = new Schema(schema) + const properties = [] + each(newSchema[propertiesName], (item, key) => { + const index = item['x-index'] + if (typeof index === 'number') { + properties[index] = { + schema: item, + key + } + } else { + properties.push({ schema: item, key }) + } + }) + return properties + } +} diff --git a/packages/react-schema-renderer/src/shared/virtual-render.tsx b/packages/react-schema-renderer/src/shared/virtual-render.tsx new file mode 100644 index 00000000000..671550f921b --- /dev/null +++ b/packages/react-schema-renderer/src/shared/virtual-render.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { globalThisPolyfill } from '@uform/shared' + +const env = { + portalDOM: null +} + +export const render = (element: React.ReactElement) => { + if (globalThisPolyfill['document']) { + env.portalDOM = + env.portalDOM || globalThisPolyfill['document'].createElement('div') + return require('react-dom').createPortal(element, env.portalDOM) + } else { + return + } +} diff --git a/packages/react-schema-renderer/src/types.ts b/packages/react-schema-renderer/src/types.ts new file mode 100644 index 00000000000..6ffedd1265a --- /dev/null +++ b/packages/react-schema-renderer/src/types.ts @@ -0,0 +1,180 @@ +import React from 'react' +import { FormPathPattern } from '@uform/shared' +import { + IFieldState, + IVirtualFieldState, + IMutators, + IFormProps, + IForm, + IFormActions, + IFormAsyncActions +} from '@uform/react' +import { ValidatePatternRules } from '@uform/validator' +import { Schema } from './shared/schema' +export interface ISchemaFieldProps { + path?: FormPathPattern +} + +export type ComponentWithStyleComponent< + ComponentProps +> = React.JSXElementConstructor & { + styledComponentId?: string + displayName?: string +} + +export interface ISchemaFieldComponentProps extends IFieldState { + schema: Schema + mutators: IMutators + form: IForm + renderField: ( + addtionKey: string | number, + reactKey?: string | number + ) => React.ReactElement +} +export interface ISchemaVirtualFieldComponentProps extends IVirtualFieldState { + schema: Schema + form: IForm + children: React.ReactElement[] + renderField: ( + addtionKey: string | number, + reactKey?: string | number + ) => React.ReactElement +} + +export interface ISchemaFieldWrapper { + (Traget: ISchemaFieldComponent): + | React.FC + | React.ClassicComponent +} + +export type ISchemaFieldComponent = ComponentWithStyleComponent< + ISchemaFieldComponentProps +> & { + __WRAPPERS__?: ISchemaFieldWrapper[] +} + +export type ISchemaVirtualFieldComponent = ComponentWithStyleComponent< + ISchemaVirtualFieldComponentProps +> & { + __WRAPPERS__?: ISchemaFieldWrapper[] +} + +export interface ISchemaFormRegistry { + fields: { + [key: string]: ISchemaFieldComponent + } + virtualFields: { + [key: string]: ISchemaVirtualFieldComponent + } + wrappers?: ISchemaFieldWrapper[] + formItemComponent: React.JSXElementConstructor + formComponent: string | React.JSXElementConstructor +} + +export type SchemaMessage = React.ReactNode + +export interface ISchema { + /** base json schema spec**/ + title?: SchemaMessage + description?: SchemaMessage + default?: any + readOnly?: boolean + writeOnly?: boolean + type?: 'string' | 'object' | 'array' | 'number' | string + enum?: Array + const?: any + multipleOf?: number + maximum?: number + exclusiveMaximum?: number + minimum?: number + exclusiveMinimum?: number + maxLength?: number + minLength?: number + pattern?: string | RegExp + maxItems?: number + minItems?: number + uniqueItems?: boolean + maxProperties?: number + minProperties?: number + required?: string[] | boolean + format?: string + /** nested json schema spec **/ + properties?: { + [key: string]: ISchema + } + items?: ISchema | ISchema[] + additionalItems?: ISchema + patternProperties?: { + [key: string]: ISchema + } + additionalProperties?: ISchema + /** extend json schema specs */ + editable?: boolean + ['x-props']?: { [name: string]: any } + ['x-index']?: number + ['x-rules']?: ValidatePatternRules + ['x-component']?: string | React.JSXElementConstructor + ['x-component-props']?: { [name: string]: any } + ['x-render']?: ( + props: T & { + renderComponent: () => React.ReactElement + } + ) => React.ReactElement + ['x-effect']?: ( + dispatch: (type: string, payload: any) => void, + option?: object + ) => { [key: string]: any } +} + +export interface ISchemaFormProps + extends IFormProps< + any, + any, + any, + ISchemaFormActions | ISchemaFormAsyncActions + > { + schema?: ISchema + component?: string | React.JSXElementConstructor + fields?: ISchemaFormRegistry['fields'] + virtualFields?: ISchemaFormRegistry['virtualFields'] + formComponent?: ISchemaFormRegistry['formComponent'] + formItemComponent?: ISchemaFormRegistry['formItemComponent'] +} + +export interface IMarkupSchemaFieldProps extends ISchema { + name?: string +} + +export type MergedFieldComponentProps = Partial< + ISchemaFieldComponentProps & ISchemaVirtualFieldComponentProps +> + +export interface IConnectOptions { + valueName?: string + eventName?: string + defaultProps?: {} + getValueFromEvent?: (event?: any, value?: any) => any + getProps?: ( + componentProps: {}, + fieldProps: MergedFieldComponentProps + ) => {} | void + getComponent?: ( + Target: any, + componentProps: {}, + fieldProps: MergedFieldComponentProps + ) => React.JSXElementConstructor +} + +export interface IConnectProps { + [key: string]: any +} + +export interface ISchemaFormActions extends IFormActions { + getSchema(): Schema + getFormSchema(): Schema +} + +export interface ISchemaFormAsyncActions extends IFormAsyncActions { + getSchema(): Promise + getFormSchema(): Promise +} diff --git a/packages/react-schema-renderer/tsconfig.json b/packages/react-schema-renderer/tsconfig.json new file mode 100644 index 00000000000..1d669c29c46 --- /dev/null +++ b/packages/react-schema-renderer/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib" + }, + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["./src/__tests__/*"] +} diff --git a/packages/types/.npmignore b/packages/react-shared-components/.npmignore similarity index 100% rename from packages/types/.npmignore rename to packages/react-shared-components/.npmignore diff --git a/packages/builder-next/LICENSE.md b/packages/react-shared-components/LICENSE.md similarity index 100% rename from packages/builder-next/LICENSE.md rename to packages/react-shared-components/LICENSE.md diff --git a/packages/react-shared-components/README.md b/packages/react-shared-components/README.md new file mode 100644 index 00000000000..0e8a2b774af --- /dev/null +++ b/packages/react-shared-components/README.md @@ -0,0 +1,2 @@ +# @uform/react-shared +> UForm React通用库 \ No newline at end of file diff --git a/packages/utils/jest.config.js b/packages/react-shared-components/jest.config.js similarity index 100% rename from packages/utils/jest.config.js rename to packages/react-shared-components/jest.config.js diff --git a/packages/utils/package.json b/packages/react-shared-components/package.json similarity index 83% rename from packages/utils/package.json rename to packages/react-shared-components/package.json index c2bb19778ca..a479c36aa14 100644 --- a/packages/utils/package.json +++ b/packages/react-shared-components/package.json @@ -1,6 +1,6 @@ { - "name": "@uform/utils", - "version": "0.4.3", + "name": "@uform/react-shared-components", + "version": "0.4.0", "license": "MIT", "main": "lib", "types": "lib/index.d.ts", @@ -29,7 +29,7 @@ "@babel/runtime": "^7.4.4" }, "dependencies": { - "@uform/types": "^0.4.3", - "camel-case": "^3.0.0" + "@uform/types": "^0.4.0", + "@uform/shared": "^0.4.0" } } diff --git a/packages/react-shared-components/src/ArrayList.tsx b/packages/react-shared-components/src/ArrayList.tsx new file mode 100644 index 00000000000..abf09c3f0b2 --- /dev/null +++ b/packages/react-shared-components/src/ArrayList.tsx @@ -0,0 +1,232 @@ +import React, { createContext, useContext, Fragment, useMemo } from 'react' +import { isNum, isFn, toArr } from '@uform/shared' +import { IArrayList, IArrayListProps } from './types' + +const ArrayContext = createContext({}) + +export const ArrayList: IArrayList = props => { + return ( + + {props.children} + + ) +} + +const useArrayList = (index: number = 0) => { + const { + value, + disabled, + editable, + minItems, + maxItems, + renders, + ...props + } = useContext(ArrayContext) + + const renderWith = ( + name: string, + render: (node: any) => React.ReactElement, + wrapper: any + ) => { + let children: any + if (renders && renders[name]) { + if (isFn(renders[name]) || renders[name].styledComponentId) { + children = renders[name](context.currentIndex) + } else { + children = render(renders[name]) + } + } else { + children = render(renders[name]) + } + if (isFn(wrapper)) { + return wrapper({ ...context, children }) || + } + return children || + } + + const newValue = toArr(value) + + const isEmpty = !newValue || (newValue && newValue.length <= 0) + const isDisable = disabled || editable === false + const allowMoveUp = newValue && newValue.length > 1 && !isDisable + const allowMoveDown = newValue && newValue.length > 1 && !isDisable + const allowRemove = isNum(minItems) ? newValue.length > minItems : !isDisable + const allowAddition = isNum(maxItems) + ? newValue.length <= maxItems + : !isDisable + + const context = { + ...props, + currentIndex: index, + isEmpty, + isDisable, + allowRemove, + allowAddition, + allowMoveDown, + allowMoveUp, + renderWith + } + + return context +} + +const useComponent = (name: string) => { + const { components } = useContext(ArrayContext) + return useMemo(() => { + if (isFn(components[name]) || components[name].styledComponentId) + return components[name] + return (props: {}) => { + return React.isValidElement(components[name]) ? ( + React.cloneElement(components[name], props) + ) : ( + + ) + } + }, []) +} + +const createButtonCls = (props: any = {}, hasText: any) => { + return { + className: `${hasText ? 'has-text' : ''} ${props.className || ''}` + } +} + +ArrayList.useArrayList = useArrayList +ArrayList.useComponent = useComponent + +ArrayList.Remove = ({ children, component, index, ...props }) => { + const { allowRemove, renderWith } = ArrayList.useArrayList(index) + const Button = ArrayList.useComponent(component) + const RemoveIcon = ArrayList.useComponent('RemoveIcon') + if (allowRemove) { + return renderWith( + 'renderRemove', + text => ( + + ), + children + ) + } + + return React.createElement(React.Fragment) +} + +ArrayList.Remove.defaultProps = { + component: 'CircleButton' +} + +ArrayList.Addition = ({ children, component, ...props }) => { + const { allowAddition, renderWith } = ArrayList.useArrayList() + const Button = ArrayList.useComponent(component) + const AdditionIcon = ArrayList.useComponent('AdditionIcon') + + if (allowAddition) { + return renderWith( + 'renderAddition', + text => ( + + ), + children + ) + } + return React.createElement(React.Fragment) +} + +ArrayList.Addition.defaultProps = { + component: 'TextButton' +} + +ArrayList.MoveUp = ({ children, component, index, ...props }) => { + const { allowMoveUp, renderWith } = ArrayList.useArrayList(index) + const Button = ArrayList.useComponent(component) + const MoveUpIcon = ArrayList.useComponent('MoveUpIcon') + + if (allowMoveUp) { + return renderWith( + 'renderMoveUp', + text => ( + + ), + children + ) + } + return React.createElement(React.Fragment) +} + +ArrayList.MoveUp.defaultProps = { + component: 'CircleButton' +} + +ArrayList.MoveDown = ({ children, component, index, ...props }) => { + const { allowMoveDown, renderWith } = ArrayList.useArrayList(index) + const Button = ArrayList.useComponent(component) + const MoveUpIcon = ArrayList.useComponent('MoveDownIcon') + + if (allowMoveDown) { + return renderWith( + 'renderMoveDown', + text => ( + + ), + children + ) + } + return React.createElement(React.Fragment) +} + +ArrayList.MoveDown.defaultProps = { + component: 'CircleButton' +} + +ArrayList.Empty = ({ children, component, ...props }) => { + const { allowAddition, isEmpty, renderWith } = ArrayList.useArrayList() + const Button = ArrayList.useComponent(component) + const AdditionIcon = ArrayList.useComponent('AdditionIcon') + let addtion: any + if (allowAddition) { + addtion = renderWith('renderAddition', text => ( + + )) + } + + if (isEmpty) { + return renderWith( + 'renderEmpty', + text => { + return ( + + + {text} + {addtion} + + ) + }, + children + ) + } + return React.createElement(React.Fragment) +} + +ArrayList.Empty.defaultProps = { + component: 'TextButton' +} diff --git a/packages/react-shared-components/src/PasswordStrength.tsx b/packages/react-shared-components/src/PasswordStrength.tsx new file mode 100644 index 00000000000..4495f10c30f --- /dev/null +++ b/packages/react-shared-components/src/PasswordStrength.tsx @@ -0,0 +1,156 @@ +import React, { Fragment } from 'react' +import { IPasswordStrengthProps } from './types' +import { isFn } from '@uform/shared' + +var isNum = function(c) { + return c >= 48 && c <= 57 +} +var isLower = function(c) { + return c >= 97 && c <= 122 +} +var isUpper = function(c) { + return c >= 65 && c <= 90 +} +var isSymbol = function(c) { + return !(isLower(c) || isUpper(c) || isNum(c)) +} +var isLetter = function(c) { + return isLower(c) || isUpper(c) +} + +const getStrength = val => { + if (!val) return 0 + let num = 0 + let lower = 0 + let upper = 0 + let symbol = 0 + let MNS = 0 + let rep = 0 + let repC = 0 + let consecutive = 0 + let sequential = 0 + const len = () => num + lower + upper + symbol + const callme = () => { + var re = num > 0 ? 1 : 0 + re += lower > 0 ? 1 : 0 + re += upper > 0 ? 1 : 0 + re += symbol > 0 ? 1 : 0 + if (re > 2 && len() >= 8) { + return re + 1 + } else { + return 0 + } + } + for (var i = 0; i < val.length; i++) { + var c = val.charCodeAt(i) + if (isNum(c)) { + num++ + if (i !== 0 && i !== val.length - 1) { + MNS++ + } + if (i > 0 && isNum(val.charCodeAt(i - 1))) { + consecutive++ + } + } else if (isLower(c)) { + lower++ + if (i > 0 && isLower(val.charCodeAt(i - 1))) { + consecutive++ + } + } else if (isUpper(c)) { + upper++ + if (i > 0 && isUpper(val.charCodeAt(i - 1))) { + consecutive++ + } + } else { + symbol++ + if (i !== 0 && i !== val.length - 1) { + MNS++ + } + } + var exists = false + for (var j = 0; j < val.length; j++) { + if (val[i] === val[j] && i !== j) { + exists = true + repC += Math.abs(val.length / (j - i)) + } + } + if (exists) { + rep++ + var unique = val.length - rep + repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC) + } + if (i > 1) { + var last1 = val.charCodeAt(i - 1) + var last2 = val.charCodeAt(i - 2) + if (isLetter(c)) { + if (isLetter(last1) && isLetter(last2)) { + var v = val.toLowerCase() + var vi = v.charCodeAt(i) + var vi1 = v.charCodeAt(i - 1) + var vi2 = v.charCodeAt(i - 2) + if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) { + sequential++ + } + } + } else if (isNum(c)) { + if (isNum(last1) && isNum(last2)) { + if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { + sequential++ + } + } + } else { + if (isSymbol(last1) && isSymbol(last2)) { + if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { + sequential++ + } + } + } + } + } + let sum = 0 + let length = len() + sum += 4 * length + if (lower > 0) { + sum += 2 * (length - lower) + } + if (upper > 0) { + sum += 2 * (length - upper) + } + if (num !== length) { + sum += 4 * num + } + sum += 6 * symbol + sum += 2 * MNS + sum += 2 * callme() + if (length === lower + upper) { + sum -= length + } + if (length === num) { + sum -= num + } + sum -= repC + sum -= 2 * consecutive + sum -= 3 * sequential + sum = sum < 0 ? 0 : sum + sum = sum > 100 ? 100 : sum + + if (sum >= 80) { + return 100 + } else if (sum >= 60) { + return 80 + } else if (sum >= 40) { + return 60 + } else if (sum >= 20) { + return 40 + } else { + return 20 + } +} + +export const PasswordStrength: React.FC = props => { + if (isFn(props.children)) { + return props.children(getStrength(String(props.value))) + } else { + return {props.children} + } +} diff --git a/packages/react-shared-components/src/PreviewText.tsx b/packages/react-shared-components/src/PreviewText.tsx new file mode 100644 index 00000000000..b2ddca4e642 --- /dev/null +++ b/packages/react-shared-components/src/PreviewText.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { IPreviewTextProps } from './types' + +export const PreviewText: React.FC = props => { + let value: any + if (props.dataSource && props.dataSource.length) { + let find = props.dataSource.filter(({ value }) => + Array.isArray(props.value) + ? props.value.some(val => val == value) + : props.value == value + ) + value = find.map(item => item.label).join(' , ') + } else { + value = Array.isArray(props.value) + ? props.value.join(' ~ ') + : String( + props.value === undefined || props.value === null ? '' : props.value + ) + } + return ( +

    + {props.addonBefore ? ' ' + props.addonBefore : ''} + {props.innerBefore ? ' ' + props.innerBefore : ''} + {props.addonTextBefore ? ' ' + props.addonTextBefore : ''} + {!value ? 'N/A' : value} + {props.addonTextAfter ? ' ' + props.addonTextAfter : ''} + {props.innerAfter ? ' ' + props.innerAfter : ''} + {props.addonAfter ? ' ' + props.addonAfter : ''} +

    + ) +} diff --git a/packages/react-shared-components/src/index.ts b/packages/react-shared-components/src/index.ts new file mode 100644 index 00000000000..c7a46558638 --- /dev/null +++ b/packages/react-shared-components/src/index.ts @@ -0,0 +1,4 @@ +export * from './ArrayList' +export * from './PreviewText' +export * from './PasswordStrength' +export * from './types' diff --git a/packages/react-shared-components/src/types.ts b/packages/react-shared-components/src/types.ts new file mode 100644 index 00000000000..eb4949ed662 --- /dev/null +++ b/packages/react-shared-components/src/types.ts @@ -0,0 +1,88 @@ +export type IArrayList = React.FC & { + Remove: React.FC + Addition: React.FC + MoveUp: React.FC + MoveDown: React.FC + Empty: React.FC + useComponent: (name: string) => ReactComponent + useArrayList: (index?: number) => IArrayListProps & ArrayListInfo +} + +interface ArrayListInfo { + currentIndex: number + isEmpty: boolean + isDisable: boolean + allowRemove: boolean + allowAddition: boolean + allowMoveDown: boolean + allowMoveUp: boolean + renderWith: ( + name: string, + render: (node: any) => React.ReactElement, + wrapper?: any + ) => any +} + +type ReactComponent = React.JSXElementConstructor + +type ReactRenderPropsChildren = + | React.ReactNode + | ((props: T) => React.ReactElement) + +export interface IArrayListProps { + value?: any[] + maxItems?: number + minItems?: number + editable?: boolean + disabled?: boolean + components?: { + CircleButton?: ReactComponent + TextButton?: ReactComponent + AdditionIcon?: ReactComponent + RemoveIcon?: ReactComponent + MoveDownIcon?: ReactComponent + MoveUpIcon?: ReactComponent + } + renders?: { + renderAddition?: ReactRenderPropsChildren + renderEmpty?: ReactRenderPropsChildren + renderMoveDown?: ReactRenderPropsChildren + renderMoveUp?: ReactRenderPropsChildren + renderRemove?: ReactRenderPropsChildren + } + context?: any +} + +export interface IArrayListOperatorProps { + children?: React.ReactElement | ((method: T) => React.ReactElement) +} + +export interface IPreviewTextProps { + className?: React.ReactText + dataSource?: any[] + value?: any + addonBefore?: React.ReactNode + innerBefore?: React.ReactNode + addonTextBefore?: React.ReactNode + addonTextAfter?: React.ReactNode + innerAfter?: React.ReactNode + addonAfter?: React.ReactNode +} + +export interface OperatorButtonProps { + index?: number + children?: ReactRenderPropsChildren + component?: string + onClick?: (event: any) => void +} + +export interface IPasswordStrengthProps { + value?: React.ReactText + children?: ReactRenderPropsChildren +} + +export type IArrayListRemoveProps = OperatorButtonProps +export type IArrayListAdditionProps = OperatorButtonProps +export type IArrayListMoveUpProps = OperatorButtonProps +export type IArrayListMoveDownProps = OperatorButtonProps +export type IArrayListEmptyProps = IArrayListAdditionProps diff --git a/packages/react-shared-components/tsconfig.json b/packages/react-shared-components/tsconfig.json new file mode 100644 index 00000000000..1d669c29c46 --- /dev/null +++ b/packages/react-shared-components/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib" + }, + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["./src/__tests__/*"] +} diff --git a/packages/types/LICENSE.md b/packages/react/LICENSE.md similarity index 100% rename from packages/types/LICENSE.md rename to packages/react/LICENSE.md diff --git a/packages/react/README.md b/packages/react/README.md index 6162875a43d..749d5be34d4 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -1,2 +1,299 @@ # @uform/react -> UForm React实现 \ No newline at end of file + +> UForm React Pure Package + + +### ArrayStringList + +```jsx +import React, { useState } from 'react' +import { + Form, + Field, + FormPath, + createFormActions, + FormSpy, + FormProvider, + FormConsumer, + FormEffectHooks +} from './src' + +const actions = createFormActions() + +const Input = props => ( + + {({ state, mutators }) => ( +
    + + {state.errors} + {state.warnings} +
    + )} +
    +) + +const { onFormInit$, onFormInputChange$, onFieldInputChange$ } = FormEffectHooks + +const App = () => { + const [values, setValues] = useState({}) + const [editable, setEditable] = useState(true) + return ( + +
    { + onFormInit$().subscribe(() => { + console.log('初始化') + }) + onFieldInputChange$().subscribe(state => { + console.log('输入变化', state) + }) + }} + onChange={() => {}} + > + + {({ state, mutators }) => { + return ( +
    + {state.value.map((item, index) => { + return ( + + + + + ) + })} + +
    + ) + }} +
    +
    +
    + ) +} + +ReactDOM.render(, document.getElementById('root')) +``` + +### ArrayObjectList + +```jsx +import React, { useState } from 'react' +import { + Form, + Field, + FormPath, + createFormActions, + FormSpy, + FormProvider, + FormConsumer, + FormEffectHooks +} from './src' + +const actions = createFormActions() + +const Input = props => ( + + {({ state, mutators }) => ( +
    + + {state.errors} + {state.warnings} +
    + )} +
    +) + +const { onFormInit$, onFormInputChange$, onFieldInputChange$ } = FormEffectHooks + +const App = () => { + const [values, setValues] = useState({}) + const [editable, setEditable] = useState(true) + return ( + +
    { + onFormInit$().subscribe(() => { + console.log('初始化') + }) + onFieldInputChange$().subscribe(state => { + console.log('输入变化', state) + }) + }} + onChange={() => {}} + > + + {({ state, mutators }) => { + return ( +
    + {state.value.map((item, index) => { + return ( + + + { + if (value == '123') { + return { + type: 'warning', + message: '这个是一个提示' + } + } + }} + /> + + + ) + })} + +
    + ) + }} +
    +
    +
    + ) +} + +ReactDOM.render(, document.getElementById('root')) +``` + +### Dynamic Object + +```jsx +import React, { useState } from 'react' +import { + Form, + Field, + FormPath, + createFormActions, + FormSpy, + FormProvider, + FormConsumer, + FormEffectHooks +} from './src' + +const actions = createFormActions() + +const Input = props => ( + + {({ state, mutators }) => ( +
    + + {state.errors} + {state.warnings} +
    + )} +
    +) + +const { onFormInit$, onFormInputChange$, onFieldInputChange$ } = FormEffectHooks + +const App = () => { + const [values, setValues] = useState({}) + const [editable, setEditable] = useState(true) + return ( + +
    { + onFormInit$().subscribe(() => { + console.log('初始化') + }) + onFieldInputChange$().subscribe(state => { + console.log('输入变化', state) + }) + }} + onChange={() => {}} + > + + {({ state, mutators }) => { + return ( + + {Object.keys(state.value).map(key => { + if (!mutators.exist(key)) return + return ( + + + + + ) + })} + + + ) + }} + +
    +
    + ) +} + +ReactDOM.render(, document.getElementById('root')) +``` diff --git a/packages/react/package.json b/packages/react/package.json index 1c9aa1b1338..47a8480d8a8 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,12 +1,13 @@ { "name": "@uform/react", - "version": "0.4.3", + "version": "0.4.0", "license": "MIT", "main": "lib", "repository": { "type": "git", "url": "git+https://github.com/alibaba/uform.git" }, + "types": "lib/index.d.ts", "bugs": { "url": "https://github.com/alibaba/uform/issues" }, @@ -14,30 +15,27 @@ "engines": { "npm": ">=3.0.0" }, - "types": "lib/index.d.ts", "scripts": { - "build": "tsc --declaration" + "build": "rm -rf lib && tsc --declaration" }, "devDependencies": { "typescript": "^3.5.2" }, "peerDependencies": { "@babel/runtime": "^7.4.4", + "scheduler": ">=0.11.2", "@types/react": "^16.8.23", "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "react-dom": ">=16.8.0", + "react-eva": "^1.0.0", + "rxjs": "^6.5.1" }, "dependencies": { - "@uform/core": "^0.4.3", - "@uform/types": "^0.4.3", - "@uform/utils": "^0.4.3", - "@uform/validator": "^0.4.3", - "pascal-case": "^2.0.1", - "react-eva": "^1.0.0", - "rxjs-compat": "^6.5.1" + "@uform/core": "^0.4.0", + "@uform/shared": "^0.4.0" }, "publishConfig": { "access": "public" }, "gitHead": "4d068dad6183e8da294a4c899a158326c0b0b050" -} +} \ No newline at end of file diff --git a/packages/react/src/__tests__/actions.spec.tsx b/packages/react/src/__tests__/actions.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/actions.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/consumer.spec.tsx b/packages/react/src/__tests__/consumer.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/consumer.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/effects.spec.tsx b/packages/react/src/__tests__/effects.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/effects.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/field.spec.tsx b/packages/react/src/__tests__/field.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/field.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/form.spec.tsx b/packages/react/src/__tests__/form.spec.tsx new file mode 100644 index 00000000000..041f97923ce --- /dev/null +++ b/packages/react/src/__tests__/form.spec.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import { + Form, + Field, + createFormActions, + FormEffectHooks, + IFieldProps +} from '../index' +import { render } from '@testing-library/react' + +const Input: React.FC = props => ( + + {({ state, mutators }) => ( +
    + + {state.errors} + {state.warnings} +
    + )} +
    +) + +const { onFieldValueChange$ } = FormEffectHooks + +describe('test all apis', () => { + test('Form', async () => { + const actions = createFormActions() + const onSubmitHandler = jest.fn() + const onValidateFailedHandler = jest.fn() + render( +
    { + onFieldValueChange$('aaa').subscribe(fieldState => { + $.setFieldState('aaa', state => { + state.value = 'hello world' + }) + }) + }} + > + +
    + ) + try { + await actions.submit() + } catch (e) { + expect(e).toEqual([{ path: 'aaa', messages: ['This field is required'] }]) + } + actions.setFieldState('aaa', state => { + state.value = '123' + }) + await actions.submit() + expect(onSubmitHandler).toBeCalledWith({ aaa: 'hello world' }) + }) +}) + +describe('major scenes', () => { + //todo +}) + +describe('bugfix', () => { + //todo +}) diff --git a/packages/react/src/__tests__/provider.spec.tsx b/packages/react/src/__tests__/provider.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/provider.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/spy.spec.tsx b/packages/react/src/__tests__/spy.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/spy.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/useDirty.spec.tsx b/packages/react/src/__tests__/useDirty.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/useDirty.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/useField.spec.tsx b/packages/react/src/__tests__/useField.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/useField.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/useForceUpdate.spec.tsx b/packages/react/src/__tests__/useForceUpdate.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/useForceUpdate.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/useForm.spec.tsx b/packages/react/src/__tests__/useForm.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/useForm.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/__tests__/virtual.spec.tsx b/packages/react/src/__tests__/virtual.spec.tsx new file mode 100644 index 00000000000..3764eab2358 --- /dev/null +++ b/packages/react/src/__tests__/virtual.spec.tsx @@ -0,0 +1,12 @@ +describe('test all apis',()=>{ + //todo +}) + +describe('major scenes',()=>{ + //todo +}) + + +describe('bugfix',()=>{ + //todo +}) \ No newline at end of file diff --git a/packages/react/src/components/Field.tsx b/packages/react/src/components/Field.tsx new file mode 100644 index 00000000000..1011f64b6cb --- /dev/null +++ b/packages/react/src/components/Field.tsx @@ -0,0 +1,53 @@ +import React from 'react' +import { useField } from '../hooks/useField' +import { isFn } from '@uform/shared' +import { IMutators, IFieldState } from '@uform/core' +import { IFieldProps } from '../types' +import { getValueFromEvent } from '../shared' + +const createFieldMutators = ( + mutators: IMutators, + props: IFieldProps, + state: IFieldState +): IMutators => { + return { + ...mutators, + change: (...args) => { + args[0] = isFn(props.getValueFromEvent) + ? props.getValueFromEvent(...args) + : args[0] + mutators.change(...args.map(event => getValueFromEvent(event))) + if (props.triggerType === 'onChange') { + mutators.validate() + } + }, + blur: () => { + mutators.blur() + if (props.triggerType === 'onBlur') { + mutators.validate() + } + } + } +} + +export const Field: React.FunctionComponent = props => { + const { state, props: innerProps, mutators, form } = useField(props) + if (!state.visible || !state.display) return + if (isFn(props.children)) { + return props.children({ + form, + state, + props: innerProps, + mutators: createFieldMutators(mutators, props, state) + }) + } else { + return {props.children} + } +} + +Field.displayName = 'ReactInternalField' + +Field.defaultProps = { + path: '', + triggerType: 'onChange' +} diff --git a/packages/react/src/components/Form.tsx b/packages/react/src/components/Form.tsx new file mode 100644 index 00000000000..948feb95544 --- /dev/null +++ b/packages/react/src/components/Form.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { isFn } from '@uform/shared' +import { useForm } from '../hooks/useForm' +import FormContext from '../context' +import { IFormProps, IFormActions, IFormAsyncActions } from '../types' + +export const Form: React.FunctionComponent< + IFormProps +> = (props = {}) => { + const form = useForm(props) + return ( + + {isFn(props.children) ? props.children(form) : props.children} + + ) +} + +Form.displayName = 'ReactInternalForm' diff --git a/packages/react/src/components/FormConsumer.tsx b/packages/react/src/components/FormConsumer.tsx new file mode 100644 index 00000000000..49ac2963a19 --- /dev/null +++ b/packages/react/src/components/FormConsumer.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import { isFn, deprecate } from '@uform/shared' +import { FormSpy } from './FormSpy' +import { IForm, LifeCycleTypes } from '@uform/core' +import { IFormConsumerProps, IFormConsumerAPI } from '../types' + +const transformStatus = (type: string) => { + switch (type) { + case LifeCycleTypes.ON_FORM_INIT: + return 'initialize' + case LifeCycleTypes.ON_FORM_SUBMIT_START: + return 'submitting' + case LifeCycleTypes.ON_FORM_SUBMIT_END: + return 'submitted' + case LifeCycleTypes.ON_FORM_VALIDATE_START: + return 'validating' + case LifeCycleTypes.ON_FORM_VALIDATE_END: + return 'validated' + default: + return type + } +} + +const transformFormAPI = (api: IForm, type: string): IFormConsumerAPI => { + deprecate('FormConsumer', 'Please use FormSpy Component.') + return { + status: transformStatus(type), + state: api.getFormState(), + submit: api.submit, + reset: api.reset + } +} + +export const FormConsumer: React.FunctionComponent< + IFormConsumerProps +> = props => { + return ( + + {({ form, type }) => { + return isFn(props.children) + ? props.children(transformFormAPI(form, type)) + : props.children + }} + + ) +} diff --git a/packages/react/src/components/FormProvider.tsx b/packages/react/src/components/FormProvider.tsx new file mode 100644 index 00000000000..ac73cc3cbdd --- /dev/null +++ b/packages/react/src/components/FormProvider.tsx @@ -0,0 +1,12 @@ +import React, { useMemo } from 'react' +import { BroadcastContext } from '../context' +import { Broadcast } from '@uform/shared' + +const { Provider } = BroadcastContext + +export const FormProvider: React.FunctionComponent = props => { + const broadcast = useMemo(() => { + return new Broadcast() + }, []) + return {props.children} +} diff --git a/packages/react/src/components/FormSpy.tsx b/packages/react/src/components/FormSpy.tsx new file mode 100644 index 00000000000..444ac63cdbd --- /dev/null +++ b/packages/react/src/components/FormSpy.tsx @@ -0,0 +1,73 @@ +import { + useContext, + useMemo, + useRef, + useEffect, + useCallback, + useState, + useReducer +} from 'react' +import { FormHeartSubscriber, LifeCycleTypes } from '@uform/core' +import { isFn, isStr, FormPath, isArr } from '@uform/shared' +import { IFormSpyProps } from '../types' +import FormContext, { BroadcastContext } from '../context' + +export const FormSpy: React.FunctionComponent = props => { + const broadcast = useContext(BroadcastContext) + const form = useContext(FormContext) + const initializedRef = useRef(false) + const [type, setType] = useState(LifeCycleTypes.ON_FORM_INIT) + const [state, dispatch] = useReducer( + (state, action) => props.reducer(state, action, form), + {} + ) + const subscriber = useCallback(({ type, payload }) => { + if (initializedRef.current) return + if (isStr(props.selector) && FormPath.parse(props.selector).match(type)) { + setType(type) + dispatch({ + type, + payload + }) + } else if (isArr(props.selector) && props.selector.indexOf(type) > -1) { + setType(type) + dispatch({ + type, + payload + }) + } + }, []) + useMemo(() => { + initializedRef.current = true + if (form) { + form.subscribe(subscriber) + } else if (broadcast) { + broadcast.subscribe(subscriber) + } + initializedRef.current = false + }, []) + useEffect(() => { + return () => { + if (form) { + form.unsubscribe(subscriber) + } else if (broadcast) { + broadcast.unsubscribe(subscriber) + } + } + }, []) + if (isFn(props.children)) { + const formApi = form ? form : broadcast && broadcast.getContext() + return props.children({ form: formApi, type, state }) + } else { + return props.children + } +} + +FormSpy.displayName = 'ReactInternalFormSpy' + +FormSpy.defaultProps = { + selector: `*`, + reducer: (state, action) => { + return state + } +} diff --git a/packages/react/src/components/VirtualField.tsx b/packages/react/src/components/VirtualField.tsx new file mode 100644 index 00000000000..84bacbdb4fe --- /dev/null +++ b/packages/react/src/components/VirtualField.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import { useVirtualField } from '../hooks/useVirtualField' +import { isFn } from '@uform/shared' +import { IVirtualFieldProps } from '../types' + +export const VirtualField: React.FunctionComponent< + IVirtualFieldProps +> = props => { + const { state, props: innerProps, form } = useVirtualField(props) + if (!state.visible || !state.display) return + if (isFn(props.children)) { + return props.children({ + form, + state, + props: innerProps + }) + } else { + return props.children + } +} + +VirtualField.displayName = 'ReactInternalVirtualField' + +VirtualField.defaultProps = { + path: '' +} diff --git a/packages/react/src/context.ts b/packages/react/src/context.ts new file mode 100644 index 00000000000..4097188b652 --- /dev/null +++ b/packages/react/src/context.ts @@ -0,0 +1,7 @@ +import { createContext } from 'react' +import { Broadcast } from '@uform/shared' +import { IForm } from '@uform/core' + +export const BroadcastContext = createContext(null) + +export default createContext(null) diff --git a/packages/react/src/decorators/connect.ts b/packages/react/src/decorators/connect.ts deleted file mode 100644 index 040870a4a46..00000000000 --- a/packages/react/src/decorators/connect.ts +++ /dev/null @@ -1,203 +0,0 @@ -import React, { PureComponent } from 'react' -import { ISchema, Dispatcher } from '@uform/types' -import { isArr, isFn, each } from '../utils' -import { IEventTargetOption, IFieldProps } from '../type' - -const isEvent = (candidate: React.SyntheticEvent): boolean => - !!(candidate && candidate.stopPropagation && candidate.preventDefault) - -const isReactNative = - typeof window !== 'undefined' && - window.navigator && - window.navigator.product && - window.navigator.product === 'ReactNative' - -const getSelectedValues = (options?: IEventTargetOption[]) => { - const result = [] - if (options) { - for (let index = 0; index < options.length; index++) { - const option = options[index] - if (option.selected) { - result.push(option.value) - } - } - } - return result -} - -// TODO 需要 any ? -const getValue = ( - event: React.SyntheticEvent | any, - isReactNative: boolean -) => { - if (isEvent(event)) { - if ( - !isReactNative && - event.nativeEvent && - event.nativeEvent.text !== undefined - ) { - return event.nativeEvent.text - } - if (isReactNative && event.nativeEvent !== undefined) { - return event.nativeEvent.text - } - - const detypedEvent = event - const { - target: { type, value, checked, files }, - dataTransfer - } = detypedEvent - - if (type === 'checkbox') { - return !!checked - } - - if (type === 'file') { - return files || (dataTransfer && dataTransfer.files) - } - - if (type === 'select-multiple') { - return getSelectedValues(event.target.options) - } - return value - } - return event -} - -const createEnum = (enums: any, enumNames: string | any[]) => { - if (isArr(enums)) { - return enums.map((item, index) => { - if (typeof item === 'object') { - return { - ...item - } - } else { - return { - ...item, - label: isArr(enumNames) ? enumNames[index] || item : item, - value: item - } - } - }) - } - - return [] -} - -const bindEffects = ( - props: IConnectProps, - effect: ISchema['x-effect'], - dispatch: Dispatcher -) => { - each(effect(dispatch, { ...props }), (event, key) => { - const prevEvent = key === 'onChange' ? props[key] : undefined - props[key] = (...args) => { - if (isFn(prevEvent)) { - prevEvent(...args) - } - if (isFn(event)) { - return event(...args) - } - } - }) - return props -} - -// 这个不枚举了,因为是 x-props 的 -export interface IConnectProps extends IFieldProps { - disabled?: boolean - readOnly?: boolean - showTime?: boolean - dataSource?: any[] - [name: string]: any -} - -export interface IConnectOptions { - valueName?: string - eventName?: string - defaultProps?: object - getValueFromEvent?: (event?: any, value?: any) => any - getProps?: ( - props: IConnectProps, - componentProps: IFieldProps - ) => IConnectProps | void - getComponent?: ( - Target: T, - props, - { - editable, - name - }: { editable: boolean | ((name: string) => boolean); name: string } - ) => T -} - -export const connect = >( - opts?: IConnectOptions -) => (Target: T): React.ComponentClass => { - opts = { - valueName: 'value', - eventName: 'onChange', - ...opts - } - return class extends PureComponent { - render() { - const { value, name, mutators, schema, editable } = this.props - - let props = { - ...opts.defaultProps, - ...schema['x-props'], - [opts.valueName]: value, - [opts.eventName]: (event, ...args) => { - mutators.change( - opts.getValueFromEvent - ? opts.getValueFromEvent.call( - { props: schema['x-props'] || {} }, - event, - ...args - ) - : getValue(event, isReactNative) - ) - } - } as IConnectProps - - if (editable !== undefined) { - if (isFn(editable)) { - if (!editable(name)) { - props.disabled = true - props.readOnly = true - } - } else if (editable === false) { - props.disabled = true - props.readOnly = true - } - } - - if (isFn(schema['x-effect'])) { - props = bindEffects(props, schema['x-effect'], mutators.dispatch) - } - - if (isFn(opts.getProps)) { - const newProps = opts.getProps(props, this.props) - if (newProps !== undefined) { - // @ts-ignore TODO - props = newProps - } - } - - if (isArr(schema.enum) && !props.dataSource) { - props.dataSource = createEnum(schema.enum, schema.enumNames) - } - - if (props.editable !== undefined) { - delete props.editable - } - - return React.createElement( - isFn(opts.getComponent) - ? opts.getComponent(Target, props, this.props) - : Target, - props - ) - } - } -} diff --git a/packages/react/src/decorators/markup.tsx b/packages/react/src/decorators/markup.tsx deleted file mode 100644 index 66e0350235c..00000000000 --- a/packages/react/src/decorators/markup.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { Component, useContext } from 'react' -import { createPortal } from 'react-dom' - -import { ISchemaFormProps } from '../type' -import { - createHOC, - schemaIs, - clone, - filterSchemaPropertiesAndReactChildren -} from '../utils' -import { MarkupContext } from '../shared/context' - -let nonameId = 0 - -const getRadomName = () => { - return `UFORM_NO_NAME_FIELD_$${nonameId++}` -} - -export const SchemaField = props => { - const parent = useContext(MarkupContext) - if (schemaIs(parent, 'object')) { - const name = props.name || getRadomName() - parent.properties = parent.properties || {} - parent.properties[name] = clone( - props, - filterSchemaPropertiesAndReactChildren - ) - return ( - - {props.children} - - ) - } else if (schemaIs(parent, 'array')) { - parent.items = clone(props, filterSchemaPropertiesAndReactChildren) - return ( - - {props.children} - - ) - } else { - return props.children || - } -} - -export const SchemaMarkup = createHOC((options, SchemaForm) => { - return class extends Component { - public static displayName = 'SchemaMarkupParser' - - public portalRoot = document.createElement('div') - - public render() { - const { - children, - initialValues, - defaultValue, - value, - schema, - ...others - } = this.props - - let alreadyHasSchema = false - let finalSchema = {} - if (schema) { - alreadyHasSchema = true - finalSchema = schema - } else { - finalSchema = { type: 'object' } - } - - nonameId = 0 - - return ( - - {!alreadyHasSchema && - // 只是为了去收集 schema 数据 - createPortal( - - {children} - , - this.portalRoot - )} - - {children} - - - ) - } - } -}) diff --git a/packages/react/src/hooks/useDirty.ts b/packages/react/src/hooks/useDirty.ts new file mode 100644 index 00000000000..f49b3af82fb --- /dev/null +++ b/packages/react/src/hooks/useDirty.ts @@ -0,0 +1,17 @@ +import React from 'react' +import { isEqual } from '@uform/shared' + +export const useDirty = (input: any = {}, keys: string[] = []) => { + const ref = React.useRef({ data: {...input}, dirtys: {}, num: 0 }) + ref.current.num = 0 + keys.forEach(key => { + if (!isEqual(input[key], ref.current.data[key])) { + ref.current.data[key] = input[key] + ref.current.dirtys[key] = true + ref.current.num++ + } else { + ref.current.dirtys[key] = false + } + }) + return ref.current +} diff --git a/packages/react/src/hooks/useField.ts b/packages/react/src/hooks/useField.ts new file mode 100644 index 00000000000..4d6b653c259 --- /dev/null +++ b/packages/react/src/hooks/useField.ts @@ -0,0 +1,76 @@ +import { useMemo, useEffect, useRef, useContext } from 'react' +import { each } from '@uform/shared' +import { IFieldStateProps, IFieldState, IForm, IField } from '@uform/core' +import { raf } from '../shared' +import { useDirty } from './useDirty' +import { useForceUpdate } from './useForceUpdate' +import { IFieldHook } from '../types' +import FormContext from '../context' + +export const useField = (options: IFieldStateProps): IFieldHook => { + const forceUpdate = useForceUpdate() + const dirty = useDirty(options, ['props', 'rules', 'required', 'editable']) + const ref = useRef<{ field: IField; unmounted: boolean }>({ + field: null, + unmounted: false + }) + const form = useContext(FormContext) + if (!form) { + throw new Error('Form object cannot be found from context.') + } + useMemo(() => { + let initialized = false + ref.current.field = form.registerField({ + ...options, + onChange() { + if (ref.current.unmounted) return + /** + * 同步Field状态只需要forceUpdate一下触发重新渲染,因为字段状态全部代理在uform core内部 + */ + if (initialized) { + raf(() => { + forceUpdate() + }) + } + } + }) + initialized = true + }, []) + + useEffect(() => { + if (dirty.num > 0) { + ref.current.field.setState((state: IFieldState) => { + each(dirty.dirtys, (result, key) => { + if (result) { + state[key] = options[key] + } + }) + }) + } + }) + + useEffect(() => { + ref.current.field.unsafe_setSourceState(state => { + state.mounted = true + }) + return () => { + ref.current.unmounted = true + ref.current.field.setState((state: IFieldState) => { + state.unmounted = true + }) + } + }, []) + + const state = ref.current.field.getState() + return { + form, + state: { + ...state, + errors: state.errors.join(', ') + }, + mutators: form.createMutators(ref.current.field), + props: state.props + } +} + +export default useField diff --git a/packages/react/src/hooks/useForceUpdate.ts b/packages/react/src/hooks/useForceUpdate.ts new file mode 100644 index 00000000000..193c8916af5 --- /dev/null +++ b/packages/react/src/hooks/useForceUpdate.ts @@ -0,0 +1,18 @@ +import { useCallback, useState } from 'react'; + +// Returning a new object reference guarantees that a before-and-after +// equivalence check will always be false, resulting in a re-render, even +// when multiple calls to forceUpdate are batched. + +export function useForceUpdate(): () => void { + const [ , dispatch ] = useState<{}>(Object.create(null)); + + // Turn dispatch(required_parameter) into dispatch(). + const memoizedDispatch = useCallback( + (): void => { + dispatch(Object.create(null)); + }, + [ dispatch ], + ); + return memoizedDispatch; +} \ No newline at end of file diff --git a/packages/react/src/hooks/useForm.ts b/packages/react/src/hooks/useForm.ts new file mode 100644 index 00000000000..3958e2aea7e --- /dev/null +++ b/packages/react/src/hooks/useForm.ts @@ -0,0 +1,128 @@ +import { useMemo, useEffect, useRef, useContext } from 'react' +import { + createForm, + IFormCreatorOptions, + LifeCycleTypes, + FormLifeCycle, + IForm, + IModel, + isStateModel, + IFormState +} from '@uform/core' +import { useDirty } from './useDirty' +import { useEva } from 'react-eva' +import { IFormProps } from '../types' +import { BroadcastContext } from '../context' +import { createFormEffects, createFormActions } from '../shared' +import { isValid } from '@uform/shared' +const FormHookSymbol = Symbol('FORM_HOOK') + +const useInternalForm = ( + options: IFormCreatorOptions & { form?: IForm } = {} +) => { + const dirty = useDirty(options, ['initialValues', 'values', 'editable']) + const alreadyHaveForm = !!options.form + const alreadyHaveHookForm = options.form && options.form[FormHookSymbol] + const form = useMemo(() => { + return alreadyHaveForm ? options.form : createForm(options) + }, []) + + useEffect(() => { + if (alreadyHaveHookForm) return + if (dirty.num > 0) { + form.setFormState(state => { + if (dirty.dirtys.values && isValid(options.values)) { + state.values = options.values + } + if (dirty.dirtys.initialValues && isValid(options.initialValues)) { + state.values = options.initialValues + state.initialValues = options.initialValues + } + if (dirty.dirtys.editable && isValid(options.editable)) { + state.editable = options.editable + } + }) + } + }) + + useEffect(() => { + if (alreadyHaveHookForm) return + form.setFormState(state => { + state.mounted = true + }) + return () => { + form.setFormState(state => { + state.unmounted = true + }) + } + }, []) + ;(form as any)[FormHookSymbol] = true + + return form +} + +export const useForm = < + Value = any, + DefaultValue = any, + EffectPayload = any, + EffectAction = any +>( + props: IFormProps +) => { + const actionsRef = useRef(null) + actionsRef.current = + actionsRef.current || props.actions || createFormActions() + const broadcast = useContext(BroadcastContext) + const { implementActions, dispatch } = useEva({ + actions: actionsRef.current, + effects: createFormEffects(props.effects, actionsRef.current) + }) + const form = useInternalForm({ + form: props.form, + values: props.value, + initialValues: props.initialValues, + useDirty: props.useDirty, + editable: props.editable, + validateFirst: props.validateFirst, + lifecycles: [ + new FormLifeCycle( + ({ type, payload }: { type: string; payload: IModel }) => { + dispatch.lazy(type, () => { + return isStateModel(payload) ? payload.getState() : payload + }) + if (type === LifeCycleTypes.ON_FORM_INPUT_CHANGE) { + if (props.onChange) { + props.onChange( + isStateModel(payload) + ? payload.getState((state: IFormState) => state.values) + : {} + ) + } + } + if (broadcast) { + broadcast.notify({ type, payload }) + } + } + ), + new FormLifeCycle( + LifeCycleTypes.ON_FORM_WILL_INIT, + (payload: IModel, form: IForm) => { + const actions = { + ...form, + dispatch: form.notify + } + if (broadcast) { + broadcast.setContext(actions) + } + implementActions(actions) + } + ) + ], + onReset: props.onReset, + onSubmit: props.onSubmit, + onValidateFailed: props.onValidateFailed + }) + return form +} + +export default useForm diff --git a/packages/react/src/hooks/useVirtualField.ts b/packages/react/src/hooks/useVirtualField.ts new file mode 100644 index 00000000000..3f5f21645dc --- /dev/null +++ b/packages/react/src/hooks/useVirtualField.ts @@ -0,0 +1,71 @@ +import { useMemo, useEffect, useRef, useContext } from 'react' +import { each } from '@uform/shared' +import { IVirtualFieldStateProps, IVirtualFieldState, IForm } from '@uform/core' +import { useDirty } from './useDirty' +import { useForceUpdate } from './useForceUpdate' +import { raf } from '../shared' +import { IVirtualFieldHook } from '../types' +import FormContext from '../context' + +export const useVirtualField = ( + options: IVirtualFieldStateProps +): IVirtualFieldHook => { + const forceUpdate = useForceUpdate() + const dirty = useDirty(options, ['props']) + const ref = useRef({ + field: null, + unmounted: false + }) + const form = useContext(FormContext) + if (!form) { + throw new Error('Form object cannot be found from context.') + } + useMemo(() => { + let initialized = false + ref.current.field = form.registerVirtualField({ + ...options, + onChange() { + if (ref.current.unmounted) return + /** + * 同步Field状态只需要forceUpdate一下触发重新渲染,因为字段状态全部代理在uform core内部 + */ + if (initialized) { + raf(() => { + forceUpdate() + }) + } + } + }) + initialized = true + }, []) + + useEffect(() => { + if (dirty.num > 0) { + ref.current.field.setState((state: IVirtualFieldState) => { + each(dirty.dirtys, (result, key) => { + if (result) { + state[key] = options[key] + } + }) + }) + } + }) + + useEffect(() => { + return () => { + ref.current.unmounted = true + ref.current.field.setState((state: IVirtualFieldState) => { + state.unmounted = true + }) + } + }, []) + + const state = ref.current.field.getState() + return { + state, + form, + props: state.props + } +} + +export default useVirtualField diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts new file mode 100644 index 00000000000..7427cdcb193 --- /dev/null +++ b/packages/react/src/index.ts @@ -0,0 +1,24 @@ +import { + FormEffectHooks, + createEffectHook, + createFormActions, + createAsyncFormActions +} from './shared' +export * from '@uform/core' +export * from './components/Form' +export * from './components/Field' +export * from './components/VirtualField' +export * from './components/FormSpy' +export * from './components/FormProvider' +export * from './components/FormConsumer' +export * from './hooks/useForm' +export * from './hooks/useField' +export * from './hooks/useVirtualField' +export * from './types' + +export { + FormEffectHooks, + createEffectHook, + createFormActions, + createAsyncFormActions +} diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx deleted file mode 100644 index 0523bb43043..00000000000 --- a/packages/react/src/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import * as React from 'react' -import { setLocale, setLanguage } from '@uform/validator' -import { FormPath } from '@uform/core' -import { IFormActions, IAsyncFormActions } from '@uform/types' -import { createActions, createAsyncActions } from 'react-eva' - -import { - OriginForm, - registerFieldMiddleware, - registerFormFieldPropsTransformer, - registerFormField, - registerFormFields, - registerFormWrapper -} from './shared/core' -import { FormField } from './state/field' -import { calculateSchemaInitialValues } from './utils' -import { SchemaField, SchemaMarkup } from './decorators/markup' -import initialize from './initialize' -import { ISchemaFormProps } from './type' - -export * from './type' -export * from './shared/virtualbox' -export * from './decorators/connect' -export * from './shared/broadcast' -export * from './shared/array' - -initialize() - -export const SchemaForm = SchemaMarkup()( - React.forwardRef((props: ISchemaFormProps, ref: React.Ref) => { - // 这个时候就有 schema 数据 - const { children, className, ...others } = props - return ( - -
    - -
    - {children} -
    - ) - }) -) - -export const Field = SchemaField - -export const setValidationLocale = setLocale - -export const setValidationLanguage = setLanguage - -export const createFormActions = (): IFormActions => - createActions( - 'getFormState', - 'getFieldState', - 'setFormState', - 'setFieldState', - 'getSchema', - 'reset', - 'submit', - 'validate', - 'dispatch' - ) - -export const createAsyncFormActions = (): IAsyncFormActions => - createAsyncActions( - 'getFormState', - 'getFieldState', - 'setFormState', - 'setFieldState', - 'getSchema', - 'reset', - 'submit', - 'validate', - 'dispatch' - ) - -export { - registerFormField, - registerFormFields, - registerFormWrapper, - registerFieldMiddleware, - registerFormFieldPropsTransformer, - calculateSchemaInitialValues, - FormPath -} - -SchemaForm.displayName = 'SchemaForm' - -export default SchemaForm diff --git a/packages/react/src/initialize/index.ts b/packages/react/src/initialize/index.ts deleted file mode 100644 index 7d3fcdf1949..00000000000 --- a/packages/react/src/initialize/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import initialObject from './object' -import initialRender from './render' -import initialVirtualbox from './virtualbox' -import { initialContainer } from '../shared/core' -import intialState from '../state' - -export default () => { - initialContainer() - intialState() - initialObject() - initialRender() - initialVirtualbox() -} diff --git a/packages/react/src/initialize/object.tsx b/packages/react/src/initialize/object.tsx deleted file mode 100644 index b499e06588d..00000000000 --- a/packages/react/src/initialize/object.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import { IFieldProps } from '../type' -import { registerFormField } from '../shared/core' -import { each } from '../utils' - -export default () => - registerFormField( - 'object', - class ObjectField extends React.Component { - public render() { - return this.renderProperties() - } - private renderProperties() { - const { renderField, getOrderProperties } = this.props - const properties = getOrderProperties() - const children = [] - each(properties, ({ key }: { key?: string } = {}) => { - if (key) { - children.push(renderField(key, true)) - } - }) - return children - } - } - ) diff --git a/packages/react/src/initialize/render.tsx b/packages/react/src/initialize/render.tsx deleted file mode 100644 index 4f2e99f7ed3..00000000000 --- a/packages/react/src/initialize/render.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { IFieldProps } from '../type' -import { registerFieldRenderer } from '../shared/core' -import { isFn } from '../utils' - -export default () => - registerFieldRenderer( - class extends React.Component { - public static displayName = 'FieldXRenderer' - public render() { - if (isFn(this.props.schema['x-render'])) { - return this.props.schema['x-render'](this.props) - } - return - } - } - ) diff --git a/packages/react/src/initialize/virtualbox.tsx b/packages/react/src/initialize/virtualbox.tsx deleted file mode 100644 index 3fba8214da2..00000000000 --- a/packages/react/src/initialize/virtualbox.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import { IFieldProps } from '../type' -import { registerFormField } from '../shared/core' -import { registerVirtualboxFlag } from '../utils' - -export default () => { - registerVirtualboxFlag('slot') - registerFormField( - 'slot', - class extends React.Component { - public static displayName = 'FormSlot' - public render() { - const { schema } = this.props - return {schema.renderChildren} - } - } - ) -} diff --git a/packages/react/src/shared.ts b/packages/react/src/shared.ts new file mode 100644 index 00000000000..3bc79a02713 --- /dev/null +++ b/packages/react/src/shared.ts @@ -0,0 +1,270 @@ +import { isFn, FormPath, globalThisPolyfill } from '@uform/shared' +import { IFormEffect, IFormActions, IFormAsyncActions } from './types' +import { Observable } from 'rxjs/internal/Observable' +import { filter } from 'rxjs/internal/operators' +import { createActions, createAsyncActions } from 'react-eva' +import { + LifeCycleTypes, + IFormState, + FormGraph, + IFieldState, + IVirtualFieldState +} from '@uform/core' + +export const createFormActions = (): IFormActions => { + if (env.currentActions) { + return env.currentActions + } + return createActions( + 'submit', + 'reset', + 'validate', + 'setFormState', + 'getFormState', + 'setFieldState', + 'getFieldState', + 'getFormGraph', + 'setFormGraph', + 'subscribe', + 'unsubscribe', + 'notify', + 'dispatch', + 'setFieldValue', + 'getFieldValue', + 'setFieldInitialValue', + 'getFieldInitialValue' + ) as IFormActions +} + +export const createAsyncFormActions = (): IFormAsyncActions => + createAsyncActions( + 'submit', + 'reset', + 'validate', + 'setFormState', + 'getFormState', + 'setFieldState', + 'getFieldState', + 'getFormGraph', + 'setFormGraph', + 'subscribe', + 'unsubscribe', + 'notify', + 'dispatch', + 'setFieldValue', + 'getFieldValue', + 'setFieldInitialValue', + 'getFieldInitialValue' + ) as IFormAsyncActions + +export interface IEventTargetOption { + selected: boolean + value: any +} + +const isEvent = (candidate: any): boolean => + candidate && + (candidate.stopPropagation || candidate.preventDefault || candidate.bubbles) + +const isReactNative = + typeof window !== 'undefined' && + window.navigator && + window.navigator.product && + window.navigator.product === 'ReactNative' + +const getSelectedValues = (options?: IEventTargetOption[]) => { + const result = [] + if (options) { + for (let index = 0; index < options.length; index++) { + const option = options[index] + if (option.selected) { + result.push(option.value) + } + } + } + return result +} + +export const getValueFromEvent = (event: any) => { + if (isEvent(event)) { + if ( + !isReactNative && + event.nativeEvent && + event.nativeEvent.text !== undefined + ) { + return event.nativeEvent.text + } + if (isReactNative && event.nativeEvent !== undefined) { + return event.nativeEvent.text + } + + const detypedEvent = event + const { + target: { type, value, checked, files }, + dataTransfer + } = detypedEvent + + if (type === 'checkbox') { + return !!checked + } + + if (type === 'file') { + return files || (dataTransfer && dataTransfer.files) + } + + if (type === 'select-multiple') { + return getSelectedValues(event.target.options) + } + return value + } + return event +} + +const compactScheduler = ([raf, caf, priority], fresh: boolean) => { + return [fresh ? callback => raf(priority, callback) : raf, caf] +} + +const getScheduler = () => { + if (!globalThisPolyfill.requestAnimationFrame) { + return [globalThisPolyfill.setTimeout, globalThisPolyfill.clearTimeout] + } + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const scheduler = require('scheduler') as any + return compactScheduler( + [ + scheduler.scheduleCallback || scheduler.unstable_scheduleCallback, + scheduler.cancelCallback || scheduler.unstable_cancelCallback, + scheduler.NormalPriority || scheduler.unstable_NormalPriority + ], + !!scheduler.unstable_requestPaint + ) + } catch (err) { + return [self.requestAnimationFrame, self.cancelAnimationFrame] + } +} + +export const env = { + effectStart: false, + effectSelector: null, + effectEnd: false, + currentActions: null +} + +export const [raf, caf] = getScheduler() + +export const createFormEffects = ( + effects: IFormEffect | null, + actions: Actions +) => { + if (isFn(effects)) { + return (selector: (type: string) => Observable) => { + env.effectEnd = false + env.effectStart = true + env.currentActions = actions + env.effectSelector = ( + type: string, + matcher?: string | ((payload: T) => boolean) + ) => { + const observable$: Observable = selector(type) + if (matcher) { + return observable$.pipe( + filter( + isFn(matcher) + ? matcher + : (payload: T): boolean => { + return FormPath.parse(matcher).match( + payload && (payload as any).name + ) + } + ) + ) + } + return observable$ + } + Object.assign(env.effectSelector, actions) + effects(env.effectSelector, actions) + env.effectStart = false + env.effectEnd = true + env.currentActions = null + } + } else { + return () => {} + } +} + +export const createEffectHook = = any[]>( + type: string +) => (...args: Props): Observable => { + if (!env.effectStart || env.effectEnd) { + throw new Error( + 'EffectHook must be called synchronously within the effects callback function.' + ) + } + if (!env.effectSelector) { + throw new Error('Can not found effect hook selector.') + } + return env.effectSelector(type, ...args) +} + +type FieldMergeState = Partial & Partial + +export const FormEffectHooks = { + onFormWillInit$: createEffectHook( + LifeCycleTypes.ON_FORM_WILL_INIT + ), + onFormInit$: createEffectHook(LifeCycleTypes.ON_FORM_INIT), + onFormChange$: createEffectHook(LifeCycleTypes.ON_FORM_CHANGE), + onFormInputChange$: createEffectHook( + LifeCycleTypes.ON_FORM_INPUT_CHANGE + ), + onFormInitialValueChange$: createEffectHook( + LifeCycleTypes.ON_FORM_INITIAL_VALUES_CHANGE + ), + onFormReset$: createEffectHook(LifeCycleTypes.ON_FORM_RESET), + onFormSubmit$: createEffectHook(LifeCycleTypes.ON_FORM_SUBMIT), + onFormSubmitStart$: createEffectHook( + LifeCycleTypes.ON_FORM_SUBMIT_START + ), + onFormSubmitEnd$: createEffectHook( + LifeCycleTypes.ON_FORM_SUBMIT_END + ), + onFormMount$: createEffectHook(LifeCycleTypes.ON_FORM_MOUNT), + onFormUnmount$: createEffectHook(LifeCycleTypes.ON_FORM_UNMOUNT), + onFormValidateStart$: createEffectHook( + LifeCycleTypes.ON_FORM_VALIDATE_START + ), + onFormValidateEnd$: createEffectHook( + LifeCycleTypes.ON_FORM_VALIDATE_END + ), + onFormValuesChange$: createEffectHook( + LifeCycleTypes.ON_FORM_VALUES_CHANGE + ), + + onFormGraphChange$: createEffectHook( + LifeCycleTypes.ON_FORM_GRAPH_CHANGE + ), + + onFieldWillInit$: createEffectHook( + LifeCycleTypes.ON_FIELD_WILL_INIT + ), + onFieldInit$: createEffectHook(LifeCycleTypes.ON_FIELD_INIT), + onFieldChange$: createEffectHook( + LifeCycleTypes.ON_FIELD_CHANGE + ), + onFieldMount$: createEffectHook( + LifeCycleTypes.ON_FIELD_MOUNT + ), + onFieldUnmount$: createEffectHook( + LifeCycleTypes.ON_FIELD_UNMOUNT + ), + onFieldInputChange$: createEffectHook( + LifeCycleTypes.ON_FIELD_INPUT_CHANGE + ), + onFieldValueChange$: createEffectHook( + LifeCycleTypes.ON_FIELD_VALUE_CHANGE + ), + onFieldInitialValueChange$: createEffectHook( + LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE + ) +} diff --git a/packages/react/src/shared/array.tsx b/packages/react/src/shared/array.tsx deleted file mode 100644 index b113282f63b..00000000000 --- a/packages/react/src/shared/array.tsx +++ /dev/null @@ -1,309 +0,0 @@ -import React from 'react' -import { isFn, getIn, camelCase, isEqual } from '../utils' -import { IFieldProps } from '../type' - -export interface ICircleButtonProps { - onClick: React.MouseEvent - hasText: boolean -} - -export interface IArrayFieldOptions { - TextButton: React.ComponentType - CircleButton: React.ComponentType - AddIcon: React.ComponentType - RemoveIcon: React.ComponentType - MoveDownIcon: React.ComponentType - MoveUpIcon: React.ComponentType -} - -export interface IArrayFieldProps extends IFieldProps { - className?: string -} - -export class ArrayFieldComponent

    extends React.Component

    { - public isActive: (key: string, value: any) => boolean - public onClearErrorHandler: () => () => void - public renderRemove: (index: number, item: any) => React.ReactElement - public renderMoveDown: (index: number, item: any) => React.ReactElement - public renderMoveUp: (index: number) => React.ReactElement - public renderExtraOperations: (index: number) => React.ReactElement - public renderEmpty: (title?: string) => React.ReactElement - public renderAddition: () => React.ReactElement - public getProps: (path?: string) => any -} - -export type TypeArrayField

    = new (props: P, context) => ArrayFieldComponent< - P -> - -export const createArrayField = ( - options: IArrayFieldOptions -): TypeArrayField => { - const { - TextButton, - CircleButton, - AddIcon, - RemoveIcon, - MoveDownIcon, - MoveUpIcon - } = { - TextButton: () =>

    You Should Pass The TextButton.
    , - CircleButton: () =>
    You Should Pass The CircleButton.
    , - AddIcon: () =>
    You Should Pass The AddIcon.
    , - RemoveIcon: () =>
    You Should Pass The RemoveIcon.
    , - MoveDownIcon: () =>
    You Should Pass The MoveDownIcon.
    , - MoveUpIcon: () =>
    You Should Pass The MoveUpIcon.
    , - ...options - } - - return class ArrayFieldComponent extends React.Component { - public isActive = (key: string, value: any): boolean => { - const readOnly: - | boolean - | ((key: string, value: any) => boolean) = this.getProps('readOnly') - const disabled = this.getDisabled() - if (isFn(disabled)) { - return disabled(key, value) - } else if (isFn(readOnly)) { - return readOnly(key, value) - } else { - return !readOnly && !disabled - } - } - - public getApi(index: number) { - const { value } = this.props - return { - index, - isActive: this.isActive, - dataSource: value, - record: value[index], - add: this.onAddHandler(), - remove: this.onRemoveHandler(index), - moveDown: e => { - return this.onMoveHandler( - index, - index + 1 > value.length - 1 ? 0 : index + 1 - )(e) - }, - moveUp: e => { - return this.onMoveHandler( - index, - index - 1 < 0 ? value.length - 1 : index - 1 - )(e) - } - } - } - - public getProps(path?: string): any { - return getIn(this.props.schema, `x-props${path ? '.' + path : ''}`) - } - - public renderWith(name: string, index, defaultRender?) { - const render = this.getProps(camelCase(`render-${name}`)) - if (isFn(index)) { - defaultRender = index - index = 0 - } - if (isFn(render)) { - return render(this.getApi(index)) - } else if (defaultRender) { - return isFn(defaultRender) - ? defaultRender(this.getApi(index), render) - : defaultRender - } - } - - public renderAddition() { - const { locale } = this.props - const { value } = this.props - return ( - this.isActive('addition', value) && - this.renderWith( - 'addition', - ( - { - add - }: { add?: (event: React.MouseEvent) => void } = {}, - text: string - ) => { - return ( -
    - - - {text || locale.addItem || '添加'} - -
    - ) - } - ) - ) - } - - public renderEmpty() { - const { locale, value } = this.props - return ( - value.length === 0 && - this.renderWith('empty', ({ add, isActive }, text) => { - const active = isActive('empty', value) - return ( -
    -
    - - {active && ( - - - {text || locale.addItem || '添加'} - - )} -
    -
    - ) - }) - ) - } - - public renderRemove(index: number, item: any): React.ReactElement { - return ( - this.isActive(`${index}.remove`, item) && - this.renderWith('remove', index, ({ remove }, text) => { - return ( - - - {text && {text}} - - ) - }) - ) - } - - public renderMoveDown(index: number, item: any) { - const { value } = this.props - return ( - value.length > 1 && - this.isActive(`${index}.moveDown`, item) && - this.renderWith('moveDown', index, ({ moveDown }, text) => { - return ( - - - {text} - - ) - }) - ) - } - - public renderMoveUp(index: number) { - const { value } = this.props - return ( - value.length > 1 && - this.isActive(`${index}.moveUp`, value) && - this.renderWith('moveUp', index, ({ moveUp }, text) => { - return ( - - - {text} - - ) - }) - ) - } - - public renderExtraOperations(index: number) { - return this.renderWith('extraOperations', index) - } - - public getDisabled(): boolean | ((key: string, value: any) => boolean) { - const { editable, name } = this.props - const disabled = this.getProps('disabled') - if (editable !== undefined) { - if (isFn(editable)) { - if (!editable(name)) { - return true - } - } else if (editable === false) { - return true - } - } - return disabled - } - - // TODO e 类型 - public onRemoveHandler(index: number): (e: any) => void { - const { value, mutators, schema, locale } = this.props - const { minItems } = schema - return e => { - e.stopPropagation() - if (minItems >= 0 && value.length - 1 < minItems) { - mutators.errors(locale.array_invalid_minItems, minItems) - } else { - mutators.remove(index) - } - } - } - - public onMoveHandler(from: number, to: number): (e: any) => void { - const { mutators } = this.props - return e => { - e.stopPropagation() - mutators.move(from, to) - } - } - - public onAddHandler() { - const { value, mutators, schema, locale } = this.props - const { maxItems } = schema - return e => { - e.stopPropagation() - if (maxItems >= 0 && value.length + 1 > maxItems) { - mutators.errors(locale.array_invalid_maxItems, maxItems) - } else { - mutators.push() - } - } - } - - public onClearErrorHandler() { - return () => { - const { value, mutators, schema } = this.props - const { maxItems, minItems } = schema - if ( - (maxItems >= 0 && value.length <= maxItems) || - (minItems >= 0 && value.length >= minItems) - ) { - mutators.errors() - } - } - } - - public validate() { - const { value, mutators, schema, locale } = this.props - const { maxItems, minItems } = schema - if (value.length > maxItems) { - mutators.errors(locale.array_invalid_maxItems, maxItems) - } else if (value.length < minItems) { - mutators.errors(locale.array_invalid_minItems, minItems) - } else { - mutators.errors() - } - } - - public componentDidUpdate(prevProps) { - if (!isEqual(prevProps.value, this.props.value)) { - this.validate() - } - } - - public componentDidMount() { - this.validate() - } - } -} diff --git a/packages/react/src/shared/broadcast.tsx b/packages/react/src/shared/broadcast.tsx deleted file mode 100644 index 03595faa69d..00000000000 --- a/packages/react/src/shared/broadcast.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, { Component, useContext, useMemo, useState } from 'react' -import { IBroadcast, isArr, isFn, isStr } from '@uform/utils' -import { ISelector, IFormActions } from '@uform/types' -import { useEva, createActions } from 'react-eva' -import { Broadcast } from '../utils' -import { BroadcastContext, StateContext } from './context' - -type ChildrenFunction = (broadcast: IBroadcast) => React.ReactNode - -interface IFormProviderProps { - children?: React.ReactNode | ChildrenFunction -} - -export class FormProvider extends Component { - public static displayName = 'FormProvider' - - public broadcast = new Broadcast() - - public componentWillUnmount() { - this.broadcast.unsubscribe() - } - - public render() { - const { children } = this.props - return ( - - {isFn(children) ? children(this.broadcast) : children} - - ) - } -} - -export const FormBridge = () => (Target: React.ComponentType) => { - const Broadcast = props => { - const broadcast = useContext(BroadcastContext) - if (!broadcast) { - return ( - - {broadcast => } - - ) - } - return - } - - Broadcast.displayName = 'FormBroadcast' - - return Broadcast -} - -export interface IOption { - selector?: ((payload: any) => boolean) | string[] | string -} - -export interface IFormState { - status?: any - state?: object - schema?: object -} - -export const useForm = (options: IOption = {}) => { - const [value, setState] = useState({}) - const broadcast = useContext(BroadcastContext) - let initialized = false - let finalValue = value - - useMemo(() => { - if (broadcast) { - broadcast.subscribe(({ type, state, schema, ...others }) => { - if (type !== 'submit' && type !== 'reset') { - if (initialized) { - if (options.selector) { - if ( - (isFn(options.selector) && options.selector({ type, state })) || - (isArr(options.selector) && - options.selector.indexOf(type) > -1) || - (isStr(options.selector) && options.selector === type) - ) { - setState({ - status: type, - state, - schema, - ...others - }) - } - } - } else { - finalValue = { - status: type, - state, - schema, - ...others - } - } - } - }) - initialized = true - } - }, [broadcast]) - - const { status, state, schema } = finalValue as IFormState - - return { - status, - state, - schema, - submit: () => { - if (broadcast) { - broadcast.notify({ type: 'submit' }) - } - }, - reset: () => { - if (broadcast) { - broadcast.notify({ type: 'reset' }) - } - }, - dispatch: (name, payload) => { - if (broadcast) { - broadcast.notify({ type: 'dispatch', name, payload }) - } - } - } -} - -interface IFormControllerOptions { - actions: IFormActions - effects: (selector: ISelector, actions: IFormActions) => void -} - -export const useFormController = ({ - actions, - effects -}: IFormControllerOptions) => { - const { implementActions } = useEva({ actions }) - const context = useContext(StateContext) - const dispatch = useMemo(() => { - if (context && context.form) { - effects(context.form.selectEffect, context.actions) - return context.form.dispatchEffect - } - }, []) - return { - dispatch, - implementActions - } -} - -useFormController.createActions = createActions - -export const FormConsumer = ({ - children, - selector -}: { - // TODO formApi - children: React.ReactElement | ((formApi: any) => React.ReactElement) - selector?: IOption['selector'] -}): React.ReactElement => { - const formApi = useForm({ selector }) - if (!formApi) { - return - } - if (isFn(children)) { - return children(formApi) - } else { - return children || - } -} diff --git a/packages/react/src/shared/context.tsx b/packages/react/src/shared/context.tsx deleted file mode 100644 index db019bed755..00000000000 --- a/packages/react/src/shared/context.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { Form } from '@uform/core' -import { ISchema, IFormActions } from '@uform/types' -import { IBroadcast } from '@uform/utils' - -export interface IStateContext { - getSchema: (path: string) => ISchema - form: Form - actions: IFormActions - locale: { [key: string]: any } - broadcast: IBroadcast -} - -export const MarkupContext = React.createContext>(null) -export const StateContext = React.createContext>(null) -export const BroadcastContext = React.createContext>(null) diff --git a/packages/react/src/shared/core.tsx b/packages/react/src/shared/core.tsx deleted file mode 100644 index 277768e9cd4..00000000000 --- a/packages/react/src/shared/core.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import * as React from 'react' -import pascalCase from 'pascal-case' - -import { ISchemaFormProps, IFieldProps } from '../type' -import { isFn, isNotEmptyStr, lowercase, each, compose } from '../utils' - -// 最原生的 Form,用到了 DOM 的 form 标签 -export interface INativeFormProps { - component: string - formRef?: React.Ref -} - -export interface IRegisteredFieldsMap { - [name: string]: ComponentWithStyleComponent -} - -export type ComponentWithStyleComponent = React.ComponentType< - ComponentProps -> & { - styledComponentId?: string -} - -let FIELD_WRAPPERS -let FORM_FIELDS -let FIELD_PROPS_TRANSFORMERS -let FIELD_RENDERER -let FORM_COMPONENT - -export const initialContainer = () => { - FIELD_WRAPPERS = [] - FORM_FIELDS = {} - FIELD_PROPS_TRANSFORMERS = {} - FIELD_RENDERER = undefined - FORM_COMPONENT = class extends React.Component { - public static defaultProps = { - component: 'form' - } - - public static displayName = 'Form' - - public render() { - const { formRef, component, ...props } = this.props - return React.createElement(component, { - ...props, - ref: formRef - }) - } - } -} - -export const registerFormField = ( - name: string, - component: ComponentWithStyleComponent, - notWrapper?: boolean -) => { - if ( - isNotEmptyStr(name) && - (isFn(component) || typeof component.styledComponentId === 'string') - ) { - if (notWrapper) { - FORM_FIELDS[lowercase(name)] = component - FORM_FIELDS[lowercase(name)].registerMiddlewares = [] - } else { - FORM_FIELDS[lowercase(name)] = compose( - component, - FIELD_WRAPPERS, - true - ) - FORM_FIELDS[lowercase(name)].registerMiddlewares = FIELD_WRAPPERS - } - FORM_FIELDS[lowercase(name)].displayName = pascalCase(name) - } -} - -export const registerFormFields = (fields: IRegisteredFieldsMap) => { - each(fields, (component, name) => { - registerFormField(name, component) - }) -} - -export const registerFieldMiddleware = (...wrappers: any[]) => { - FIELD_WRAPPERS = FIELD_WRAPPERS.concat(wrappers) - each(FORM_FIELDS, (component, key) => { - if ( - !component.registerMiddlewares.some( - wrapper => wrappers.indexOf(wrapper) > -1 - ) - ) { - FORM_FIELDS[key] = compose( - FORM_FIELDS[key], - wrappers, - true - ) - FORM_FIELDS[key].registerMiddlewares = FIELD_WRAPPERS - } - }) -} - -export const registerFormWrapper = (...wrappers: any[]) => { - FORM_COMPONENT = wrappers.reduce((buf, fn, index) => { - const comp = isFn(fn) ? fn(buf) : buf - comp.displayName = `FormWrapperLevel${index}` - return comp - }, FORM_COMPONENT) -} - -export const registerFieldRenderer = (renderer: React.ComponentType) => { - FIELD_RENDERER = renderer -} - -// TODO transformer type -export const registerFormFieldPropsTransformer = ( - name: string, - transformer -) => { - if (isFn(transformer)) { - FIELD_PROPS_TRANSFORMERS[name] = transformer - } -} - -export const getFormFieldPropsTransformer = (name: string) => - FIELD_PROPS_TRANSFORMERS[name] - -export const getFormField = (name: string) => { - return FORM_FIELDS[name] -} - -export const getFieldRenderer = () => FIELD_RENDERER - -export const OriginForm = React.forwardRef( - (props: ISchemaFormProps, ref: React.Ref) => - React.createElement(FORM_COMPONENT, { ...props, ref }) -) diff --git a/packages/react/src/shared/mutators.ts b/packages/react/src/shared/mutators.ts deleted file mode 100644 index d016ce1db13..00000000000 --- a/packages/react/src/shared/mutators.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { isArr, flatArr, isNum, toArr } from '../utils' - -export const createMutators = props => { - return { - change(value: any): void { - props.form.setValue(props.name, value) - }, - - dispatch(name: string, payload: any) { - props.form.dispatchEffect(name, { - name: props.name, - path: props.path, - payload - }) - }, - - errors(errors: string[] | string, ...args) { - props.form.setErrors(props.name, flatArr(toArr(errors)), ...args) - }, - - push(value: any): void { - const arr = toArr(props.form.getValue(props.name)) - props.form.setValue(props.name, arr.concat(value)) - }, - - pop(): void { - const arr = [].concat(toArr(props.form.getValue(props.name))) - arr.pop() - props.form.setValue(props.name, arr) - }, - - insert(index: number, value: any): void { - const arr = [].concat(toArr(props.form.getValue(props.name))) - arr.splice(index, 0, value) - props.form.setValue(props.name, arr) - }, - - remove(index: number): void { - let val = props.form.getValue(props.name) - if (isNum(index) && isArr(val)) { - val = [].concat(val) - val.splice(index, 1) - props.form.setValue(props.name, val) - } else { - props.form.removeValue(props.name) - } - }, - - unshift(value: any): void { - const arr = [].concat(toArr(props.form.getValue(props.name))) - arr.unshift(value) - props.form.setValue(props.name, arr) - }, - - shift(): void { - const arr = [].concat(toArr(props.form.getValue(props.name))) - arr.shift() - props.form.setValue(props.name, arr) - }, - - move($from: number, $to: number): void { - const arr = [].concat(toArr(props.form.getValue(props.name))) - const item = arr[$from] - arr.splice($from, 1) - arr.splice($to, 0, item) - props.form.setValue(props.name, arr) - } - } -} diff --git a/packages/react/src/shared/virtualbox.tsx b/packages/react/src/shared/virtualbox.tsx deleted file mode 100644 index da7865fc043..00000000000 --- a/packages/react/src/shared/virtualbox.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react' -import pascalCase from 'pascal-case' -import { registerFormField, ComponentWithStyleComponent } from './core' -import { SchemaField } from '../decorators/markup' -import { registerVirtualboxFlag } from '../utils' -import { FormField } from '../state/field' -import { IFieldProps } from '../type' - -export type TVirtualBoxProps = React.PropsWithChildren<{ - name?: string - render?: (fieldProps: IFieldProps) => string | JSX.Element | null -}> - -export const createVirtualBox =

    ( - name: string, - component: ComponentWithStyleComponent, - isController?: boolean -) => { - registerVirtualboxFlag(name) - registerFormField( - name, - class extends React.PureComponent { - public static displayName = 'VirtualBoxWrapper' - - public render() { - const { schema, schemaPath, path, getOrderProperties } = this.props - const parentPath = path.slice(0, path.length - 1) - const properties = getOrderProperties(schema) - const children = properties.map(({ key }) => { - const newPath = parentPath.concat(key) - const newName = newPath.join('.') - const newSchemaPath = schemaPath.concat(key) - return ( - - ) - }) - return React.createElement( - component, - isController ? this.props : (schema['x-props'] as any), - children - ) - } - }, - true - ) - - const VirtualBox = ({ - children, - name: fieldName, - render, - ...props - }: P & TVirtualBoxProps) => ( - - {children} - - ) - - if (component.defaultProps) { - VirtualBox.defaultProps = component.defaultProps - } - - VirtualBox.displayName = pascalCase(name) - - return VirtualBox -} - -export const createControllerBox =

    ( - name: string, - component: ComponentWithStyleComponent -) => createVirtualBox

    (name, component, true) - -export const FormSlot = ({ name, children }) => { - return ( - - ) -} diff --git a/packages/react/src/state/field.tsx b/packages/react/src/state/field.tsx deleted file mode 100644 index 183b1fbc068..00000000000 --- a/packages/react/src/state/field.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import React, { Component, useContext } from 'react' -import { ISchema } from '@uform/types' - -import { - createHOC, - isEqual, - each, - schemaIs, - filterSchema, - lowercase -} from '../utils' -import { createMutators } from '../shared/mutators' -import { StateContext } from '../shared/context' -import { getFieldRenderer, getFormField } from '../shared/core' -import { IStateFieldProps, IStateFieldState, IFieldProps } from '../type' - -const StateField = createHOC((options, Field) => { - class StateField extends Component { - public static displayName = 'StateField' - - private initialized: boolean - private unmounted: boolean - private field: any - // TODO mutators 文件应该暴露出来 interface - private mutators: any - - constructor(props) { - super(props) - this.initialized = false - this.state = {} - this.field = props.form.registerField( - props.name || props.schemaPath.join('.'), - { - path: props.schemaPath, - onChange: this.onChangeHandler(), - props: props.schema - } - ) - this.mutators = createMutators(props) - this.initialized = true - } - - public onChangeHandler() { - return fieldState => { - if (this.unmounted) { - return - } - if (this.initialized) { - this.setState(fieldState) - } else { - // eslint-disable-next-line react/no-direct-mutation-state - this.state = fieldState - } - } - } - - public componentWillUnmount() { - this.unmounted = true - this.field.unmount() - } - - public componentDidMount() { - this.unmounted = false - this.field.mount() - } - - public componentDidUpdate(prevProps) { - if (!isEqual(this.props.schema, prevProps.schema, filterSchema)) { - this.field.changeProps(this.props.schema) - } - } - - public renderField = (key, addReactKey: boolean) => { - const path = this.props.path.concat(key) - const schemaPath = this.props.schemaPath.concat(key) - const name = path.join('.') - - return ( - - ) - } - - public getOrderProperties = (outerSchema?: ISchema) => { - const { schema: innerSchema, path } = this.props - if (!innerSchema && !outerSchema) { - return [] - } - - const properties = [] - each((outerSchema || innerSchema || {}).properties, (item, key) => { - const index = item['x-index'] - const newPath = path.concat(key) - const newName = newPath.join('.') - if (typeof index === 'number') { - properties[index] = { - schema: item, - key, - path: newPath, - name: newName - } - } else { - properties.push({ schema: item, key, path: newPath, name: newName }) - } - }) - return properties.reduce((buf, item) => { - return item ? buf.concat(item) : buf - }, []) - } - - public render() { - const { - name, - path, - schemaPath, - broadcast, - schema, - form, - locale, - getSchema - } = this.props - const { - value, - visible, - display, - props, - errors, - loading, - editable, - required - } = this.state - const newValue = schemaIs(props, 'object') - ? value || {} - : schemaIs(props, 'array') - ? value || [] - : value - //todo: 重置schema children,这里有点恶心,后面重构的时候需要想下怎么重置更合适 - if (schema.properties) { - props.properties = schema.properties - } else if (schema.items) { - props.items = schema.items - } - return visible === false || display === false ? ( - - ) : ( - - ) - } - } - - return props => { - const { name, path, schemaPath } = props - const { form, getSchema, locale, broadcast } = useContext(StateContext) - return ( - - ) - } -}) - -export const FormField = StateField()((props: IFieldProps) => { - const schema = props.schema - const fieldComponentName = lowercase(schema['x-component'] || schema.type) - const renderComponent = schema['x-render'] - ? (innerProps: any) => { - return React.createElement(getFormField(fieldComponentName), { - ...props, - ...innerProps, - schema, - path: props.path, - name: props.name - }) - } - : undefined - - const component = schema['x-render'] - ? getFieldRenderer() - : getFormField(fieldComponentName) - - if (component) { - return React.createElement(component, { ...props, renderComponent }) - } else { - if (console && console.error) { - if (fieldComponentName) { - console.error( - `The schema field \`${fieldComponentName}\`'s component is not found.` - ) - } else { - console.error( - "The schema field's component is not found, or field's schema is not defined." - ) - } - } - return - } -}) diff --git a/packages/react/src/state/form.tsx b/packages/react/src/state/form.tsx deleted file mode 100644 index e5cf43859cf..00000000000 --- a/packages/react/src/state/form.tsx +++ /dev/null @@ -1,317 +0,0 @@ -import React, { Component } from 'react' -import { connect } from 'react-eva' -import { createForm, Form } from '@uform/core' -import { IFormState, IFormActions } from '@uform/types' - -import { - createHOC, - getSchemaNodeFromPath, - isEqual, - isObj, - clone, - isEmpty -} from '../utils' -import { StateContext } from '../shared/context' -import { getFormFieldPropsTransformer } from '../shared/core' -import { FormBridge } from '../shared/broadcast' -import { IStateFormProps } from '../type' - -export const StateForm = createHOC((options, Form) => { - class StateForm extends Component { - public static displayName = 'StateForm' - - public static defaultProps = { - locale: {} - } - - private unmounted: boolean - private initialized: boolean - private lastFormValues: IFormState - private formState: IFormState - private form: Form - private unsubscribe: () => void - - constructor(props) { - super(props) - this.initialized = false - this.form = createForm({ - initialValues: props.defaultValue || props.initialValues, - values: props.value, - effects: props.effects, - subscribes: props.subscribes, - schema: props.schema, - editable: props.editable, - traverse: schema => { - const traverse = - schema && - getFormFieldPropsTransformer(schema['x-component'] || schema.type) - return traverse ? traverse(schema) : schema - }, - onSubmit: this.onSubmitHandler(props), - onFormChange: this.onFormChangeHandler(props), - onFieldChange: this.onFieldChangeHandler(props), - onValidateFailed: this.onValidateFailed(props), - onReset: this.onResetHandler(props), - onFormWillInit: form => { - props.implementActions(this.getActions(form)) - } - }) - this.formState = {} as IFormState - this.initialized = true - } - - public getActions(form: Form): IFormActions { - return { - setFormState: form.setFormState, - getFormState: form.getFormState, - setFieldState: form.setFieldState, - getFieldState: form.getFieldState, - reset: this.reset, - submit: this.submit, - validate: this.validate, - getSchema: this.getSchema, - dispatch: this.dispatch - } - } - - public notify(payload) { - const { broadcast, schema } = this.props - if (broadcast) { - payload.schema = schema - broadcast.notify(payload) - } - } - - public onValidateFailed = $props => { - return (...args) => { - const props = this.props || $props - if (props.onValidateFailed) { - return props.onValidateFailed(...args) - } - } - } - - public onFormChangeHandler(props) { - let lastState = this.formState - return ({ formState }) => { - if (this.unmounted) { - return - } - if (lastState && lastState.pristine !== formState.pristine) { - if (lastState.pristine) { - this.notify({ - type: 'changed', - state: formState - }) - } else { - this.notify({ - type: 'reseted', - state: formState - }) - } - } - - lastState = formState - - // eslint-disable-next-line react/no-direct-mutation-state - this.formState = formState - - if (!this.initialized) { - this.notify({ - type: 'initialize', - state: formState - }) - } - } - } - - public onFieldChangeHandler = $props => { - return ({ formState }) => { - const props = this.props || $props - if (props.onChange) { - const values = formState.values - if (!isEqual(this.lastFormValues, values)) { - props.onChange(values) - this.lastFormValues = clone(values) - } - } - } - } - - public getSchema = path => { - return getSchemaNodeFromPath(this.props.schema, path) - } - - public onSubmitHandler = $props => { - return ({ formState }) => { - const props = this.props || $props - if (props.onSubmit) { - const promise = props.onSubmit(clone(formState.values)) - if (promise && promise.then) { - this.notify({ - type: 'submitting', - state: this.formState - }) - promise.then( - () => { - this.notify({ - type: 'submitted', - state: this.formState - }) - }, - error => { - this.notify({ - type: 'submitted', - state: this.formState - }) - throw error - } - ) - } - } - } - } - - public onResetHandler($props) { - return ({ formState }) => { - const props = this.props || $props - if (props.onReset) { - props.onReset(clone(formState.values)) - } - } - } - - public componentDidUpdate(prevProps) { - const { value, editable, initialValues } = this.props - if (!isEmpty(value) && !isEqual(value, prevProps.value)) { - this.form.changeValues(value) - } else if (this.form.isDirtyValues(value)) { - this.form.changeValues(value) - } - if ( - !isEmpty(initialValues) && - !isEqual(initialValues, prevProps.initialValues) - ) { - this.form.initialize({ - values: initialValues, - initialValues - }) - } - if (!isEmpty(editable) && !isEqual(editable, prevProps.editable)) { - this.form.changeEditable(editable) - } - } - - public componentDidMount() { - this.unmounted = false - this.form.dispatchEffect('onFormMount', this.form.publishState()) - - this.unsubscribe = this.props.broadcast.subscribe( - ({ type, name, payload }) => { - if (this.unmounted) { - return - } - if (type === 'submit') { - this.submit() - } else if (type === 'reset') { - this.reset() - } else if (type === 'dispatch') { - this.form.dispatchEffect(name, payload) - } - } - ) - } - - public componentWillUnmount() { - this.unmounted = true - if (this.form) { - this.form.destructor() - this.unsubscribe() - delete this.form - } - } - - public onNativeSubmitHandler = e => { - if (e.preventDefault) { - e.stopPropagation() - e.preventDefault() - } - this.form.submit().catch(e => { - if (console && console.error) { - console.error(e) - } - }) - } - - public getValues = () => { - return this.form.getValue() - } - - public submit = () => { - return this.form.submit() - } - - public reset = ( - params?: boolean | { forceClear?: boolean; validate?: boolean }, - validate: boolean = true - ) => { - let forceClear: boolean - if (isObj(params)) { - forceClear = !!params.forceClear - validate = !isEmpty(params.validate) ? params.validate : validate - } - this.form.reset(forceClear, validate) - } - - public validate = () => { - return this.form.validate() - } - - public dispatch = (type, payload) => { - this.form.dispatchEffect(type, payload) - } - - public render() { - /* eslint-disable @typescript-eslint/no-unused-vars */ - const { - onSubmit, - onChange, - onReset, - onValidateFailed, - initialValues, - defaultValue, - effects, - implementActions, - dispatch, - editable, - subscribes, - subscription, - children, - schema, - broadcast, - locale, - value, - ...others - } = this.props - /* eslint-enable @typescript-eslint/no-unused-vars */ - - return ( - -

    - {children} -
    - - ) - } - } - - return connect({ autoRun: false })(FormBridge()(StateForm)) -}) diff --git a/packages/react/src/state/index.tsx b/packages/react/src/state/index.tsx deleted file mode 100644 index 101fadf7015..00000000000 --- a/packages/react/src/state/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { registerFormWrapper } from '../shared/core' -import { StateForm } from './form' - -export default () => { - registerFormWrapper(StateForm()) -} diff --git a/packages/react/src/type.ts b/packages/react/src/type.ts deleted file mode 100644 index 572efd5c194..00000000000 --- a/packages/react/src/type.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { IFormOptions, IFormPayload, ISchema, IField } from '@uform/types' -import { Form } from '@uform/core' -import { IBroadcast } from '@uform/utils' - -export interface IEventTargetOption { - selected: boolean - value: any -} - -export interface IEnhanceSchema extends ISchema { - renderChildren?: React.ReactElement -} - -export interface IFieldProps - extends Omit, - IStateFieldProps { - state?: string - size?: string - children?: React.ReactNode - schema: IEnhanceSchema - getOrderProperties: (schema?: ISchema) => any - renderField: (key, addReactKey?: boolean) => React.ReactElement - editable: boolean | ((name: string) => boolean) -} - -export interface IStateFieldProps { - name: string - schema: ISchema - path: string[] - schemaPath: any - locale: { [key: string]: any } - getSchema: (path: string) => ISchema - broadcast: IBroadcast - form: Form - // TODO mutators 文件应该暴露出来 interface - mutators?: any -} - -export interface IStateFieldState { - value?: any - props?: any - errors?: any - visible?: boolean - display?: boolean - loading?: boolean - editable?: boolean - required?: boolean -} - -export interface ISchemaFormProps extends IFormOptions { - className?: string - children?: React.ReactNode - value?: V - onChange?: (payload: IFormPayload) => void -} - -export interface IStateFormProps extends ISchemaFormProps { - broadcast: IBroadcast - - // eva - implementActions: (actions: object) => object - dispatch: (type: string, ...args: any) => void - subscription: () => void - - // ConfigProvider - locale: { [key: string]: any } -} diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts new file mode 100644 index 00000000000..2a1ac48ce5e --- /dev/null +++ b/packages/react/src/types.ts @@ -0,0 +1,181 @@ +import React from 'react' +import { + IFieldStateProps, + IVirtualFieldStateProps, + IForm, + IMutators, + IFieldState, + IFormValidateResult, + IFormState, + IFormResetOptions, + IFormSubmitResult, + FormHeartSubscriber, + IFormGraph +} from '@uform/core' +import { FormPathPattern } from '@uform/shared' +import { Observable } from 'rxjs/internal/Observable' +export interface IFormEffect { + ( + selector: IFormExtendsEffectSelector, + actions: Actions + ): void +} + +export interface IFormEffectSelector { + ( + type: string, + matcher?: string | ((payload: Payload) => boolean) + ): Observable +} + +export type IFormExtendsEffectSelector< + Payload = any, + Actions = {} +> = IFormEffectSelector & Actions + +export interface IFormProps< + Value = {}, + DefaultValue = {}, + EffectPayload = any, + EffectActions = {} +> { + value?: Value + initialValues?: DefaultValue + actions?: EffectActions + effects?: IFormEffect + form?: IForm + onChange?: (values: Value) => void + onSubmit?: (values: Value) => void | Promise + onReset?: () => void + onValidateFailed?: (valideted: IFormValidateResult) => void + children?: React.ReactElement | ((form: IForm) => React.ReactElement) + useDirty?: boolean + editable?: boolean + validateFirst?: boolean +} + +export interface IFieldAPI { + state: IFieldState + form: IForm + props: {} + mutators: IMutators +} + +export interface IVirtualFieldAPI { + state: IFieldState + form: IForm + props: {} +} + +export interface IFieldProps extends IFieldStateProps { + triggerType?: 'onChange' | 'onBlur' + getValueFromEvent?: (...args: any[]) => any + children?: React.ReactElement | ((api: IFieldAPI) => React.ReactElement) +} + +export interface IVirtualFieldProps extends IVirtualFieldStateProps { + children?: + | React.ReactElement + | ((api: IVirtualFieldAPI) => React.ReactElement) +} + +export interface IFormSpyAPI { + form: IForm + type: string + state: any +} + +export interface IFormSpyProps { + selector?: string[] | string + reducer?: ( + state: any, + action: { type: string; payload: any }, + form: IForm + ) => any + children?: React.ReactElement | ((api: IFormSpyAPI) => React.ReactElement) +} + +export interface IFormConsumerAPI { + status: string + state: IFormState + submit: IForm['submit'] + reset: IForm['reset'] +} + +export interface IFormConsumerProps { + selector?: string[] | string + children?: + | React.ReactElement + | ((api: IFormConsumerAPI) => React.ReactElement) +} + +export interface IFieldHook { + form: IForm + state: IFieldState + props: {} + mutators: IMutators +} + +export interface IVirtualFieldHook { + form: IForm + state: IFieldState + props: {} +} + +export interface IFormActions { + submit( + onSubmit?: (values: IFormState['values']) => void | Promise + ): Promise + reset(options?: IFormResetOptions): void + validate(path?: FormPathPattern, options?: {}): Promise + setFormState(callback?: (state: IFormState) => any): void + getFormState(callback?: (state: IFormState) => any): any + clearErrors: (pattern?: FormPathPattern) => void + setFieldState( + path: FormPathPattern, + callback?: (state: IFieldState) => void + ): void + getFieldState( + path: FormPathPattern, + callback?: (state: IFieldState) => any + ): any + getFormGraph(): IFormGraph + setFormGraph(graph: IFormGraph): void + subscribe(callback?: FormHeartSubscriber): void + unsubscribe(callback?: FormHeartSubscriber): void + notify: (type: string, payload: T) => void + dispatch: (type: string, payload: T) => void + setFieldValue(path?: FormPathPattern, value?: any): void + getFieldValue(path?: FormPathPattern): any + setFieldInitialValue(path?: FormPathPattern, value?: any): void + getFieldInitialValue(path?: FormPathPattern): any +} + +export interface IFormAsyncActions { + submit( + onSubmit?: (values: IFormState['values']) => void | Promise + ): Promise + reset(options?: IFormResetOptions): Promise + clearErrors: (pattern?: FormPathPattern) => Promise + validate(path?: FormPathPattern, options?: {}): Promise + setFormState(callback?: (state: IFormState) => any): Promise + getFormState(callback?: (state: IFormState) => any): Promise + setFieldState( + path: FormPathPattern, + callback?: (state: IFieldState) => void + ): Promise + getFieldState( + path: FormPathPattern, + callback?: (state: IFieldState) => any + ): Promise + getFormGraph(): Promise + setFormGraph(graph: IFormGraph): Promise + subscribe(callback?: FormHeartSubscriber): Promise + unsubscribe(callback?: FormHeartSubscriber): Promise + notify: (type: string, payload: T) => Promise + dispatch: (type: string, payload: T) => void + setFieldValue(path?: FormPathPattern, value?: any): Promise + getFieldValue(path?: FormPathPattern): Promise + setFieldInitialValue(path?: FormPathPattern, value?: any): Promise + getFieldInitialValue(path?: FormPathPattern): Promise +} diff --git a/packages/react/src/utils.tsx b/packages/react/src/utils.tsx deleted file mode 100644 index f2d2e7bc2ec..00000000000 --- a/packages/react/src/utils.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import { reduce, isFn, isStr, isArr } from '@uform/utils' - -export * from '@uform/utils' - -export const isNum = (value: string | number): boolean => - typeof value === 'number' - -export const isNotEmptyStr = (str: string): boolean => !!(isStr(str) && str) - -export const flatArr = (arr: any[]) => - arr.reduce((buf, item) => { - return isArr(item) ? buf.concat(flatArr(item)) : buf.concat(item) - }, []) - -export const compose = (payload: any, args: any[], revert: boolean) => - reduce( - args, - (buf, fn: any) => { - return isFn(fn) ? fn(buf) : buf - }, - payload, - revert - ) - -export const createHOC = (wrapper?: (options: object, Target) => any) => ( - options?: object -) => (Target: React.ComponentType) => { - return wrapper({ ...options }, Target) -} - -export const filterSchema = (_, key): boolean => - ['items', 'properties'].indexOf(key) < 0 - -export const filterSchemaPropertiesAndReactChildren = (_, key): boolean => { - return ['items', 'properties', 'children'].indexOf(key) < 0 -} diff --git a/packages/utils/.npmignore b/packages/shared/.npmignore similarity index 100% rename from packages/utils/.npmignore rename to packages/shared/.npmignore diff --git a/packages/utils/LICENSE.md b/packages/shared/LICENSE.md similarity index 100% rename from packages/utils/LICENSE.md rename to packages/shared/LICENSE.md diff --git a/packages/utils/README.md b/packages/shared/README.md similarity index 57% rename from packages/utils/README.md rename to packages/shared/README.md index a4984e5d18d..83e6261b3c4 100644 --- a/packages/utils/README.md +++ b/packages/shared/README.md @@ -1,2 +1,2 @@ -# @uform/utils +# @uform/shared > UForm工具函数集 \ No newline at end of file diff --git a/packages/shared/jest.config.js b/packages/shared/jest.config.js new file mode 100644 index 00000000000..dfc970503b3 --- /dev/null +++ b/packages/shared/jest.config.js @@ -0,0 +1 @@ +module.exports = require('../../scripts/jest.base') diff --git a/packages/types/package.json b/packages/shared/package.json similarity index 68% rename from packages/types/package.json rename to packages/shared/package.json index dd9878ab43d..fdf93918d05 100644 --- a/packages/types/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { - "name": "@uform/types", - "version": "0.4.3", + "name": "@uform/shared", + "version": "0.4.0", "license": "MIT", "main": "lib", "types": "lib/index.d.ts", @@ -15,6 +15,10 @@ "engines": { "npm": ">=3.0.0" }, + "publishConfig": { + "access": "public" + }, + "gitHead": "f513fc2dcca781b3f7aa588c4419bce20cba2d8b", "scripts": { "build": "tsc --declaration" }, @@ -22,14 +26,12 @@ "typescript": "^3.5.2" }, "peerDependencies": { - "@babel/runtime": "^7.4.4", - "rxjs": "^6.3.3" + "@babel/runtime": "^7.4.4" }, "dependencies": { - "rxjs": "^6.3.3" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "f513fc2dcca781b3f7aa588c4419bce20cba2d8b" + "@uform/types": "^0.4.0", + "camel-case": "^3.0.0", + "cool-path": "^0.1.1", + "deepmerge": "^4.0.0" + } } diff --git a/packages/shared/src/__tests__/index.spec.ts b/packages/shared/src/__tests__/index.spec.ts new file mode 100644 index 00000000000..8b0cfc66ae2 --- /dev/null +++ b/packages/shared/src/__tests__/index.spec.ts @@ -0,0 +1,116 @@ +import { isEqual } from '../compare' +import { toArr, every, some, findIndex, find, includes } from '../array' +import { clone } from '../clone' + + +test('toArr', () => { + expect(isEqual(toArr([123]), [123])).toBeTruthy() + expect(isEqual(toArr(123), [123])).toBeTruthy() + expect(isEqual(toArr(null), [])).toBeTruthy() +}) + +test('clone form data', () => { + var dd = new Map() + dd.set('aaa', { bb: 123 }) + var a = { + aa: 123123, + bb: [{ bb: 111 }, { bb: 222 }], + cc: () => { + // eslint-disable-next-line no-console + console.log('123') + }, + dd + } + var cloned = clone(a) + expect(isEqual(cloned, a)).toBeTruthy() + expect(a === cloned).toBeFalsy() + expect(a.bb[0] === cloned.bb[0]).toBeFalsy() + expect(a.dd === cloned.dd).toBeFalsy() + expect(a.dd.get('aaa') === cloned.dd.get('aaa')).toBeTruthy() + expect(a.cc === cloned.cc).toBeTruthy() +}) + +test('filter equal', () => { + var a = { + aa: { + bb: 123 + } + } + var b = { + aa: { + bb: 123 + } + } + + expect(isEqual(a, b)).toBeTruthy() + expect(isEqual(a, b, (_, key) => key !== 'aa')).toBeTruthy() +}) + +test('filter clone', () => { + var a = { + aa: { + bb: 123 + }, + cc: { + dd: [1, 3, 4, 5] + } + } + + var b = clone(a, (_, key) => key !== 'aa') + + expect(a.aa === b.aa).toBeTruthy() + expect(a.cc === b.cc).toBeFalsy() + expect(isEqual(a.cc, b.cc)).toBeTruthy() +}) + + +test('some', () => { + const values1 = [1, 2, 3, 4, 5] + const values2 = [] + const values3 = { a: 1, b: 2, c: 3 } + const values4 = {} + expect(some(values1, item => item === 3)).toBeTruthy() + expect(some(values1, item => item === 6)).toBeFalsy() + expect(some(values2, () => true)).toBeFalsy() + expect(some(values2, () => false)).toBeFalsy() + expect(some(values3, item => item === 3)).toBeTruthy() + expect(some(values3, item => item === 6)).toBeFalsy() + expect(some(values4, () => true)).toBeFalsy() + expect(some(values4, () => false)).toBeFalsy() +}) + +test('every', () => { + const values1 = [1, 2, 3, 4, 5] + const values2 = [] + const values3 = { a: 1, b: 2, c: 3 } + const values4 = {} + expect(every(values1, item => item < 6)).toBeTruthy() + expect(every(values1, item => item < 3)).toBeFalsy() + expect(every(values2, () => true)).toBeTruthy() + expect(every(values2, () => false)).toBeTruthy() + expect(every(values2, () => false)).toBeTruthy() + expect(every(values3, item => item < 6)).toBeTruthy() + expect(every(values3, item => item < 3)).toBeFalsy() + expect(every(values4, () => false)).toBeTruthy() + expect(every(values4, () => false)).toBeTruthy() +}) + +test('findIndex', () => { + const value = [1, 2, 3, 4, 5] + expect(isEqual(findIndex(value, item => item > 3), 3)).toBeTruthy() + expect(isEqual(findIndex(value, item => item < 3, true), 1)).toBeTruthy() + expect(isEqual(findIndex(value, item => item > 6), -1)).toBeTruthy() +}) + +test('find', () => { + const value = [1, 2, 3, 4, 5] + expect(isEqual(find(value, item => item > 3), 4)).toBeTruthy() + expect(isEqual(find(value, item => item < 3, true), 2)).toBeTruthy() + expect(isEqual(find(value, item => item > 6), void 0)).toBeTruthy() +}) + +test('includes', () => { + const value = [1, 2, 3, 4, 5] + expect(includes(value, 3)).toBeTruthy() + expect(includes(value, 6)).toBeFalsy() +}) diff --git a/packages/utils/src/array.ts b/packages/shared/src/array.ts similarity index 71% rename from packages/utils/src/array.ts rename to packages/shared/src/array.ts index 042580e08b8..a5b8e5981a0 100644 --- a/packages/utils/src/array.ts +++ b/packages/shared/src/array.ts @@ -1,11 +1,20 @@ -import { isArr, isObj, isStr } from '@uform/types' +import { isArr, isObj, isStr } from './types' type EachArrayIterator = (currentValue: T, key: number) => void | boolean type EachStringIterator = (currentValue: string, key: number) => void | boolean -type EachObjectIterator = ( - currentValue: T[keyof T], +type EachObjectIterator = ( + currentValue: T, key: string ) => void | boolean +type MapArrayIterator = ( + currentValue: TItem, + key: number +) => TResult +type MapStringIterator = (currentValue: string, key: number) => TResult +type MapObjectIterator = ( + currentValue: TItem, + key: string +) => TResult type MemoArrayIterator = ( previousValue: U, currentValue: T, @@ -16,11 +25,11 @@ type MemoStringIterator = ( currentValue: string, key: number ) => T -type MemoObjectIterator = ( - previousValue: U, - currentValue: T[keyof T], +type MemoObjectIterator = ( + previousValue: TResult, + currentValue: TValue, key: string -) => U +) => TResult export const toArr = (val: any): any[] => (isArr(val) ? val : val ? [val] : []) @@ -34,12 +43,12 @@ export function each( iterator: EachArrayIterator, revert?: boolean ): void -export function each( +export function each( val: T, - iterator: EachObjectIterator, + iterator: EachObjectIterator, revert?: boolean ): void -export function each(val: any, iterator: any, revert?: boolean): object { +export function each(val: any, iterator: any, revert?: boolean): void { if (isArr(val) || isStr(val)) { if (revert) { for (let i: number = val.length - 1; i >= 0; i--) { @@ -48,7 +57,7 @@ export function each(val: any, iterator: any, revert?: boolean): object { } } } else { - for (let i = 0, length = val.length; i < length; i++) { + for (let i = 0; i < val.length; i++) { if (iterator(val[i], i) === false) { return } @@ -66,21 +75,21 @@ export function each(val: any, iterator: any, revert?: boolean): object { } } -export function map( +export function map( val: string, - iterator: EachStringIterator, + iterator: MapStringIterator, revert?: boolean -): string[] -export function map( - val: T[], - iterator: EachArrayIterator, +): any +export function map( + val: TItem[], + iterator: MapArrayIterator, revert?: boolean -): T[] -export function map( +): any +export function map( val: T, - iterator: EachObjectIterator, + iterator: MapObjectIterator, revert?: boolean -): object +): any export function map(val: any, iterator: any, revert?: boolean): any { const res = isArr(val) || isStr(val) ? [] : {} each( @@ -110,12 +119,12 @@ export function reduce( accumulator?: T, revert?: boolean ): T -export function reduce( +export function reduce( val: T, - iterator: MemoObjectIterator, - accumulator?: U, + iterator: MemoObjectIterator, + accumulator?: TResult, revert?: boolean -): U +): TResult export function reduce( val: any, iterator: any, @@ -133,8 +142,8 @@ export function reduce( return result } -export function every( - val: string, +export function every( + val: T, iterator: EachStringIterator, revert?: boolean ): boolean @@ -143,9 +152,9 @@ export function every( iterator: EachArrayIterator, revert?: boolean ): boolean -export function every( +export function every( val: T, - iterator: EachObjectIterator, + iterator: EachObjectIterator, revert?: boolean ): boolean export function every(val: any, iterator: any, revert?: boolean): boolean { @@ -163,8 +172,8 @@ export function every(val: any, iterator: any, revert?: boolean): boolean { return res } -export function some( - val: string, +export function some( + val: T, iterator: EachStringIterator, revert?: boolean ): boolean @@ -173,9 +182,9 @@ export function some( iterator: EachArrayIterator, revert?: boolean ): boolean -export function some( +export function some( val: T, - iterator: EachObjectIterator, + iterator: EachObjectIterator, revert?: boolean ): boolean export function some(val: any, iterator: any, revert?: boolean): boolean { @@ -193,8 +202,8 @@ export function some(val: any, iterator: any, revert?: boolean): boolean { return res } -export function findIndex( - val: string, +export function findIndex( + val: T, iterator: EachStringIterator, revert?: boolean ): number @@ -203,9 +212,9 @@ export function findIndex( iterator: EachArrayIterator, revert?: boolean ): number -export function findIndex( +export function findIndex( val: T, - iterator: EachObjectIterator, + iterator: EachObjectIterator, revert?: boolean ): keyof T export function findIndex( @@ -227,8 +236,8 @@ export function findIndex( return res } -export function find( - val: string, +export function find( + val: T, iterator: EachStringIterator, revert?: boolean ): any @@ -237,9 +246,9 @@ export function find( iterator: EachArrayIterator, revert?: boolean ): T -export function find( +export function find( val: T, - iterator: EachObjectIterator, + iterator: EachObjectIterator, revert?: boolean ): T[keyof T] export function find(val: any, iterator: any, revert?: boolean): any { @@ -257,8 +266,8 @@ export function find(val: any, iterator: any, revert?: boolean): any { return res } -export function includes( - val: string[], +export function includes( + val: T, searchElement: string, revert?: boolean ): boolean @@ -266,6 +275,8 @@ export function includes( val: T[], searchElement: T, revert?: boolean -): boolean { +): boolean +export function includes(val: any, searchElement: any, revert?: boolean) { + if (isStr(val)) return val.includes(searchElement) return some(val, item => item === searchElement, revert) } diff --git a/packages/utils/src/broadcast.ts b/packages/shared/src/broadcast.ts similarity index 58% rename from packages/utils/src/broadcast.ts rename to packages/shared/src/broadcast.ts index d2764fe5683..d96454511bf 100644 --- a/packages/utils/src/broadcast.ts +++ b/packages/shared/src/broadcast.ts @@ -1,5 +1,5 @@ -import { isFn } from '@uform/types' import { each } from './array' +import { isFn } from './types' type Subscriber = (notification: N) => void @@ -10,12 +10,13 @@ const noop = () => undefined // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface IBroadcast extends Broadcast {} -export class Broadcast { +export class Broadcast { private entries = [] private buffer = [] private length: number + private context: any - public subscribe(subscriber: Subscriber, subscription?: any) { + public subscribe(subscriber: Subscriber, subscription?: any) { if (!isFn(subscriber)) { return noop } @@ -30,9 +31,23 @@ export class Broadcast { } } - public unsubscribe() { - this.entries.length = 0 - this.buffer.length = 0 + public setContext(context?: any) { + this.context = context + } + + public getContext() { + return this.context + } + + public unsubscribe(subscriber?: Subscriber) { + if (subscriber) { + this.entries = this.entries.filter(suber => { + return suber.subscriber !== subscriber + }) + } else { + this.entries.length = 0 + this.buffer.length = 0 + } } public flushBuffer({ subscriber, subscription }) { @@ -40,15 +55,15 @@ export class Broadcast { if (isFn(filter)) { const notification = filter(payload, subscription) if (notification !== undefined) { - subscriber(notification) + subscriber.call(this.context, notification) } } else { - subscriber(payload, subscription) + subscriber.call(this.context, payload, subscription) } }) } - public notify(payload: P, filter?: Filter) { + public notify(payload: Payload, filter?: Filter) { if (this.length === 0) { this.buffer.push({ payload, filter }) return @@ -57,10 +72,10 @@ export class Broadcast { if (isFn(filter)) { const notification = filter(payload, subscription) if (notification !== undefined) { - subscriber(notification) + subscriber.call(this.context, notification) } } else { - subscriber(payload, subscription) + subscriber.call(this.context, payload, subscription) } }) this.buffer.length = 0 diff --git a/packages/utils/src/case.ts b/packages/shared/src/case.ts similarity index 100% rename from packages/utils/src/case.ts rename to packages/shared/src/case.ts diff --git a/packages/utils/src/clone.ts b/packages/shared/src/clone.ts similarity index 88% rename from packages/utils/src/clone.ts rename to packages/shared/src/clone.ts index ab7c432cd8a..60a61b676c6 100644 --- a/packages/utils/src/clone.ts +++ b/packages/shared/src/clone.ts @@ -1,5 +1,5 @@ -import { isFn } from '@uform/types' -import { globalThisPolyfill } from './globalThis' +import { isFn } from './types' +import { globalThisPolyfill } from './global' type Filter = (value: any, key: string) => boolean @@ -55,6 +55,15 @@ export const clone = (values: any, filter?: Filter) => { if (values._isAMomentObject) { return values } + if (values._isJSONSchemaObject) { + return values + } + if (isFn(values.toJS)) { + return values + } + if (isFn(values.toJSON)) { + return values + } if (Object.getOwnPropertySymbols(values || {}).length) { return values } diff --git a/packages/utils/src/compare.ts b/packages/shared/src/compare.ts similarity index 60% rename from packages/utils/src/compare.ts rename to packages/shared/src/compare.ts index 6f7b25f688e..b295f41e299 100644 --- a/packages/utils/src/compare.ts +++ b/packages/shared/src/compare.ts @@ -1,4 +1,4 @@ -import { isFn, isArr } from '@uform/types' +import { isFn, isArr } from './types' const isArray = isArr const keyList = Object.keys const hasProp = Object.prototype.hasOwnProperty @@ -6,9 +6,11 @@ const hasProp = Object.prototype.hasOwnProperty type Filter = (comparies: { a: any; b: any }, key: string) => boolean /* eslint-disable */ -function equal(a: any, b: any, filter: Filter) { +function equal(a: any, b: any, filter?: Filter) { // fast-deep-equal index.js 2.0.1 - if (a === b) { return true } + if (a === b) { + return true + } if (a && b && typeof a === 'object' && typeof b === 'object') { const arrA = isArray(a) @@ -19,36 +21,73 @@ function equal(a: any, b: any, filter: Filter) { if (arrA && arrB) { length = a.length - if (length !== b.length) { return false } - for (i = length; i-- !== 0;) { if (!equal(a[i], b[i], filter)) { return false } } + if (length !== b.length) { + return false + } + for (i = length; i-- !== 0; ) { + if (!equal(a[i], b[i], filter)) { + return false + } + } return true } - if (arrA !== arrB) { return false } + if (arrA !== arrB) { + return false + } + const momentA = a && a._isAMomentObject + const momentB = b && b._isAMomentObject + if (momentA !== momentB) return false + if (momentA && momentB) return a.isSame(b) + const immutableA = a && a.toJS + const immutableB = b && b.toJS + if (immutableA !== immutableB) return false + if (immutableA) return a.is ? a.is(b) : a === b + const schemaA = a && a.toJSON + const schemaB = b && b.toJSON + if (schemaA !== schemaB) return false + if (schemaA && schemaB) return equal(a.toJSON(), b.toJSN(), filter) const dateA = a instanceof Date const dateB = b instanceof Date - if (dateA !== dateB) { return false } - if (dateA && dateB) { return a.getTime() === b.getTime() } + if (dateA !== dateB) { + return false + } + if (dateA && dateB) { + return a.getTime() === b.getTime() + } const regexpA = a instanceof RegExp const regexpB = b instanceof RegExp - if (regexpA !== regexpB) { return false } - if (regexpA && regexpB) { return a.toString() === b.toString() } + if (regexpA !== regexpB) { + return false + } + if (regexpA && regexpB) { + return a.toString() === b.toString() + } const urlA = a instanceof URL const urlB = b instanceof URL - if (urlA && urlB) { return a.href === b.href } + if (urlA && urlB) { + return a.href === b.href + } const keys = keyList(a) length = keys.length - if (length !== keyList(b).length) { return false } + if (length !== keyList(b).length) { + return false + } - for (i = length; i-- !== 0;) { if (!hasProp.call(b, keys[i])) { return false } } + for (i = length; i-- !== 0; ) { + if (!hasProp.call(b, keys[i])) { + return false + } + } // end fast-deep-equal // Custom handling for React - for (i = length; i-- !== 0;) { + for (i = length; i-- !== 0; ) { key = keys[i] + if (key === '_owner' && a.$$typeof) { // React-specific: avoid traversing React elements' _owner. // _owner contains circular references @@ -58,11 +97,15 @@ function equal(a: any, b: any, filter: Filter) { } else { if (isFn(filter)) { if (filter({ a: a[key], b: b[key] }, key)) { - if (!equal(a[key], b[key], filter)) { return false } + if (!equal(a[key], b[key], filter)) { + return false + } } } else { // all other properties should be traversed as usual - if (!equal(a[key], b[key], filter)) { return false } + if (!equal(a[key], b[key], filter)) { + return false + } } } } diff --git a/packages/shared/src/deprecate.ts b/packages/shared/src/deprecate.ts new file mode 100644 index 00000000000..f3aefb8a102 --- /dev/null +++ b/packages/shared/src/deprecate.ts @@ -0,0 +1,22 @@ +import { isFn, isStr } from './types' + +export const deprecate = ( + method: any, + message?: string, + help?: string +) => { + if (isFn(method)) { + return function(p1?: P1, p2?: P2, p3?: P3, p4?: P4, p5?: P5) { + deprecate(message, help) + method.apply(this, arguments) + } + } + if (isStr(method)) { + console.error( + new Error( + `${method} has been deprecated. Do not continue to use this api.${message || + ''}` + ) + ) + } +} diff --git a/packages/shared/src/global.ts b/packages/shared/src/global.ts new file mode 100644 index 00000000000..40b3d949c98 --- /dev/null +++ b/packages/shared/src/global.ts @@ -0,0 +1,19 @@ +function globalThis() { + try { + if (typeof self !== 'undefined') { + return self + } + } catch (e) {} + try { + if (typeof window !== 'undefined') { + return window + } + } catch (e) {} + try { + if (typeof global !== 'undefined') { + return global + } + } catch (e) {} + return Function('return this')() +} +export const globalThisPolyfill = globalThis() diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 00000000000..79e1ef38ad6 --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,12 @@ +export * from './array' +export * from './compare' +export * from './types' +export * from './clone' +export * from './isEmpty' +export * from './case' +export * from './string' +export * from './global' +export * from './path' +export * from './deprecate' +export * from './broadcast' +export * from './merge' diff --git a/packages/utils/src/isEmpty.ts b/packages/shared/src/isEmpty.ts similarity index 96% rename from packages/utils/src/isEmpty.ts rename to packages/shared/src/isEmpty.ts index 173787c9787..6d4ee498a3c 100644 --- a/packages/utils/src/isEmpty.ts +++ b/packages/shared/src/isEmpty.ts @@ -2,6 +2,8 @@ const has = Object.prototype.hasOwnProperty const toString = Object.prototype.toString +export const isValid = (val: any) => val !== undefined + export function isEmpty(val: any): boolean { // Null and Undefined... if (val == null) { diff --git a/packages/shared/src/merge.ts b/packages/shared/src/merge.ts new file mode 100644 index 00000000000..c7924efb947 --- /dev/null +++ b/packages/shared/src/merge.ts @@ -0,0 +1,2 @@ +import deeepmerge from 'deepmerge' +export const merge = deeepmerge diff --git a/packages/shared/src/path.ts b/packages/shared/src/path.ts new file mode 100644 index 00000000000..1a4a1d55d93 --- /dev/null +++ b/packages/shared/src/path.ts @@ -0,0 +1,3 @@ +import FormPath, { Pattern as FormPathPattern } from 'cool-path' + +export { FormPath, FormPathPattern } diff --git a/packages/utils/src/stringLength.ts b/packages/shared/src/string.ts similarity index 100% rename from packages/utils/src/stringLength.ts rename to packages/shared/src/string.ts diff --git a/packages/types/src/types.ts b/packages/shared/src/types.ts similarity index 100% rename from packages/types/src/types.ts rename to packages/shared/src/types.ts diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 00000000000..1d669c29c46 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib" + }, + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["./src/__tests__/*"] +} diff --git a/packages/types/README.md b/packages/types/README.md deleted file mode 100644 index fcb94f48de0..00000000000 --- a/packages/types/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# @uform/validator -> UForm数据校验工具 \ No newline at end of file diff --git a/packages/types/src/effects.ts b/packages/types/src/effects.ts deleted file mode 100644 index 1f30157e479..00000000000 --- a/packages/types/src/effects.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IFormActions, IFormPathMatcher } from './form' -import { Observable } from 'rxjs/internal/Observable' -export type Dispatcher = (eventName: string, payload: any) => void -export type IEffects = (selector: ISelector, actions: IFormActions) => void - -export type ISelector = ( - eventName: string, - formPathPattern?: string | IFormPathMatcher -) => Observable diff --git a/packages/types/src/field.ts b/packages/types/src/field.ts deleted file mode 100644 index 7741058ac89..00000000000 --- a/packages/types/src/field.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { IRuleDescription } from './rule' -import { ISchema } from './schema' -import { Path } from './path' -import { IFormPathMatcher } from './form' - -export interface IField { - value: V - valid: boolean - dirty: boolean - invalid: boolean - visible: boolean - display: boolean - hiddenFromParent: boolean - shownFromParent: boolean - required: boolean - editable: boolean - loading: boolean - errors: string[] - effectErrors: string[] - pristine: boolean - initialValue: V - name: string - path: string[] - props: ISchema - rules: IRuleDescription[] - dirtyType: string - lastValidateValue: V - notify: (forceUpdate?: boolean) => void - changeEditable: (editable: boolean | ((name: string) => boolean)) => void - match: (path: Path | IFormPathMatcher) => boolean - initialize: (options: IFieldOptions) => void - publishState: () => IFieldState - onChange: (fn: () => void) => void - updateState: (fn: (state: IFieldState) => void) => void - destructor: () => void - syncContextValue: () => void - pathEqual: (path: Path | IFormPathMatcher) => boolean -} - -export interface IFieldOptions { - path: Path - name?: string - props: any - value?: any - initialValue?: any - onChange?: (...args: any[]) => void -} - -export interface IFieldState { - value: any - valid: boolean - invalid: boolean - visible: boolean - required: boolean - editable: boolean - loading: boolean - errors: string[] - pristine: boolean - initialValue: any - name: string - path: string[] - props: ISchema - rules: IRuleDescription[] -} - -export interface IFieldMap { - [name: string]: IField -} diff --git a/packages/types/src/form.ts b/packages/types/src/form.ts deleted file mode 100644 index 40057cc6425..00000000000 --- a/packages/types/src/form.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { Subject } from 'rxjs/internal/Subject' -import { Path } from './path' -import { IFieldState, IField } from './field' -import { ISchema } from './schema' -import { IEffects } from './effects' - -export interface IFormPayload { - formState: IFormState -} - -export interface IFieldPayload { - fieldState: IFieldState - formState: IFormState -} - -export interface IFieldError { - name: string - errors: string[] -} - -export interface IFormState { - values: V // 表单数据 - initialValues: V // 初始化数据 - valid: boolean // 是否合法 - invalid: boolean // 是否不合法 - errors: IFieldError[] // 错误提示集合 - pristine: boolean // 是否是原始态 - dirty: boolean // 是否存在变化 -} - -export interface ISubscribers { - [eventName: string]: Subject -} - -export interface IFormOptions { - editable: boolean | ((name: string) => boolean) - effects: IEffects - defaultValue?: V - values?: V - initialValues?: V - schema: ISchema - subscribes: ISubscribers - onFormChange: (payload: IFormPayload) => void - onFieldChange: (payload: IFieldPayload) => void - onValidateFailed: (fieldErrors: IFieldError[]) => void - onFormWillInit?: (form: any) => void - onReset: (payload: IFormPayload) => void - onSubmit: (values: any) => Promise | void - traverse?: (schema: ISchema) => ISchema -} - -// 通过 createActions 创建出来的 actions 接口 -export interface IFormActions { - setFieldState: ( - name: Path | IFormPathMatcher, - callback: (fieldState: IFieldState) => void - ) => Promise - - getFieldState: { - ( - name: Path | IFormPathMatcher, - callback: (fieldState: IFieldState) => void - ): void - (name: Path | IFormPathMatcher): IFieldState - } - - getFormState: { - (): IFormState - (callback: (formState: IFormState) => void): void - } - - setFormState: (callback: (formState: IFormState) => void) => Promise - getSchema: (path: Path) => ISchema - reset: ( - forceClear?: boolean | { forceClear?: boolean; validate?: boolean }, - validate?: boolean - ) => void - submit: () => Promise - validate: () => Promise // error will be IFormState['errors'] - dispatch: (type: string, payload: T) => void -} - -// 通过 createAsyncActions 创建出来的 actions 接口 -export interface IAsyncFormActions { - setFieldState: ( - name: Path | IFormPathMatcher, - callback: (fieldState: IFieldState) => void - ) => Promise - - getFieldState: { - ( - name: Path | IFormPathMatcher, - callback: (fieldState: IFieldState) => void - ): Promise - (name: Path | IFormPathMatcher): Promise - } - - getFormState: { - (): Promise - (callback: (formState: IFormState) => void): Promise - } - - setFormState: (callback: (fieldState: IFormState) => void) => Promise - getSchema: (path: Path) => Promise - reset: ( - forceClear?: boolean | { forceClear?: boolean; validate?: boolean }, - validate?: boolean - ) => Promise - submit: () => Promise - validate: () => Promise //reject err will be IFormState['errors'] - dispatch: (type: string, payload: T) => Promise -} - -export interface IFormPathMatcher { - (payload: IField | Path | { fieldState: IFieldState }): boolean - hasWildcard: boolean - pattern: string -} - -export type TextAlign = 'left' | 'right' -export type Size = 'small' | 'medium' | 'large' -export type Layout = 'horizontal' | 'vertical' | 'inline' -export type TextEl = string | JSX.Element | null -export type LabelAlign = 'left' | 'top' | 'inset' - -type ColSpanType = number | string -export interface ColSize { - span?: ColSpanType - order?: ColSpanType - offset?: ColSpanType - push?: ColSpanType - pull?: ColSpanType -} - -export interface ColProps extends React.HTMLAttributes { - span?: ColSpanType - order?: ColSpanType - offset?: ColSpanType - push?: ColSpanType - pull?: ColSpanType - xs?: ColSpanType | ColSize - sm?: ColSpanType | ColSize - md?: ColSpanType | ColSize - lg?: ColSpanType | ColSize - xl?: ColSpanType | ColSize - xxl?: ColSpanType | ColSize - prefixCls?: string -} -// export type ColProps = { span: number; offset?: number } | number - -export interface IFormItemGridProps { - name?: string - help?: React.ReactNode - extra?: React.ReactNode - description?: string - title?: string - cols?: any -} - -interface IFormSharedProps { - labelCol?: ColProps | number - wrapperCol?: ColProps | number - autoAddColon?: boolean - size?: Size - inline?: boolean - labelAlign?: LabelAlign - labelTextAlign?: TextAlign - className?: string - style?: React.CSSProperties - prefix?: string - maxTipsNum?: number -} - -export interface IFormProps extends IFormSharedProps { - layout?: string - children?: React.ReactNode - component?: string - onValidateFailed?: () => void -} - -export interface IFormItemProps extends IFormSharedProps { - id?: string - required?: boolean - label?: React.ReactNode - extra?: React.ReactNode - validateState?: any - isTableColItem?: boolean - help?: React.ReactNode - noMinHeight?: boolean - children?: React.ReactElement - type?: string - schema?: ISchema -} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts deleted file mode 100644 index 1438c5e65b8..00000000000 --- a/packages/types/src/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './schema' -export * from './types' -export * from './validator' -export * from './effects' -export * from './path' -export * from './field' -export * from './form' -export * from './rule' diff --git a/packages/types/src/path.ts b/packages/types/src/path.ts deleted file mode 100644 index cdb40e73793..00000000000 --- a/packages/types/src/path.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type Path = PathNode[] | PathNode | null -export type PathNode = string | number -export type ArrayPath = PathNode[] diff --git a/packages/types/src/rule.ts b/packages/types/src/rule.ts deleted file mode 100644 index 9a949239501..00000000000 --- a/packages/types/src/rule.ts +++ /dev/null @@ -1,36 +0,0 @@ -export interface IRuleDescription { - required?: boolean - message?: string - pattern?: RegExp | string - validator?: Validator - format?: DefaultPatternRule -} - -export type Validator = ( - value: any, - rule: IRuleDescription, - values: any, - name: string -) => string | Promise - -export type DefaultPatternRule = - | 'url' - | 'email' - | 'ipv6' - | 'ipv4' - | 'number' - | 'integer' - | 'qq' - | 'phone' - | 'idcard' - | 'taodomain' - | 'money' - | 'zh' - | 'date' - | 'zip' - -export type Rule = - | Validator - | Array - | DefaultPatternRule - | IRuleDescription diff --git a/packages/types/src/schema.ts b/packages/types/src/schema.ts deleted file mode 100644 index 2bbed5703d3..00000000000 --- a/packages/types/src/schema.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Rule } from './rule' -import { Dispatcher } from './effects' - -type MayBeArray = V | (V[]) - -export interface ISchema { - type?: string - title?: string | JSX.Element - description?: string | JSX.Element - default?: MayBeArray - required?: boolean - enum?: Array<{ label: string | JSX.Element; value: V } | string | number> - enumNames?: string[] - properties?: { - [key: string]: ISchema - } - items?: ISchema - minItems?: number - maxItems?: number - ['x-props']?: { [name: string]: any } - ['x-index']?: number - ['x-rules']?: Rule - ['x-component']?: string - ['x-effect']?: ( - dispatch: Dispatcher, - option?: object - ) => { [key: string]: any } -} diff --git a/packages/types/src/validator.ts b/packages/types/src/validator.ts deleted file mode 100644 index 76225c1bb33..00000000000 --- a/packages/types/src/validator.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IFieldState } from './field' -export interface IValidateResponse { - name: string - value: any - field: IFieldState - invalid: boolean - valid: boolean - errors: string[] -} - -export type ValidateHandler = (response: IValidateResponse[]) => void diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json deleted file mode 100644 index 704cba57071..00000000000 --- a/packages/types/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib" - }, - "include": [ - "./src/**/*.ts", - "./src/**/*.tsx" - ], - "exclude": [ - "./src/__tests__/*" - ] -} diff --git a/packages/utils/src/__tests__/index.spec.js b/packages/utils/src/__tests__/index.spec.js deleted file mode 100644 index c0fa3d45fd3..00000000000 --- a/packages/utils/src/__tests__/index.spec.js +++ /dev/null @@ -1,325 +0,0 @@ -import { getIn, setIn, getPathSegments } from '../accessor' -import { Broadcast } from '../broadcast' -import { isEqual } from '../compare' -import { toArr, every, some, findIndex, find, includes } from '../array' -import { clone } from '../clone' -import { calculateSchemaInitialValues } from '../schema' - -test('test accessor', () => { - const value = { a: { b: { c: 2, d: 333 } } } - expect(getIn(value, 'a.b.c') === 2).toBeTruthy() - setIn(value, 'a.b.c', 1111) - expect(getIn(value, 'a.b.c') === 1111).toBeTruthy() -}) - -test('test accessor with large path', () => { - const value = { array: [{ aa: 123, bb: 321 }] } - expect(isEqual(getIn(value, 'array.0.[aa,bb]'), [123, 321])).toBeTruthy() -}) - -test('test setIn auto create array', () => { - const value = {} - setIn(value, 'array.0.bb.2', 'hello world') - expect( - isEqual(value, { - array: [ - { - bb: [undefined, undefined, 'hello world'] - } - ] - }) - ).toBeTruthy() -}) - -test('getSchema return undefined', () => { - const value = {} - setIn(value, 'array.0.bb.2', 'hello world', () => {}) - - expect( - isEqual(value, { - array: [ - { - bb: [undefined, undefined, 'hello world'] - } - ] - }) - ).toBeTruthy() -}) - -test('test setIn dose not affect other items', () => { - const value = { - aa: [ - { - dd: [ - { - ee: '是' - } - ], - cc: '1111' - } - ] - } - - setIn(value, 'aa.1.dd.0.ee', '否') - expect( - isEqual(value.aa[0], { - dd: [ - { - ee: '是' - } - ], - cc: '1111' - }) - ).toBeTruthy() -}) - -test('destruct getIn', () => { - // getIn 通过解构表达式从扁平数据转为复合嵌套数据 - const value = { a: { b: { c: 2, d: 333 } } } - expect( - isEqual(getIn({ a: { b: { kk: 2, mm: 333 } } }, 'a.b.{c:kk,d:mm}'), { - c: 2, - d: 333 - }) - ).toBeTruthy() - expect( - isEqual( - getIn( - { kk: 2, mm: 333 }, - `{ - a : { - b : { - c : kk, - d : mm - } - } - }` - ), - value - ) - ).toBeTruthy() -}) - -test('destruct setIn', () => { - const value = { a: { b: { c: 2, d: 333 } } } - // setIn 从复杂嵌套结构中解构数据出来对其做赋值处理 - expect( - isEqual( - setIn( - {}, - `{ - a : { - b : { - c, - d - } - } - }`, - value - ), - { c: 2, d: 333 } - ) - ).toBeTruthy() - expect( - isEqual( - setIn( - {}, - ` - [aa,bb] - `, - [123, 444] - ), - { aa: 123, bb: 444 } - ) - ).toBeTruthy() - expect( - isEqual(setIn({}, 'aa.bb.ddd.[aa,bb]', [123, 444]), { - aa: { bb: { ddd: { aa: 123, bb: 444 } } } - }) - ).toBeTruthy() - - expect( - isEqual(setIn({}, 'aa.bb.ddd.[{cc:aa,bb}]', [{ cc: 123, bb: 444 }]), { - aa: { bb: { ddd: { aa: 123, bb: 444 } } } - }) - ).toBeTruthy() -}) - -test('broadcast', () => { - const centerHub = new Broadcast() - const unsubscribe = centerHub.subscribe(payload => { - expect(payload === 111).toBeTruthy() - }) - return new Promise(resolve => { - setTimeout(() => { - centerHub.notify(111) - unsubscribe() - centerHub.notify(222) - resolve() - }, 1000) - }) -}) - -test('toArr', () => { - expect(isEqual(toArr([123]), [123])).toBeTruthy() - expect(isEqual(toArr(123), [123])).toBeTruthy() - expect(isEqual(toArr(null), [])).toBeTruthy() -}) - -test('clone form data', () => { - var dd = new Map() - dd.set('aaa', { bb: 123 }) - var a = { - aa: 123123, - bb: [{ bb: 111 }, { bb: 222 }], - cc: () => { - // eslint-disable-next-line no-console - console.log('123') - }, - dd - } - var cloned = clone(a) - expect(isEqual(cloned, a)).toBeTruthy() - expect(a === cloned).toBeFalsy() - expect(a.bb[0] === cloned.bb[0]).toBeFalsy() - expect(a.dd === cloned.dd).toBeFalsy() - expect(a.dd.get('aaa') === cloned.dd.get('aaa')).toBeTruthy() - expect(a.cc === cloned.cc).toBeTruthy() -}) - -test('filter equal', () => { - var a = { - aa: { - bb: 123 - } - } - var b = { - aa: { - bb: 123 - } - } - - expect(isEqual(a, b)).toBeTruthy() - expect(isEqual(a, b, (_, key) => key !== 'aa')).toBeTruthy() -}) - -test('filter clone', () => { - var a = { - aa: { - bb: 123 - }, - cc: { - dd: [1, 3, 4, 5] - } - } - - var b = clone(a, (_, key) => key !== 'aa') - - expect(a.aa === b.aa).toBeTruthy() - expect(a.cc === b.cc).toBeFalsy() - expect(isEqual(a.cc, b.cc)).toBeTruthy() -}) - -test('setIn', () => { - var values = {} - setIn(values, 'a', '123232323') - expect(isEqual(values.a, '123232323')).toBeTruthy() -}) - -test('calculateSchemaInitialValues', () => { - var values1 = JSON.parse( - '{"type":"object","properties":{"[startDate,endDate]":{"type":"daterange","default":["2019-01-24","2019-01-30"],"z-index":0,"id":"[startDate,endDate]","x-index":0}}}' - ) - var values2 = JSON.parse( - '{"type":"object","properties":{"[startDate,endDate]":{"type":"daterange","default":["2019-01-24",""],"z-index":0,"id":"[startDate,endDate]","x-index":0}}}' - ) - var values3 = JSON.parse( - '{"type":"object","properties":{"[startDate,endDate]":{"type":"daterange","default":["","2019-01-30"],"z-index":0,"id":"[startDate,endDate]","x-index":0}}}' - ) - var values4 = JSON.parse( - '{"type":"object","properties":{"[startDate,endDate]":{"type":"daterange","z-index":0,"id":"[startDate,endDate]","x-index":0}}}' - ) - var result1 = calculateSchemaInitialValues(values1) - var result2 = calculateSchemaInitialValues(values2) - var result3 = calculateSchemaInitialValues(values3) - var result4 = calculateSchemaInitialValues(values4) - expect( - isEqual( - JSON.stringify(result1), - JSON.stringify({ startDate: '2019-01-24', endDate: '2019-01-30' }) - ) - ).toBeTruthy() - expect( - isEqual( - JSON.stringify(result2), - JSON.stringify({ startDate: '2019-01-24', endDate: '' }) - ) - ).toBeTruthy() - expect( - isEqual( - JSON.stringify(result3), - JSON.stringify({ startDate: '', endDate: '2019-01-30' }) - ) - ).toBeTruthy() - expect( - isEqual( - JSON.stringify(result4), - JSON.stringify({ startDate: undefined, endDate: undefined }) - ) - ).toBeTruthy() -}) - -test('getPathSegments', () => { - expect(isEqual(getPathSegments(0), [0])).toBeTruthy() -}) - -test('some', () => { - const values1 = [1, 2, 3, 4, 5] - const values2 = [] - const values3 = { a: 1, b: 2, c: 3 } - const values4 = {} - expect(some(values1, item => item === 3)).toBeTruthy() - expect(some(values1, item => item === 6)).toBeFalsy() - expect(some(values2, () => true)).toBeFalsy() - expect(some(values2, () => false)).toBeFalsy() - expect(some(values3, item => item === 3)).toBeTruthy() - expect(some(values3, item => item === 6)).toBeFalsy() - expect(some(values4, () => true)).toBeFalsy() - expect(some(values4, () => false)).toBeFalsy() -}) - -test('every', () => { - const values1 = [1, 2, 3, 4, 5] - const values2 = [] - const values3 = { a: 1, b: 2, c: 3 } - const values4 = {} - expect(every(values1, item => item < 6)).toBeTruthy() - expect(every(values1, item => item < 3)).toBeFalsy() - expect(every(values2, () => true)).toBeTruthy() - expect(every(values2, () => false)).toBeTruthy() - expect(every(values2, () => false)).toBeTruthy() - expect(every(values3, item => item < 6)).toBeTruthy() - expect(every(values3, item => item < 3)).toBeFalsy() - expect(every(values4, () => false)).toBeTruthy() - expect(every(values4, () => false)).toBeTruthy() -}) - -test('findIndex', () => { - const value = [1, 2, 3, 4, 5] - expect(isEqual(findIndex(value, item => item > 3), 3)).toBeTruthy() - expect(isEqual(findIndex(value, item => item < 3, true), 1)).toBeTruthy() - expect(isEqual(findIndex(value, item => item > 6), -1)).toBeTruthy() -}) - -test('find', () => { - const value = [1, 2, 3, 4, 5] - expect(isEqual(find(value, item => item > 3), 4)).toBeTruthy() - expect(isEqual(find(value, item => item < 3, true), 2)).toBeTruthy() - expect(isEqual(find(value, item => item > 6), void 0)).toBeTruthy() -}) - -test('includes', () => { - const value = [1, 2, 3, 4, 5] - expect(includes(value, 3)).toBeTruthy() - expect(includes(value, 6)).toBeFalsy() -}) diff --git a/packages/utils/src/accessor.ts b/packages/utils/src/accessor.ts deleted file mode 100644 index bcf862d9cee..00000000000 --- a/packages/utils/src/accessor.ts +++ /dev/null @@ -1,594 +0,0 @@ -import { - Path, - PathNode, - ArrayPath, - isStr, - isNum, - isPlainObj, - isArr, - isObj -} from '@uform/types' -import { map, each, every } from './array' -import { LRUMap } from './lru' - -interface ITokenizerHandlers { - name(str: string): void - destructObjectStart(): void - destructObjectEnd(): void - destructArrayStart(): void - destructArrayEnd(): void - destructKey(str: string, isColon?: boolean): void -} - -type Destruct = - | { - [key: string]: string - } - | Path - -type Getter = (obj: any, path: Path, value?: any) => any - -type Setter = ( - obj: any, - path: Path, - value?: any, - getSchema?: (path: string[] | number[]) => any -) => any - -type HasIn = (obj: any, path: Path) => boolean - -function whitespace(c: string) { - return c === ' ' || c === '\n' || c === '\t' || c === '\f' || c === '\r' -} - -function toString(val: Path | null) { - if (!val) { - return '' - } - if (isArr(val)) { - return (val as string[]).join('.') - } - return isStr(val) ? val : '' -} - -const PathCache = new LRUMap(1000) - -export function getPathSegments(path: Path): ArrayPath { - if (isArr(path)) { - return path as string[] - } - if (isStr(path) && path) { - const cached = PathCache.get(path) - if (cached) { - return cached - } - const pathArr = (path as string).split('.') - const parts = [] - - for (let i = 0; i < pathArr.length; i++) { - let p = pathArr[i] - - while (p[p.length - 1] === '\\' && pathArr[i + 1] !== undefined) { - p = p.slice(0, -1) + '.' - p += pathArr[++i] - } - - parts.push(p) - } - PathCache.set(path, parts) - return parts - } - if (isNum(path)) { - return [path as number] - } - return [] -} - -class DestructTokenizer { - private text: string - - private index: number - - private handlers: ITokenizerHandlers - - private state: (char: string, prev?: string) => void - - private declareNameStart: number - - private declareNameEnd: number - - private nbraceCount: number - - private nbracketCount: number - - private EOF: boolean - - private destructKeyStart: number - - private destructKey: string - - constructor(text: string, handlers: ITokenizerHandlers) { - this.text = text - this.index = 0 - this.handlers = handlers - this.state = this.processNameStart - this.declareNameStart = 0 - this.declareNameEnd = 0 - this.nbraceCount = 0 - this.nbracketCount = 0 - } - - public parse() { - let char = '' - let prev = '' - const l = this.text.length - for (; this.index < l; this.index++) { - char = this.text.charAt(this.index) - this.EOF = l - 1 === this.index - this.state(char, prev) - prev = char - } - } - - private processNameStart(char: string) { - if (char === '{' || char === '[') { - this.state = this.processDestructStart - this.index-- - } else if (!whitespace(char)) { - this.declareNameStart = this.index - this.state = this.processName - } - } - - private processName(char: string, prev: string) { - if (whitespace(char)) { - this.declareNameEnd = this.index - this.handlers.name(this.getName()) - } else if (this.EOF) { - this.declareNameEnd = this.index + 1 - this.handlers.name(this.getName()) - } - } - - private processDestructStart(char) { - if (char === '{') { - this.nbraceCount++ - this.handlers.destructObjectStart() - } else if (char === '[') { - this.nbracketCount++ - this.handlers.destructArrayStart() - } else if (!whitespace(char)) { - this.state = this.processDestructKey - this.destructKeyStart = this.index - this.index-- - } - } - - private processDestructKey(char: string, prev: string) { - if (char === '}') { - this.nbraceCount-- - - if (this.nbraceCount || this.nbracketCount) { - this.state = this.processDestructStart - } - if (!whitespace(prev)) { - this.destructKey = this.text.substring( - this.destructKeyStart, - this.index - ) - } - - this.handlers.destructKey(this.destructKey) - this.handlers.destructObjectEnd() - if (!this.nbraceCount && !this.nbracketCount) { - this.index = this.text.length - } - } else if (char === ']') { - this.nbracketCount-- - - if (this.nbraceCount || this.nbracketCount) { - this.state = this.processDestructStart - } - if (!whitespace(prev)) { - this.destructKey = this.text.substring( - this.destructKeyStart, - this.index - ) - } - this.handlers.destructKey(this.destructKey) - this.handlers.destructArrayEnd() - if (!this.nbraceCount && !this.nbracketCount) { - this.index = this.text.length - } - } else if (whitespace(char) || char === ':' || char === ',') { - if (!whitespace(prev)) { - this.destructKey = this.text.substring( - this.destructKeyStart, - this.index - ) - } - if (!whitespace(char)) { - this.state = this.processDestructStart - this.handlers.destructKey(this.destructKey, char === ':') - } - } - } - - private getName() { - return this.text.substring(this.declareNameStart, this.declareNameEnd) - } -} - -const parseDestruct = (str: PathNode) => { - if (!isStr(str)) { - return str - } - - let destruct: Destruct - const stack = [] - let token = '' - let realKey = '' - let lastDestruct: Destruct - let root: Destruct - - new DestructTokenizer(str as string, { - name(key: string) { - root = key - }, - destructKey(key, readyReplace) { - if (!key) { - return - } - token = key - if (readyReplace) { - realKey = key - lastDestruct = destruct - return - } - if (isArr(destruct)) { - ;(destruct as string[]).push(key) - } else if (isPlainObj(destruct)) { - destruct[realKey && lastDestruct === destruct ? realKey : key] = key - } - realKey = '' - lastDestruct = destruct - }, - destructArrayStart() { - if (!destruct) { - root = [] - destruct = root - } else { - destruct = [] - } - const tail = stack[stack.length - 1] - if (isPlainObj(tail)) { - tail[token] = destruct - } else if (isArr(tail)) { - tail.push(destruct) - } - stack.push(destruct) - }, - destructObjectStart() { - if (!destruct) { - root = {} - destruct = root - } else { - destruct = {} - } - const tail = stack[stack.length - 1] - if (isPlainObj(tail)) { - tail[token] = destruct - } else if (isArr(tail)) { - tail.push(destruct) - } - stack.push(destruct) - }, - destructArrayEnd() { - stack.pop() - destruct = stack[stack.length - 1] - }, - destructObjectEnd() { - stack.pop() - destruct = stack[stack.length - 1] - } - }).parse() - return root -} - -const traverse = (obj: any, callback: any) => { - const internalTraverse = (internalObj: any, path: string[]) => { - if (isStr(internalObj)) { - return callback(internalObj, internalObj) - } - each(internalObj, (item: any, key: string) => { - const newPath = path.concat(key) - if (isArr(item) || isPlainObj(item)) { - internalTraverse(item, newPath) - } else { - callback(newPath, item) - } - }) - } - - return internalTraverse(obj, []) -} - -const mapReduce = (obj: any, callback: any) => { - const internalTraverse = (internalObj: any, path: string[]) => { - return map(internalObj, (item: any, key: string) => { - const newPath = path.concat(key) - if (isArr(item) || isPlainObj(item)) { - return internalTraverse(item, newPath) - } else { - return callback( - newPath, - newPath.slice(0, newPath.length - 1).concat(item) - ) - } - }) - } - - return internalTraverse(obj, []) -} - -const parseDesturctPath = (path: Path): any => { - const newPath = getPathSegments(path) - const lastKey = newPath[newPath.length - 1] - const startPath = newPath.slice(0, newPath.length - 1) - const destruct = parseDestruct(lastKey) - return { - path: newPath, - lastKey, - startPath, - destruct - } -} - -const parsePaths = (path: Path): any => { - const result = [] - const parsed = parseDesturctPath(path) - if (isStr(parsed.destruct)) { - return path - } else if (parsed.destruct) { - traverse(parsed.destruct, (internalPath, key) => { - result.push({ - path: parsed.startPath.concat(internalPath), - startPath: parsed.startPath, - endPath: internalPath, - key - }) - }) - return result - } else { - return path - } -} - -const resolveGetIn = (get: Getter) => { - const cache = new Map() - return (obj: any, path: Path, value?: any): any => { - let ast = null - - if (!cache.get(path)) { - ast = parseDesturctPath(path) - cache.set(path, ast) - } else { - ast = cache.get(path) - } - if (!isArr(ast.destruct) && !isPlainObj(ast.destruct)) { - return get(obj, path, value) - } - return mapReduce(ast.destruct, (mapPath, key) => { - return get(obj, ast.startPath.concat(key[key.length - 1])) - }) - } -} - -const resolveUpdateIn = (update: Setter, internalGetIn: Getter) => { - const cache = new Map() - return ( - obj: any, - path: Path, - value?: any, - getSchema?: (path: string[] | number[]) => any - ) => { - let paths: any = [] - if (!cache.get(path)) { - paths = parsePaths(path) - cache.set(path, paths) - } else { - paths = cache.get(path) - } - if (!isArr(paths)) { - return update(obj, path, value, getSchema) - } - if (paths && paths.length) { - each(paths, ({ mapPath, key, startPath, endPath }) => { - update( - obj, - startPath.concat(key), - internalGetIn(value, endPath), - getSchema - ) - }) - } - return obj - } -} - -const resolveExistIn = (has: HasIn) => { - const cache = new Map() - return (obj: any, path: Path) => { - let paths: any = [] - if (!cache.get(path)) { - paths = parsePaths(path) - cache.set(path, paths) - } else { - paths = cache.get(path) - } - if (!isArr(paths)) { - return has(obj, path) - } - if (paths && paths.length) { - return every(paths, ({ startPath, key }) => { - return has(obj, startPath.concat(key)) - }) - } - - return false - } -} - -function _getIn(obj: any, path: Path, value: any) { - if (!isObj(obj) || !path) { - return obj - } - - path = toString(path) - - if (path in obj) { - return obj[path as string] - } - - const pathArr = getPathSegments(path) - - for (let i = 0; i < pathArr.length; i++) { - if (!Object.prototype.propertyIsEnumerable.call(obj, pathArr[i])) { - return value - } - - obj = obj[pathArr[i]] - - if (obj === undefined || obj === null) { - // `obj` is either `undefined` or `null` so we want to stop the loop, and - // if this is not the last bit of the path, and - // if it did't return `undefined` - // it would return `null` if `obj` is `null` - // but we want `get({foo: null}, 'foo.bar')` to equal `undefined`, or the supplied value, not `null` - if (i !== pathArr.length - 1) { - return value - } - - break - } - } - - return obj -} - -function _setIn( - obj: any, - path: Path, - value: any, - getSchema?: (path: string[] | number[]) => any -) { - if (!isObj(obj) || !path) { - return - } - - path = toString(path) - - if (path in obj) { - obj[path as string] = value - return - } - - const pathArr = getPathSegments(path) - - for (let i = 0; i < pathArr.length; i++) { - const p = pathArr[i] - if (!isObj(obj[p])) { - if (obj[p] === undefined && value === undefined) { - return - } - if (/^\d+$/.test(pathArr[i + 1 + ''])) { - if (getSchema) { - const schema = getSchema(pathArr.slice(0, i) as string[]) - - if (!schema || schema.type === 'array') { - obj[p] = [] - } else { - obj[p] = {} - } - } else { - obj[p] = [] - } - } else { - obj[p] = {} - } - } - - if (i === pathArr.length - 1) { - obj[p] = value - } - - obj = obj[p] - } -} - -function _deleteIn(obj: any, path: Path) { - if (!isObj(obj) || !path) { - return - } - - path = toString(path) - - if (path in obj) { - delete obj[path as string] - return - } - - const pathArr = getPathSegments(path) - - for (let i = 0; i < pathArr.length; i++) { - const p = pathArr[i] - - if (i === pathArr.length - 1) { - if (isArr(obj)) { - obj.splice(p as number, 1) - } else { - delete obj[p] - } - return - } - - obj = obj[p] - - if (!isObj(obj)) { - return - } - } -} - -function _existIn(obj: any, path: Path) { - if (!isObj(obj) || !path) { - return false - } - - path = toString(path) - - if (path in obj) { - return true - } - - const pathArr = getPathSegments(path) - - for (let i = 0; i < pathArr.length; i++) { - if (isObj(obj)) { - if (!(pathArr[i] in obj)) { - return false - } - - obj = obj[pathArr[i]] - } else { - return false - } - } - - return true -} -export const getIn = resolveGetIn(_getIn) -export const setIn = resolveUpdateIn(_setIn, getIn) -export const deleteIn = resolveUpdateIn(_deleteIn, getIn) -export const existIn = resolveExistIn(_existIn) -export { parseDesturctPath, parseDestruct, parsePaths } diff --git a/packages/utils/src/defer.ts b/packages/utils/src/defer.ts deleted file mode 100644 index e47fbfd8a44..00000000000 --- a/packages/utils/src/defer.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const defer = () => { - let internalResolve: (payload: P) => void - let internalReject: (error: E) => void - const promise = new Promise((resolve, reject) => { - internalResolve = resolve - internalReject = reject - }) - return { - promise, - resolve: internalResolve, - reject: internalReject - } -} diff --git a/packages/utils/src/globalThis.ts b/packages/utils/src/globalThis.ts deleted file mode 100644 index 99af5ea3b72..00000000000 --- a/packages/utils/src/globalThis.ts +++ /dev/null @@ -1,13 +0,0 @@ -function globalThis() { - if (typeof self !== 'undefined') { - return self - } - if (typeof window !== 'undefined') { - return window - } - if (typeof global !== 'undefined') { - return global - } - return Function('return this')() -} -export const globalThisPolyfill = globalThis() diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts deleted file mode 100644 index 43661128d1d..00000000000 --- a/packages/utils/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * from './accessor' -export * from './array' -export * from './compare' -export * from './broadcast' -export * from '@uform/types' -export * from './clone' -export * from './schema' -export * from './lru' -export * from './isEmpty' -export * from './case' -export * from './defer' -export * from './stringLength' -export * from './globalThis' diff --git a/packages/utils/src/lru.ts b/packages/utils/src/lru.ts deleted file mode 100644 index ddeefacf677..00000000000 --- a/packages/utils/src/lru.ts +++ /dev/null @@ -1,310 +0,0 @@ -/** - * A doubly linked list-based Least Recently Used (LRU) cache. Will keep most - * recently used items while discarding least recently used items when its limit - * is reached. - * - * Licensed under MIT. Copyright (c) 2010 Rasmus Andersson - * See README.md for details. - * - * Illustration of the design: - * - * entry entry entry entry - * ______ ______ ______ ______ - * | head |.newer => | |.newer => | |.newer => | tail | - * | A | | B | | C | | D | - * |______| <= older.|______| <= older.|______| <= older.|______| - * - * removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added - */ -/* eslint-disable */ - -const NEWER = Symbol('newer') -const OLDER = Symbol('older') - -export function LRUMap(limit: number, entries?: any) { - if (typeof limit !== 'number') { - // called as (entries) - entries = limit - limit = 0 - } - - this.size = 0 - this.limit = limit - this.oldest = this.newest = undefined - this._keymap = new Map() - - if (entries) { - this.assign(entries) - if (limit < 1) { - this.limit = this.size - } - } -} - -function Entry(key: any, value: any) { - this.key = key - this.value = value - this[NEWER] = undefined - this[OLDER] = undefined -} - -LRUMap.prototype._markEntryAsUsed = function(entry: any) { - if (entry === this.newest) { - // Already the most recenlty used entry, so no need to update the list - return - } - // HEAD--------------TAIL - // <.older .newer> - // <--- add direction -- - // A B C E - if (entry[NEWER]) { - if (entry === this.oldest) { - this.oldest = entry[NEWER] - } - entry[NEWER][OLDER] = entry[OLDER] // C <-- E. - } - if (entry[OLDER]) { - entry[OLDER][NEWER] = entry[NEWER] // C. --> E - } - entry[NEWER] = undefined // D --x - entry[OLDER] = this.newest // D. --> E - if (this.newest) { - this.newest[NEWER] = entry // E. <-- D - } - this.newest = entry -} - -LRUMap.prototype.assign = function(entries: any) { - let entry: any - let limit = this.limit || Number.MAX_VALUE - this._keymap.clear() - const it = entries[Symbol.iterator]() - for (let itv = it.next(); !itv.done; itv = it.next()) { - const e = new Entry(itv.value[0], itv.value[1]) - this._keymap.set(e.key, e) - if (!entry) { - this.oldest = e - } else { - entry[NEWER] = e - e[OLDER] = entry - } - entry = e - if (limit-- === 0) { - throw new Error('overflow') - } - } - this.newest = entry - this.size = this._keymap.size -} - -LRUMap.prototype.get = function(key: any) { - // First, find our cache entry - const entry = this._keymap.get(key) - if (!entry) { return } // Not cached. Sorry. - // As was found in the cache, register it as being requested recently - this._markEntryAsUsed(entry) - return entry.value -} - -LRUMap.prototype.set = function(key: any, value: any) { - let entry = this._keymap.get(key) - - if (entry) { - // update existing - entry.value = value - this._markEntryAsUsed(entry) - return this - } - - // new entry - this._keymap.set(key, (entry = new Entry(key, value))) - - if (this.newest) { - // link previous tail to the new tail (entry) - this.newest[NEWER] = entry - entry[OLDER] = this.newest - } else { - // we're first in -- yay - this.oldest = entry - } - - // add new entry to the end of the linked list -- it's now the freshest entry. - this.newest = entry - ++this.size - if (this.size > this.limit) { - // we hit the limit -- remove the head - this.shift() - } - - return this -} - -LRUMap.prototype.shift = function() { - // todo: handle special case when limit == 1 - const entry = this.oldest - if (entry) { - if (this.oldest[NEWER]) { - // advance the list - this.oldest = this.oldest[NEWER] - this.oldest[OLDER] = undefined - } else { - // the cache is exhausted - this.oldest = undefined - this.newest = undefined - } - // Remove last strong reference to and remove links from the purged - // entry being returned: - entry[NEWER] = entry[OLDER] = undefined - this._keymap.delete(entry.key) - --this.size - return [entry.key, entry.value] - } -} - -// ---------------------------------------------------------------------------- -// Following code is optional and can be removed without breaking the core -// functionality. - -LRUMap.prototype.find = function(key: any) { - const e = this._keymap.get(key) - return e ? e.value : undefined -} - -LRUMap.prototype.has = function(key: any) { - return this._keymap.has(key) -} - -LRUMap.prototype.delete = function(key: any) { - const entry = this._keymap.get(key) - if (!entry) { return } - this._keymap.delete(entry.key) - if (entry[NEWER] && entry[OLDER]) { - // relink the older entry with the newer entry - entry[OLDER][NEWER] = entry[NEWER] - entry[NEWER][OLDER] = entry[OLDER] - } else if (entry[NEWER]) { - // remove the link to us - entry[NEWER][OLDER] = undefined - // link the newer entry to head - this.oldest = entry[NEWER] - } else if (entry[OLDER]) { - // remove the link to us - entry[OLDER][NEWER] = undefined - // link the newer entry to head - this.newest = entry[OLDER] - } else { - // if(entry[OLDER] === undefined && entry.newer === undefined) { - this.oldest = this.newest = undefined - } - - this.size-- - return entry.value -} - -LRUMap.prototype.clear = function() { - // Not clearing links should be safe, as we don't expose live links to user - this.oldest = this.newest = undefined - this.size = 0 - this._keymap.clear() -} - -function EntryIterator(oldestEntry: any) { - this.entry = oldestEntry -} -EntryIterator.prototype[Symbol.iterator] = function() { - return this -} -EntryIterator.prototype.next = function() { - const ent = this.entry - if (ent) { - this.entry = ent[NEWER] - return { done: false, value: [ent.key, ent.value] } - } else { - return { done: true, value: undefined } - } -} - -function KeyIterator(oldestEntry) { - this.entry = oldestEntry -} -KeyIterator.prototype[Symbol.iterator] = function() { - return this -} -KeyIterator.prototype.next = function() { - const ent = this.entry - if (ent) { - this.entry = ent[NEWER] - return { done: false, value: ent.key } - } else { - return { done: true, value: undefined } - } -} - -function ValueIterator(oldestEntry) { - this.entry = oldestEntry -} -ValueIterator.prototype[Symbol.iterator] = function() { - return this -} -ValueIterator.prototype.next = function() { - const ent = this.entry - if (ent) { - this.entry = ent[NEWER] - return { done: false, value: ent.value } - } else { - return { done: true, value: undefined } - } -} - -LRUMap.prototype.keys = function() { - return new KeyIterator(this.oldest) -} - -LRUMap.prototype.values = function() { - return new ValueIterator(this.oldest) -} - -LRUMap.prototype.entries = function() { - return this -} - -LRUMap.prototype[Symbol.iterator] = function() { - return new EntryIterator(this.oldest) -} - -LRUMap.prototype.forEach = function(fun: (value: any, key: any, ctx: object) => void, thisObj: any) { - if (typeof thisObj !== 'object') { - thisObj = this - } - let entry = this.oldest - while (entry) { - fun.call(thisObj, entry.value, entry.key, this) - entry = entry[NEWER] - } -} - -/** Returns a JSON (array) representation */ -LRUMap.prototype.toJSON = function() { - const s = new Array(this.size) - let i = 0 - let entry = this.oldest - while (entry) { - s[i++] = { key: entry.key, value: entry.value } - entry = entry[NEWER] - } - return s -} - -/** Returns a String representation */ -LRUMap.prototype.toString = function() { - let s = '' - let entry = this.oldest - while (entry) { - s += String(entry.key) + ':' + entry.value - entry = entry[NEWER] - if (entry) { - s += ' < ' - } - } - return s -} diff --git a/packages/utils/src/schema.ts b/packages/utils/src/schema.ts deleted file mode 100644 index cf59e611816..00000000000 --- a/packages/utils/src/schema.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { each, toArr } from './array' -import { getIn, setIn } from './accessor' -import { isFn, Path, ISchema, ArrayPath } from '@uform/types' -import { isEmpty } from './isEmpty' -const numberRE = /^\d+$/ -const VIRTUAL_BOXES = {} - -interface IRuleDescription { - required?: boolean - message?: string - pattern?: RegExp | string - validator?: RuleHandler -} - -interface IPathInfo { - name: string - path: string[] - schemaPath: string[] -} - -type RuleHandler = ( - value: any, - rule: IRuleDescription, - values: object, - name: string -) => string | null - -export const getSchemaNodeFromPath = (schema: ISchema, path: Path) => { - let res = schema - let suc = 0 - path = toArr(path) - for (let i = 0; i < path.length; i++) { - const key = path[i] - if (res && !isEmpty(res.properties)) { - res = res.properties[key] - suc++ - } else if (res && !isEmpty(res.items) && numberRE.test(key as string)) { - res = res.items - suc++ - } - } - return suc === path.length ? res : undefined -} - -export const schemaIs = (schema: ISchema, type: string) => { - return schema && schema.type === type -} - -export const isVirtualBox = (name: string) => { - return !!VIRTUAL_BOXES[name] -} - -export const registerVirtualboxFlag = (name: string) => { - VIRTUAL_BOXES[name] = true -} - -const isVirtualBoxSchema = (schema: ISchema) => { - return isVirtualBox(schema.type) || isVirtualBox(schema['x-component']) -} - -const schemaTraverse = ( - schema: ISchema, - callback: any, - path: ArrayPath = [], - schemaPath = [] -) => { - if (schema) { - if (isVirtualBoxSchema(schema)) { - path = path.slice(0, path.length - 1) - } - callback(schema, { path, schemaPath }) - if (schemaIs(schema, 'object') || schema.properties) { - each(schema.properties, (subSchema, key) => { - schemaTraverse( - subSchema, - callback, - path.concat(key), - schemaPath.concat(key) - ) - }) - } else if (schemaIs(schema, 'array') || schema.items) { - if (schema.items) { - callback( - schema.items, - key => { - schemaTraverse( - schema.items, - callback, - path.concat(key), - schemaPath.concat(key) - ) - }, - path - ) - } - } - } -} - -export const calculateSchemaInitialValues = ( - schema: ISchema, - initialValues: any, - callback?: (pathInfo: IPathInfo, schema: ISchema, value: any) => void -) => { - initialValues = initialValues || schema.default || {} - schemaTraverse(schema, (subSchema, $path, parentPath) => { - const defaultValue = subSchema.default - if (isFn($path) && parentPath) { - each(toArr(getIn(initialValues, parentPath)), (value, index) => { - $path(index) - }) - } else if ($path) { - const isVirtualBoxInstance = isVirtualBoxSchema(subSchema) - const name = isVirtualBoxInstance - ? $path.schemaPath.join('.') - : $path.path.join('.') - const path = isVirtualBoxInstance ? $path.schemaPath : $path.path - const schemaPath = $path.schemaPath - const initialValue = getIn(initialValues, name) - const value = !isEmpty(initialValue) ? initialValue : defaultValue - if (!isEmpty(value)) { - setIn(initialValues, name, value) - } - if (callback && isFn(callback)) { - const newPath = { - name, - path, - schemaPath - } - callback(newPath, subSchema, value) - } - } - }) - return initialValues -} diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json deleted file mode 100644 index 704cba57071..00000000000 --- a/packages/utils/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib" - }, - "include": [ - "./src/**/*.ts", - "./src/**/*.tsx" - ], - "exclude": [ - "./src/__tests__/*" - ] -} diff --git a/packages/validator/package.json b/packages/validator/package.json index 59a84fbe377..d3298e901bb 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@uform/validator", - "version": "0.4.3", + "version": "0.4.0", "license": "MIT", "main": "lib", "repository": { @@ -25,8 +25,8 @@ "@babel/runtime": "^7.4.4" }, "dependencies": { - "@uform/types": "^0.4.3", - "@uform/utils": "^0.4.3" + "@uform/shared": "^0.4.0", + "@uform/types": "^0.4.0" }, "publishConfig": { "access": "public" diff --git a/packages/validator/src/__tests__/index.spec.js b/packages/validator/src/__tests__/index.spec.js deleted file mode 100644 index 37f9c1ef391..00000000000 --- a/packages/validator/src/__tests__/index.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import { runValidation } from '../index' - -const testResponse = (response, tester) => { - response.forEach(item => { - expect(tester[item.name] === item.valid).toBeTruthy() - }) -} - -test('sample validation', () => { - const values = { - a: 123.333, - b: '123123', - c: '123aa', - d: [null, null, null] - } - - const fieldMap = { - a: { - rules: 'integer' - }, - b: { - rules: 'url' - }, - c: { - rules(value) { - return new Promise(resolve => { - setTimeout(() => { - resolve() - }, 2000) - }) - } - }, - d: { - rules: { required: true } - } - } - - return runValidation(values, fieldMap).then(response => { - testResponse(response, { - a: false, - b: false, - c: true, - d: false - }) - }) -}) diff --git a/packages/validator/src/__tests__/index.spec.ts b/packages/validator/src/__tests__/index.spec.ts new file mode 100644 index 00000000000..cf541d6dc44 --- /dev/null +++ b/packages/validator/src/__tests__/index.spec.ts @@ -0,0 +1,185 @@ +import { FormValidator } from '../index' + +const batchTestRules = async (rules: any[], options?: any) => { + const validator = new FormValidator(options) + rules.forEach(({ value, rules, errors = [], warnings = [] }, index) => { + validator.register(`$${index}`, validate => + validate(value, rules).then(t => { + expect(t.errors).toEqual(errors) + expect(t.warnings).toEqual(warnings) + }) + ) + }) + await validator.validate() +} + +test('register', async () => { + const validator = new FormValidator() + let errors1: string[] + let errors2: string[] + validator.register('a.b.c.e', validate => { + validate('123', { required: true }).then(({ errors }) => { + errors1 = errors + }) + }) + validator.register('a.b.c', validate => { + validate('', { required: true }).then(({ errors }) => { + errors2 = errors + }) + }) + const validateResponse = await validator.validate() + expect(errors1).toEqual([]) + expect(errors2).toEqual(['This field is required']) + expect(validateResponse).toEqual({ + errors: [{ path: 'a.b.c', messages: ['This field is required'] }], + warnings: [] + }) +}) + +test('required', async () => { + await batchTestRules([ + { + value: '', + rules: { + required: true + }, + errors: ['This field is required'] + }, + { + value: '', + rules: { + required: true, + message: '该字段不能为空' + }, + errors: ['该字段不能为空'] + }, + { + value: [], + rules: { + required: true, + message: '该字段不能为空' + }, + errors: ['该字段不能为空'] + }, + { + value: false, + rules: { + required: true, + message: '该字段不能为空' + }, + errors: [] + }, + { + value: [], + rules: { + validator: (value: any) => { + return value.length == 0 ? '数组不能为空' : '' + } + }, + errors: ['数组不能为空'] + }, + { + value: [], + rules: { + validator: (value: any) => { + return new Promise(resolve => { + setTimeout(() => { + resolve(value.length == 0 ? 'async validate failed' : '') + }, 1000) + }) + } + }, + errors: ['async validate failed'] + } + ]) +}) + +test('pattern', async () => { + await batchTestRules([ + { + value: '123', + rules: { + pattern: /^[A-Z]+$/, + message: 'must be upper case letters' + }, + errors: ['must be upper case letters'] + }, + { + value: 'HUYUY', + rules: { + pattern: /^[A-Z]+$/, + message: 'must be upper case letters' + }, + errors: [] + } + ]) +}) + +test('max', async () => { + await batchTestRules([ + { + value: '123', + rules: { + max: 2 + }, + errors: ['The length of 123 must be at most 2'] + }, + { + value: '123', + rules: { + max: 3, + message: 'The length of 123 must be at most 3' + }, + errors: [] + } + ]) +}) + +test('whitespace', async () => { + await batchTestRules([ + { + value: ' ', + rules: { + whitespace: true + }, + errors: ['This field cannot be empty'] + } + ]) +}) + +test('validateFirst', async () => { + const secondValidator = jest.fn() + await batchTestRules( + [ + { + value: ' ', + rules: [ + { + whitespace: true + }, + secondValidator + ], + errors: ['This field cannot be empty'] + } + ], + { + validateFirst: true + } + ) + expect(secondValidator).toBeCalledTimes(0) +}) + +//内置正则库测试 +test('formats', async () => { + //todo +}) + +//模板引擎测试 +test('template', async () => { + //todo +}) + +//自定义规则测试 +test('custom rules', async () => { + //todo +}) diff --git a/packages/validator/src/validators/regexp.ts b/packages/validator/src/formats.ts similarity index 100% rename from packages/validator/src/validators/regexp.ts rename to packages/validator/src/formats.ts diff --git a/packages/validator/src/index.ts b/packages/validator/src/index.ts index 3cfbd1f7569..9c92a315a6f 100644 --- a/packages/validator/src/index.ts +++ b/packages/validator/src/index.ts @@ -1,139 +1,3 @@ -import { - each, - reduce, - isFn, - toArr, - getIn, - isArr, - isEqual, - clone, - format, - isEmpty -} from './utils' -import { validate } from './validators' -import { ValidateHandler, IValidateResponse, IFieldMap } from '@uform/types' export * from './message' - -const flatArr = (arr: any[]) => { - return reduce( - arr, - (buf: any, item: any) => { - return isArr(item) - ? buf.concat(flatArr(item)) - : item - ? buf.concat(item) - : buf - }, - [] - ) -} - -export { format } - -export const runValidation = async ( - values: object, - fieldMap: IFieldMap, - forceUpdate?: boolean | ValidateHandler, - callback?: ValidateHandler -): Promise => { - const queue = [] - if (isFn(forceUpdate)) { - callback = forceUpdate as ValidateHandler - forceUpdate = false - } - each(fieldMap, (field, name) => { - const value = getIn(values, name) - if ( - field.visible === false || - field.display === false || - field.editable === false - ) { - return - } - if (!forceUpdate) { - if (field.pristine) return - if (isEmpty(field.lastValidateValue) && isEmpty(value)) return - if (isEqual(field.lastValidateValue, value)) { - return - } - } - - const title = field.props && field.props.title - const rafId = setTimeout(() => { - field.loading = true - field.dirty = true - if (field.notify) { - field.notify() - } - }, 100) - queue.push( - Promise.all( - toArr(field.rules).map(rule => { - return validate(value, rule, values, (title as string) || name) - }) - ).then(errors => { - clearTimeout(rafId) - const lastFieldErrors = toArr(field.errors) - const lastValid = field.valid - const lastLoading = field.loading - const newErrors = flatArr(toArr(errors)) - const effectErrors = flatArr(toArr(field.effectErrors)) - field.loading = false - field.errors = newErrors - field.effectErrors = effectErrors - if (forceUpdate) { - if (newErrors.length || effectErrors.length) { - field.valid = false - field.invalid = true - } else { - field.valid = true - field.invalid = false - } - field.dirty = true - } else { - if (!field.pristine) { - if (newErrors.length || effectErrors.length) { - field.valid = false - field.invalid = true - } else { - field.valid = true - field.invalid = false - } - if ( - !isEqual(lastValid, field.valid) || - !isEqual(lastFieldErrors, field.errors) - ) { - field.dirty = true - } - } - } - - if (field.loading !== lastLoading) { - field.dirty = true - } - - if (field.dirty && field.notify) { - field.notify() - } - field.lastValidateValue = clone(value) - return { - name, - value, - field, - invalid: field.invalid, - valid: field.valid, - errors: newErrors.concat(effectErrors) - } - }) - ) - }) - - return Promise.all(queue).then(response => { - if (isFn(callback)) { - callback(response) - } - return response - }) -} - -export default runValidation +export * from './validator' +export * from './types' diff --git a/packages/validator/src/locale.ts b/packages/validator/src/locale.ts new file mode 100644 index 00000000000..fe62480e453 --- /dev/null +++ b/packages/validator/src/locale.ts @@ -0,0 +1,54 @@ +export default { + en: { + pattern: 'This field does not match any pattern', + required: 'This field is required', + number: 'This field is not a number', + integer: 'This field is not an integer number', + url: 'This field is a invalid url', + email: 'This field is not a email format', + ipv6: 'This field is not a ipv6 format', + ipv4: 'This field is not a ipv4 format', + idcard: 'This field is not an idcard format', + taodomain: 'This field is not a taobao domain format', + qq: 'This field is not a qq number format', + phone: 'This field is not a phone number format', + money: 'This field is not a currency format', + zh: 'This field is not a chinese string', + date: 'This field is not a valid date format', + zip: 'This field is not a zip format', + len: 'The length or number of entries must be {{len}}', + min: 'The length or number of entries must be at least {{min}}', + maximum: 'The value cannot be greater than {{maximum}}', + exclusiveMaximum: 'The value must be less than {{exclusiveMaximum}}', + minimum: 'The value cannot be less than {{minimum}}', + exclusiveMinimum: 'The value must be greater than {{exclusiveMinimum}}', + max: 'The length or number of entries must be at most {{max}}', + whitespace: 'This field cannot be empty' + }, + zh: { + pattern: '该字段不是一个合法的字段', + required: '该字段是必填字段', + number: '该字段不是合法的数字', + integer: '该字段不是合法的整型数字', + url: '该字段不是合法的url', + email: '该字段不是合法的邮箱格式', + ipv6: '该字段不是合法的ipv6格式', + ipv4: '该字段不是合法的ipv4格式', + idcard: '该字段不是合法的身份证格式', + taodomain: '该字段不符合淘系域名规则', + qq: '该字段不符合QQ号格式', + phone: '该字段不是有效的手机号', + money: '该字段不是有效货币格式', + zh: '该字段不是合法的中文字符串', + date: '该字段不是合法的日期格式', + zip: '该字段不是合法的邮编格式', + len: '长度或条目数必须为{{len}}', + min: '长度或条目数不能小于{{min}}', + max: '长度或条目数不能大于{{max}}', + maximum: '数值不能大于{{maximum}}', + exclusiveMaximum: '数值必须小于{{exclusiveMaximum}}', + minimum: '数值不能小于{{minimum}}', + exclusiveMinimum: '数值必须大于{{exclusiveMinimum}}', + whitespace: 'This field cannot be empty' + } +} diff --git a/packages/validator/src/locale/index.ts b/packages/validator/src/locale/index.ts deleted file mode 100644 index 8c88dd4a724..00000000000 --- a/packages/validator/src/locale/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -export default { - en: { - pattern: '%s value %s does not match pattern %s', - required: '%s is required', - number: '%s is not a number', - integer: '%s is not an integer number', - url: '%s is a invalid url', - email: '%s is not a email format', - ipv6: '%s is not a ipv6 format', - ipv4: '%s is not a ipv4 format', - idcard: '%s is not an idcard format', - taodomain: '%s is not a taobao domain format', - qq: '%s is not a qq number format', - phone: '%s is not a phone number format', - money: '%s is not a currency format', - zh: '%s is not a chinese string', - date: '%s is not a valid date format', - zip: '%s is not a zip format' - }, - zh: { - pattern: '该字段不是一个合法的字段', - required: '该字段是必填字段', - number: '该字段不是合法的数字', - integer: '该字段不是合法的整型数字', - url: '该字段不是合法的url', - email: '该字段不是合法的邮箱格式', - ipv6: '该字段不是合法的ipv6格式', - ipv4: '该字段不是合法的ipv4格式', - idcard: '该字段不是合法的身份证格式', - taodomain: '该字段不符合淘系域名规则', - qq: '该字段不符合QQ号格式', - phone: '该字段不是有效的手机号', - money: '该字段不是有效货币格式', - zh: '该字段不是合法的中文字符串', - date: '该字段不是合法的日期格式', - zip: '该字段不是合法的邮编格式' - } -} diff --git a/packages/validator/src/message.ts b/packages/validator/src/message.ts index cb5388f2d76..d36b70a5e76 100644 --- a/packages/validator/src/message.ts +++ b/packages/validator/src/message.ts @@ -1,6 +1,13 @@ -import { getIn, each, globalThisPolyfill } from './utils' +import { + FormPath, + each, + globalThisPolyfill, + merge as deepmerge +} from '@uform/shared' import locales from './locale' +const getIn = FormPath.getIn + const self: any = globalThisPolyfill export interface ILocaleMessages { @@ -34,19 +41,26 @@ const getMatchLang = (lang: string) => { return find } -export const setLocale = (locale: ILocales) => { - Object.assign(LOCALE.messages, locale) +export const setValidationLocale = (locale: ILocales) => { + LOCALE.messages = deepmerge(LOCALE.messages, locale) } -export const setLanguage = (lang: string) => { +export const setLocale = setValidationLocale + +export const setValidationLanguage = (lang: string) => { LOCALE.lang = lang } +export const setLanguage = setValidationLanguage + export const getMessage = (path: string) => { - return ( - getIn(LOCALE.messages, `${getMatchLang(LOCALE.lang)}.${path}`) || - 'field is not valid,but not found error message.' - ) + const message = getIn(LOCALE.messages, `${getMatchLang(LOCALE.lang)}.${path}`) + if (!message && console && console.error) { + console.error( + `field is not valid,but not found ${path} error message. Please set the language pack first through setValidationLocale` + ) + } + return message || 'Field is invalid' } -setLocale(locales) +setValidationLocale(locales) diff --git a/packages/validator/src/rules.ts b/packages/validator/src/rules.ts new file mode 100644 index 00000000000..e4f04740831 --- /dev/null +++ b/packages/validator/src/rules.ts @@ -0,0 +1,87 @@ +import { getMessage } from './message' +import { + isEmpty, + stringLength, + isStr, + isFn, + toArr, + isBool +} from '@uform/shared' +import { ValidateDescription } from './types' +const isValidateEmpty = (value: any) => { + if (typeof value === 'object') { + for (let key in value) { + if (value.hasOwnProperty(key)) { + if (!isValidateEmpty(value[key])) return false + } + } + return true + } else { + return isEmpty(value) + } +} + +const getLength = (value: any) => + isStr(value) ? stringLength(value) : value ? value.length : 0 + +export default { + required(value: any, rule: ValidateDescription) { + return isValidateEmpty(value) ? getMessage('required') : '' + }, + max(value: any, rule: ValidateDescription) { + const length = getLength(value) + const max = Number(rule.max) + return length > max ? getMessage('max') : '' + }, + maximum(value: any, rule: ValidateDescription) { + return Number(value) > Number(rule.maximum) ? getMessage('maximum') : '' + }, + exclusiveMaximum(value: any, rule: ValidateDescription) { + return Number(value) >= Number(rule.maximum) + ? getMessage('exclusiveMaximum') + : '' + }, + minimum(value: any, rule: ValidateDescription) { + return Number(value) < Number(rule.minimum) ? getMessage('minimum') : '' + }, + exclusiveMinimum(value: any, rule: ValidateDescription) { + return Number(value) <= Number(rule.minimum) + ? getMessage('exclusiveMinimum') + : '' + }, + len(value: any, rule: ValidateDescription) { + const length = getLength(value) + const len = Number(rule.len) + return length !== len ? getMessage('len') : '' + }, + min(value: any, rule: ValidateDescription) { + const length = getLength(value) + const min = Number(rule.len) + return length < min ? getMessage('min') : '' + }, + pattern(value: any, rule: ValidateDescription) { + return !new RegExp(rule.pattern).test(value) + ? rule.message || getMessage('pattern') + : '' + }, + async validator(value: any, rule: ValidateDescription) { + if (isFn(rule.validator)) { + const response = await Promise.resolve(rule.validator(value, rule)) + if (isBool(response)) { + return response ? rule.message : '' + } else { + return response + } + } + throw new Error("The rule's validator property must be a function.") + }, + whitespace(value: any, rule: ValidateDescription) { + if (rule.whitespace) { + return /^\s+$/.test(value) || value === '' ? getMessage('whitespace') : '' + } + }, + enum(value: any, rule: ValidateDescription) { + const enums = toArr(rule.enum) + return enums.indexOf(value) === -1 ? getMessage('enum') : '' + } +} diff --git a/packages/validator/src/types.ts b/packages/validator/src/types.ts new file mode 100644 index 00000000000..788d03ac6d3 --- /dev/null +++ b/packages/validator/src/types.ts @@ -0,0 +1,94 @@ +export interface ValidatorOptions { + validateFirst?: boolean +} + +export type ValidateNode = ( + options: ValidateFieldOptions +) => Promise<{ + errors: string[] + warnings: string[] +}> + +export type ValidateNodeMap = { + [key in string]: ValidateNode +} + +export type ValidateFormatsMap = { + [key in string]: RegExp +} + +export interface ValidateDescription { + format?: string + validator?: CustomValidator + required?: boolean + pattern?: RegExp | string + max?: number + maximum?: number + exclusiveMaximum?: number + exclusiveMinimum?: number + minimum?: number + min?: number + len?: number + whitespace?: boolean + enum?: any[] + message?: string +} + +export type ValidateRules = ValidateDescription[] + +export type ValidateArrayRules = Array< + string | CustomValidator | ValidateDescription +> + +export type ValidatePatternRules = + | string + | CustomValidator + | ValidateDescription + | ValidateArrayRules + +export type CustomValidator = ( + value: any, + rescription: ValidateDescription +) => ValidateResponse + +export type ValidateResponse = + | null + | string + | boolean + | { + type?: 'error' | 'warning' + message: string + } + +export type ValidateRulesMap = { + [key in string]: ( + value: any, + description: ValidateDescription + ) => ValidateResponse | Promise +} + +export interface ValidateFieldOptions { + first?: boolean + key?: string +} + +export type ValidateCalculator = ( + validate: ( + value: any, + rules: ValidatePatternRules + ) => Promise<{ + errors: string[] + warnings: string[] + }> +) => void + +export interface ValidateNodeResult { + errors: Array<{ + path: string + messages: string[] + }> + warnings: Array<{ + path: string + messages: string[] + }> +} diff --git a/packages/validator/src/utils.ts b/packages/validator/src/utils.ts deleted file mode 100644 index 67e16b7ae3e..00000000000 --- a/packages/validator/src/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -export * from '@uform/utils' - -const formatRegExp = /%[sdj%]/g - -export function format(...args: any[]) { - let i = 1 - const f = args[0] - const len = args.length - if (typeof f === 'function') { - return f.apply(null, args.slice(1)) - } - if (typeof f === 'string') { - const str = String(f).replace(formatRegExp, (x: string) => { - if (x === '%%') { - return '%' - } - if (i >= len) { - return x - } - switch (x) { - case '%s': - return String(args[i++]) - case '%d': - return Number(args[i++]) + '' - case '%j': - try { - return JSON.stringify(args[i++]) - } catch (_) { - return '[Circular]' - } - default: - return x - } - }) - return str - } - return f -} diff --git a/packages/validator/src/validator.ts b/packages/validator/src/validator.ts new file mode 100644 index 00000000000..e32c6267f6a --- /dev/null +++ b/packages/validator/src/validator.ts @@ -0,0 +1,277 @@ +import { + ValidatorOptions, + ValidateNodeMap, + ValidatePatternRules, + ValidateRules, + ValidateFormatsMap, + ValidateRulesMap, + ValidateResponse, + ValidateDescription, + ValidateFieldOptions, + ValidateCalculator, + ValidateNode, + ValidateNodeResult +} from './types' +import { + isFn, + isStr, + isArr, + isObj, + each, + FormPath, + FormPathPattern +} from '@uform/shared' +import { getMessage } from './message' +import defaultFormats from './formats' +import defaultRules from './rules' + +//校验规则集合 +const ValidatorRules: ValidateRulesMap = {} + +//校验格式集合 +const ValidatorFormators: ValidateFormatsMap = {} + +//模板引擎 +const template = (message: ValidateResponse, context: any): string => { + if (isStr(message)) { + if (isFn(FormValidator.template)) { + return FormValidator.template(message, context) + } + return message.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, $0) => { + return FormPath.getIn(context, $0) + }) + } else if (isObj(message)) { + return template(message.message, context) + } else { + return '' + } +} + +class FormValidator { + private validateFirst: boolean + private nodes: ValidateNodeMap + + constructor(options: ValidatorOptions = {}) { + this.validateFirst = options.validateFirst + this.nodes = {} + } + + transformRules(rules: ValidatePatternRules) { + if (isStr(rules)) { + if (!ValidatorFormators[rules]) { + throw new Error('Can not found validator pattern') + } + return [ + { + pattern: ValidatorFormators[rules], + message: getMessage(rules) || 'Can not found validator message.' + } + ] + } else if (isFn(rules)) { + return [ + { + validator: rules + } + ] + } else if (isArr(rules)) { + return rules.reduce((buf, rule) => { + return buf.concat(this.transformRules(rule)) + }, []) + } else if (isObj(rules)) { + if (rules.format) { + if (!ValidatorFormators[rules.format]) { + throw new Error('Can not found validator pattern') + } + rules.pattern = ValidatorFormators[rules.format] + rules.message = rules.message || getMessage(rules.format) + } + return [rules] + } + return [] + } + + async internalValidate( + value: any, + rules: ValidateRules, + options: ValidateFieldOptions = {} + ): Promise<{ + errors: string[] + warnings: string[] + }> { + const first = + options.first !== undefined ? !!options.first : !!this.validateFirst + const errors: string[] = [] + const warnings = [] + try { + for (let i = 0; i < rules.length; i++) { + const ruleObj = rules[i] + const keys = Object.keys(ruleObj).sort(key => + key === 'validator' ? 1 : -1 + ) + for (let l = 0; l < keys.length; l++) { + let key = keys[l] + if (ruleObj.hasOwnProperty(key) && ruleObj[key] !== undefined) { + const rule = ValidatorRules[key] + if (rule) { + const payload = await rule(value, ruleObj) + const message = template(payload, { + ...ruleObj, + value, + key: options.key + }) + if (isStr(payload)) { + if (first) { + if (message) { + errors.push(message) + throw new Error(message) + } + } + if (message) errors.push(message) + } else if (isObj(payload)) { + if (payload.type === 'warning') { + if (message) warnings.push(message) + } else { + if (first) { + if (message) { + errors.push(message) + throw new Error(message) + } + } + if (message) errors.push(message) + } + } + } + } + } + } + return { + errors, + warnings + } + } catch (e) { + return { + errors, + warnings + } + } + } + + async validateNodes( + pattern: FormPath, + options: ValidateFieldOptions + ): Promise { + const errors = [] + const warnings = [] + let promise = Promise.resolve({ errors, warnings }) + each(this.nodes, (validator, path) => { + if (pattern.match(path)) { + promise = promise.then(async ({ errors, warnings }) => { + const result = await validator(options) + return { + errors: result.errors.length + ? errors.concat({ + path: path.toString(), + messages: result.errors + }) + : errors, + warnings: result.warnings.length + ? warnings.concat({ + path: path.toString(), + messages: result.warnings + }) + : warnings + } + }) + } + }) + return promise.catch(error => { + console.error(error) + return { + errors: [], + warnings: [] + } + }) + } + + validate = ( + path?: FormPathPattern, + options?: ValidateFieldOptions + ): Promise => { + const pattern = FormPath.getPath(path || '*') + return this.validateNodes(pattern, options) + } + + register = (path: FormPathPattern, calculator: ValidateCalculator) => { + const newPath = FormPath.getPath(path) + this.nodes[newPath.toString()] = (options: ValidateFieldOptions) => { + return new Promise((resolve, reject) => { + const validate = async (value: any, rules: ValidatePatternRules) => { + const data = { + ...options, + key: newPath.toString() + } + return this.internalValidate( + value, + this.transformRules(rules), + data + ).then( + payload => { + resolve(payload) + return payload + }, + payload => { + reject(payload) + return Promise.reject(payload) + } + ) + } + calculator(validate) + }) + } + } + + unregister = (path: FormPathPattern) => { + const newPath = FormPath.getPath(path) + delete this.nodes[newPath.toString()] + } + + static template: ( + message: ValidateResponse, + data: ValidateDescription & { value: any; key: string } + ) => string + + //注册通用规则 + static registerRules(rules: ValidateRulesMap) { + each(rules, (rule, key) => { + if (isFn(rule)) { + ValidatorRules[key] = rule + } + }) + } + /** + * https://github.com/alibaba/uform/issues/215 + * + * @static + * @param {ValidateFormatsMap} formats + * @memberof FormValidator + */ + static registerFormats(formats: ValidateFormatsMap) { + each(formats, (pattern, key) => { + if (isStr(pattern) || pattern instanceof RegExp) { + ValidatorFormators[key] = new RegExp(pattern) + } + }) + } + + //注册校验消息模板引擎 + static registerMTEngine = template => { + if (isFn(template)) { + FormValidator.template = template + } + } +} + +FormValidator.registerFormats(defaultFormats) +FormValidator.registerRules(defaultRules) + +export { FormValidator } diff --git a/packages/validator/src/validators/custom.ts b/packages/validator/src/validators/custom.ts deleted file mode 100644 index a458b4991b8..00000000000 --- a/packages/validator/src/validators/custom.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { isFn } from '../utils' -import { IRuleDescription } from '@uform/types' -export default ( - value: any, - rule: IRuleDescription, - values: any, - name: string -) => { - if (isFn(rule.validator)) { - return rule.validator(value, rule, values, name) - } -} diff --git a/packages/validator/src/validators/format.ts b/packages/validator/src/validators/format.ts deleted file mode 100644 index 79cb200a1ef..00000000000 --- a/packages/validator/src/validators/format.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { format } from '../utils' -import { getMessage } from '../message' -import { patternValidate } from './pattern' -import { IRuleDescription } from '@uform/types' -import RegExpPatterns from './regexp' - -const PatternKeys = Object.keys(RegExpPatterns) - -const batchValidate = ( - value: any, - rule: IRuleDescription, - values: any, - name: string -) => { - for (let i = 0; i < PatternKeys.length; i++) { - if (PatternKeys[i] === rule.format) { - return patternValidate( - RegExpPatterns[PatternKeys[i]], - value, - format(rule.message || getMessage(rule.format), name, value) - ) - } - } -} - -export default ( - value: any, - rule: IRuleDescription, - values: any, - name: string -) => { - return batchValidate(value, rule, values, name) -} diff --git a/packages/validator/src/validators/index.ts b/packages/validator/src/validators/index.ts deleted file mode 100644 index aa6fe36880d..00000000000 --- a/packages/validator/src/validators/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { IRuleDescription, Rule } from '@uform/types' -import { isObj, isStr, isFn } from '../utils' -import formatValidate from './format' -import requiredValidate from './required' -import patternValidate from './pattern' -import customValidate from './custom' -/* - * rule : { - format:"", - * required:true, - * message:"", - * pattern:"", - * validator(value,rule,callback,values){ - * } - * } - * -**/ - -const batchInvoke = (...fns: Array<(...args: any[]) => void>) => { - return (...args: any[]) => { - return fns.map(fn => Promise.resolve(fn(...args))) - } -} - -const batchValidate = ( - value: any, - rule: IRuleDescription, - values: any, - name: string -) => { - return Promise.all( - batchInvoke( - formatValidate, - requiredValidate, - patternValidate, - customValidate - )(value, rule, values, name) - ) -} - -export const validate = (value: any, rule: Rule, values: any, name: string) => { - const newRule = isObj(rule) - ? rule - : isStr(rule) - ? { format: rule } - : isFn(rule) - ? { validator: rule } - : {} - return batchValidate(value, newRule as IRuleDescription, values, name) -} diff --git a/packages/validator/src/validators/pattern.ts b/packages/validator/src/validators/pattern.ts deleted file mode 100644 index a1501a78770..00000000000 --- a/packages/validator/src/validators/pattern.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { isRegExp, format, isFn, isEmpty } from '../utils' -import { getMessage } from '../message' -import { IRuleDescription } from '@uform/types' - -export const patternValidate = ( - pattern: string | RegExp | ((...args: any[]) => boolean), - value: any, - message: string -) => { - if (isEmpty(value)) { - return '' - } - if (isRegExp(pattern)) { - pattern.lastIndex = 0 - } - const valid = isFn(pattern) - ? pattern(value) - : isRegExp(pattern) - ? pattern.test(String(value)) - : new RegExp(String(pattern)).test(String(value)) - return !valid ? message : '' -} - -export default ( - value: any, - rule: IRuleDescription, - values: any, - name: string -) => { - if (rule.pattern) { - return patternValidate( - rule.pattern, - value, - format(rule.message || getMessage('pattern'), name, value, rule.pattern) - ) - } -} diff --git a/packages/validator/src/validators/required.ts b/packages/validator/src/validators/required.ts deleted file mode 100644 index 63f0e243e1a..00000000000 --- a/packages/validator/src/validators/required.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { format, isEmpty } from '../utils' -import { getMessage } from '../message' -import { IRuleDescription } from '@uform/types' - -const isValidateEmpty = (value: any) => { - if (typeof value === 'object') { - for (let key in value) { - if (value.hasOwnProperty(key)) { - if (!isValidateEmpty(value[key])) return false - } - } - return true - } else { - return isEmpty(value) - } -} - -export default ( - value: any, - rule: IRuleDescription, - values: any, - name: string -) => { - if (rule.required) { - return isValidateEmpty(value) - ? format(rule.message || getMessage('required'), name) - : '' - } -} diff --git a/scripts/jest.base.js b/scripts/jest.base.js index 2f8e215763a..79e5ea9ce32 100644 --- a/scripts/jest.base.js +++ b/scripts/jest.base.js @@ -22,6 +22,7 @@ module.exports = { '^.+\\.jsx?$': 'babel-jest' }, preset: 'ts-jest', + testMatch: ['**/__tests__/**/*.[jt]s?(x)'], setupFilesAfterEnv: [ require.resolve('jest-dom/extend-expect'), require.resolve('@testing-library/react/cleanup-after-each'),