This is page 14 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .all-contributorsrc
├── .codecov.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE
│ │ └── config.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows
│ ├── check-pr-title.yml
│ ├── ci.yml
│ ├── commitlint.yml
│ ├── issue-open-check.yml
│ ├── package-size.yml
│ └── pr-welcome.yml
├── .gitignore
├── .prettierrc.js
├── .umirc.js
├── .vscode
│ └── cspell.json
├── .yarnrc
├── CHANGELOG.md
├── commitlint.config.js
├── devtools
│ ├── .eslintrc
│ └── chrome-extension
│ ├── .npmignore
│ ├── assets
│ │ └── img
│ │ ├── loading.svg
│ │ └── logo
│ │ ├── 128x128.png
│ │ ├── 16x16.png
│ │ ├── 38x38.png
│ │ ├── 48x48.png
│ │ ├── error.png
│ │ ├── gray.png
│ │ └── scalable.png
│ ├── config
│ │ ├── webpack.base.ts
│ │ ├── webpack.dev.ts
│ │ └── webpack.prod.ts
│ ├── LICENSE.md
│ ├── package.json
│ ├── src
│ │ ├── app
│ │ │ ├── components
│ │ │ │ ├── FieldTree.tsx
│ │ │ │ ├── filter.ts
│ │ │ │ ├── LeftPanel.tsx
│ │ │ │ ├── RightPanel.tsx
│ │ │ │ ├── SearchBox.tsx
│ │ │ │ └── Tabs.tsx
│ │ │ ├── demo.tsx
│ │ │ └── index.tsx
│ │ └── extension
│ │ ├── backend.ts
│ │ ├── background.ts
│ │ ├── content.ts
│ │ ├── devpanel.tsx
│ │ ├── devtools.tsx
│ │ ├── inject.ts
│ │ ├── manifest.json
│ │ ├── popup.tsx
│ │ └── views
│ │ ├── devpanel.ejs
│ │ ├── devtools.ejs
│ │ └── popup.ejs
│ ├── tsconfig.build.json
│ └── tsconfig.json
├── docs
│ ├── functions
│ │ ├── contributors.ts
│ │ └── npm-search.ts
│ ├── guide
│ │ ├── advanced
│ │ │ ├── async.md
│ │ │ ├── async.zh-CN.md
│ │ │ ├── build.md
│ │ │ ├── build.zh-CN.md
│ │ │ ├── business-logic.md
│ │ │ ├── business-logic.zh-CN.md
│ │ │ ├── calculator.md
│ │ │ ├── calculator.zh-CN.md
│ │ │ ├── controlled.md
│ │ │ ├── controlled.zh-CN.md
│ │ │ ├── custom.md
│ │ │ ├── custom.zh-CN.md
│ │ │ ├── destructor.md
│ │ │ ├── destructor.zh-CN.md
│ │ │ ├── input.less
│ │ │ ├── layout.md
│ │ │ ├── layout.zh-CN.md
│ │ │ ├── linkages.md
│ │ │ ├── linkages.zh-CN.md
│ │ │ ├── validate.md
│ │ │ └── validate.zh-CN.md
│ │ ├── contribution.md
│ │ ├── contribution.zh-CN.md
│ │ ├── form-builder.md
│ │ ├── form-builder.zh-CN.md
│ │ ├── index.md
│ │ ├── index.zh-CN.md
│ │ ├── issue-helper.md
│ │ ├── issue-helper.zh-CN.md
│ │ ├── learn-formily.md
│ │ ├── learn-formily.zh-CN.md
│ │ ├── quick-start.md
│ │ ├── quick-start.zh-CN.md
│ │ ├── scenes
│ │ │ ├── dialog-drawer.md
│ │ │ ├── dialog-drawer.zh-CN.md
│ │ │ ├── edit-detail.md
│ │ │ ├── edit-detail.zh-CN.md
│ │ │ ├── index.less
│ │ │ ├── login-register.md
│ │ │ ├── login-register.zh-CN.md
│ │ │ ├── more.md
│ │ │ ├── more.zh-CN.md
│ │ │ ├── query-list.md
│ │ │ ├── query-list.zh-CN.md
│ │ │ ├── step-form.md
│ │ │ ├── step-form.zh-CN.md
│ │ │ ├── tab-form.md
│ │ │ ├── tab-form.zh-CN.md
│ │ │ └── VerifyCode.tsx
│ │ ├── upgrade.md
│ │ └── upgrade.zh-CN.md
│ ├── index.md
│ ├── index.zh-CN.md
│ └── site
│ ├── Contributors.less
│ ├── Contributors.tsx
│ ├── QrCode.less
│ ├── QrCode.tsx
│ ├── Section.less
│ ├── Section.tsx
│ └── styles.less
├── global.config.ts
├── jest.config.js
├── lerna.json
├── LICENSE.md
├── package.json
├── packages
│ ├── .eslintrc
│ ├── antd
│ │ ├── __tests__
│ │ │ ├── moment.spec.ts
│ │ │ └── sideEffects.spec.ts
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── build-style.ts
│ │ ├── create-style.ts
│ │ ├── docs
│ │ │ ├── components
│ │ │ │ ├── ArrayCards.md
│ │ │ │ ├── ArrayCards.zh-CN.md
│ │ │ │ ├── ArrayCollapse.md
│ │ │ │ ├── ArrayCollapse.zh-CN.md
│ │ │ │ ├── ArrayItems.md
│ │ │ │ ├── ArrayItems.zh-CN.md
│ │ │ │ ├── ArrayTable.md
│ │ │ │ ├── ArrayTable.zh-CN.md
│ │ │ │ ├── ArrayTabs.md
│ │ │ │ ├── ArrayTabs.zh-CN.md
│ │ │ │ ├── Cascader.md
│ │ │ │ ├── Cascader.zh-CN.md
│ │ │ │ ├── Checkbox.md
│ │ │ │ ├── Checkbox.zh-CN.md
│ │ │ │ ├── DatePicker.md
│ │ │ │ ├── DatePicker.zh-CN.md
│ │ │ │ ├── Editable.md
│ │ │ │ ├── Editable.zh-CN.md
│ │ │ │ ├── Form.md
│ │ │ │ ├── Form.zh-CN.md
│ │ │ │ ├── FormButtonGroup.md
│ │ │ │ ├── FormButtonGroup.zh-CN.md
│ │ │ │ ├── FormCollapse.md
│ │ │ │ ├── FormCollapse.zh-CN.md
│ │ │ │ ├── FormDialog.md
│ │ │ │ ├── FormDialog.zh-CN.md
│ │ │ │ ├── FormDrawer.md
│ │ │ │ ├── FormDrawer.zh-CN.md
│ │ │ │ ├── FormGrid.md
│ │ │ │ ├── FormGrid.zh-CN.md
│ │ │ │ ├── FormItem.md
│ │ │ │ ├── FormItem.zh-CN.md
│ │ │ │ ├── FormLayout.md
│ │ │ │ ├── FormLayout.zh-CN.md
│ │ │ │ ├── FormStep.md
│ │ │ │ ├── FormStep.zh-CN.md
│ │ │ │ ├── FormTab.md
│ │ │ │ ├── FormTab.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ ├── Input.md
│ │ │ │ ├── Input.zh-CN.md
│ │ │ │ ├── NumberPicker.md
│ │ │ │ ├── NumberPicker.zh-CN.md
│ │ │ │ ├── Password.md
│ │ │ │ ├── Password.zh-CN.md
│ │ │ │ ├── PreviewText.md
│ │ │ │ ├── PreviewText.zh-CN.md
│ │ │ │ ├── Radio.md
│ │ │ │ ├── Radio.zh-CN.md
│ │ │ │ ├── Reset.md
│ │ │ │ ├── Reset.zh-CN.md
│ │ │ │ ├── Select.md
│ │ │ │ ├── Select.zh-CN.md
│ │ │ │ ├── SelectTable.md
│ │ │ │ ├── SelectTable.zh-CN.md
│ │ │ │ ├── Space.md
│ │ │ │ ├── Space.zh-CN.md
│ │ │ │ ├── Submit.md
│ │ │ │ ├── Submit.zh-CN.md
│ │ │ │ ├── Switch.md
│ │ │ │ ├── Switch.zh-CN.md
│ │ │ │ ├── TimePicker.md
│ │ │ │ ├── TimePicker.zh-CN.md
│ │ │ │ ├── Transfer.md
│ │ │ │ ├── Transfer.zh-CN.md
│ │ │ │ ├── TreeSelect.md
│ │ │ │ ├── TreeSelect.zh-CN.md
│ │ │ │ ├── Upload.md
│ │ │ │ └── Upload.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __builtins__
│ │ │ │ ├── hooks
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── useClickAway.ts
│ │ │ │ │ └── usePrefixCls.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── loading.ts
│ │ │ │ ├── moment.ts
│ │ │ │ ├── pickDataProps.ts
│ │ │ │ ├── portal.tsx
│ │ │ │ ├── render.ts
│ │ │ │ └── sort.tsx
│ │ │ ├── array-base
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-cards
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-collapse
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-items
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-table
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-tabs
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── cascader
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── checkbox
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── date-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── editable
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form-button-group
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form-collapse
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-dialog
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-drawer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-grid
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form-item
│ │ │ │ ├── animation.less
│ │ │ │ ├── grid.less
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form-layout
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ ├── style.ts
│ │ │ │ └── useResponsiveFormLayout.ts
│ │ │ ├── form-step
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-tab
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── index.ts
│ │ │ ├── input
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── number-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── password
│ │ │ │ ├── index.tsx
│ │ │ │ ├── PasswordStrength.tsx
│ │ │ │ └── style.ts
│ │ │ ├── preview-text
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── radio
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── reset
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── select-table
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ ├── style.ts
│ │ │ │ ├── useCheckSlackly.tsx
│ │ │ │ ├── useFilterOptions.tsx
│ │ │ │ ├── useFlatOptions.tsx
│ │ │ │ ├── useSize.tsx
│ │ │ │ ├── useTitleAddon.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── space
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── style.less
│ │ │ ├── style.ts
│ │ │ ├── submit
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── switch
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── time-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── transfer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── tree-select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── upload
│ │ │ ├── index.tsx
│ │ │ ├── placeholder.ts
│ │ │ └── style.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── benchmark
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.tsx
│ │ ├── template.ejs
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ ├── webpack.base.ts
│ │ ├── webpack.dev.ts
│ │ └── webpack.prod.ts
│ ├── core
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── docs
│ │ │ ├── api
│ │ │ │ ├── entry
│ │ │ │ │ ├── ActionResponse.less
│ │ │ │ │ ├── ActionResponse.tsx
│ │ │ │ │ ├── createForm.md
│ │ │ │ │ ├── createForm.zh-CN.md
│ │ │ │ │ ├── FieldEffectHooks.md
│ │ │ │ │ ├── FieldEffectHooks.zh-CN.md
│ │ │ │ │ ├── FormChecker.md
│ │ │ │ │ ├── FormChecker.zh-CN.md
│ │ │ │ │ ├── FormEffectHooks.md
│ │ │ │ │ ├── FormEffectHooks.zh-CN.md
│ │ │ │ │ ├── FormHooksAPI.md
│ │ │ │ │ ├── FormHooksAPI.zh-CN.md
│ │ │ │ │ ├── FormPath.md
│ │ │ │ │ ├── FormPath.zh-CN.md
│ │ │ │ │ ├── FormValidatorRegistry.md
│ │ │ │ │ └── FormValidatorRegistry.zh-CN.md
│ │ │ │ └── models
│ │ │ │ ├── ArrayField.md
│ │ │ │ ├── ArrayField.zh-CN.md
│ │ │ │ ├── Field.md
│ │ │ │ ├── Field.zh-CN.md
│ │ │ │ ├── Form.md
│ │ │ │ ├── Form.zh-CN.md
│ │ │ │ ├── ObjectField.md
│ │ │ │ ├── ObjectField.zh-CN.md
│ │ │ │ ├── Query.md
│ │ │ │ ├── Query.zh-CN.md
│ │ │ │ ├── VoidField.md
│ │ │ │ └── VoidField.zh-CN.md
│ │ │ ├── guide
│ │ │ │ ├── architecture.md
│ │ │ │ ├── architecture.zh-CN.md
│ │ │ │ ├── field.md
│ │ │ │ ├── field.zh-CN.md
│ │ │ │ ├── form.md
│ │ │ │ ├── form.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ ├── mvvm.md
│ │ │ │ ├── mvvm.zh-CN.md
│ │ │ │ ├── values.md
│ │ │ │ └── values.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── array.spec.ts
│ │ │ │ ├── effects.spec.ts
│ │ │ │ ├── externals.spec.ts
│ │ │ │ ├── field.spec.ts
│ │ │ │ ├── form.spec.ts
│ │ │ │ ├── graph.spec.ts
│ │ │ │ ├── heart.spec.ts
│ │ │ │ ├── internals.spec.ts
│ │ │ │ ├── lifecycle.spec.ts
│ │ │ │ ├── object.spec.ts
│ │ │ │ ├── shared.ts
│ │ │ │ └── void.spec.ts
│ │ │ ├── effects
│ │ │ │ ├── index.ts
│ │ │ │ ├── onFieldEffects.ts
│ │ │ │ └── onFormEffects.ts
│ │ │ ├── global.d.ts
│ │ │ ├── index.ts
│ │ │ ├── models
│ │ │ │ ├── ArrayField.ts
│ │ │ │ ├── BaseField.ts
│ │ │ │ ├── Field.ts
│ │ │ │ ├── Form.ts
│ │ │ │ ├── Graph.ts
│ │ │ │ ├── Heart.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── LifeCycle.ts
│ │ │ │ ├── ObjectField.ts
│ │ │ │ ├── Query.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── VoidField.ts
│ │ │ ├── shared
│ │ │ │ ├── checkers.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── effective.ts
│ │ │ │ ├── externals.ts
│ │ │ │ └── internals.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── element
│ │ ├── .npmignore
│ │ ├── build-style.ts
│ │ ├── create-style.ts
│ │ ├── docs
│ │ │ ├── .vuepress
│ │ │ │ ├── components
│ │ │ │ │ ├── createCodeSandBox.js
│ │ │ │ │ ├── dumi-previewer.vue
│ │ │ │ │ └── highlight.js
│ │ │ │ ├── config.js
│ │ │ │ ├── enhanceApp.js
│ │ │ │ ├── styles
│ │ │ │ │ └── index.styl
│ │ │ │ └── util.js
│ │ │ ├── demos
│ │ │ │ ├── guide
│ │ │ │ │ ├── array-cards
│ │ │ │ │ │ ├── effects-json-schema.vue
│ │ │ │ │ │ ├── effects-markup-schema.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── array-collapse
│ │ │ │ │ │ ├── effects-json-schema.vue
│ │ │ │ │ │ ├── effects-markup-schema.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── array-items
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── array-table
│ │ │ │ │ │ ├── effects-json-schema.vue
│ │ │ │ │ │ ├── effects-markup-schema.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── array-tabs
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── cascader
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── checkbox
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── date-picker
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── editable
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-button-group.vue
│ │ │ │ │ ├── form-collapse
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── form-dialog
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-drawer
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-grid
│ │ │ │ │ │ ├── form.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── native.vue
│ │ │ │ │ ├── form-item
│ │ │ │ │ │ ├── bordered-none.vue
│ │ │ │ │ │ ├── common.vue
│ │ │ │ │ │ ├── feedback.vue
│ │ │ │ │ │ ├── inset.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ ├── size.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-layout
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-step
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── form-tab
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── form.vue
│ │ │ │ │ ├── input
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── input-number
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── password
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── preview-text
│ │ │ │ │ │ ├── base.vue
│ │ │ │ │ │ └── extend.vue
│ │ │ │ │ ├── radio
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── reset
│ │ │ │ │ │ ├── base.vue
│ │ │ │ │ │ ├── force.vue
│ │ │ │ │ │ └── validate.vue
│ │ │ │ │ ├── select
│ │ │ │ │ │ ├── json-schema-async.vue
│ │ │ │ │ │ ├── json-schema-sync.vue
│ │ │ │ │ │ ├── markup-schema-async-search.vue
│ │ │ │ │ │ ├── markup-schema-async.vue
│ │ │ │ │ │ ├── markup-schema-sync.vue
│ │ │ │ │ │ ├── template-async.vue
│ │ │ │ │ │ └── template-sync.vue
│ │ │ │ │ ├── space
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── submit
│ │ │ │ │ │ ├── base.vue
│ │ │ │ │ │ └── loading.vue
│ │ │ │ │ ├── switch
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── time-picker
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── transfer
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ └── upload
│ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ └── template.vue
│ │ │ │ └── index.vue
│ │ │ ├── guide
│ │ │ │ ├── array-cards.md
│ │ │ │ ├── array-collapse.md
│ │ │ │ ├── array-items.md
│ │ │ │ ├── array-table.md
│ │ │ │ ├── array-tabs.md
│ │ │ │ ├── cascader.md
│ │ │ │ ├── checkbox.md
│ │ │ │ ├── date-picker.md
│ │ │ │ ├── editable.md
│ │ │ │ ├── form-button-group.md
│ │ │ │ ├── form-collapse.md
│ │ │ │ ├── form-dialog.md
│ │ │ │ ├── form-drawer.md
│ │ │ │ ├── form-grid.md
│ │ │ │ ├── form-item.md
│ │ │ │ ├── form-layout.md
│ │ │ │ ├── form-step.md
│ │ │ │ ├── form-tab.md
│ │ │ │ ├── form.md
│ │ │ │ ├── index.md
│ │ │ │ ├── input-number.md
│ │ │ │ ├── input.md
│ │ │ │ ├── password.md
│ │ │ │ ├── preview-text.md
│ │ │ │ ├── radio.md
│ │ │ │ ├── reset.md
│ │ │ │ ├── select.md
│ │ │ │ ├── space.md
│ │ │ │ ├── submit.md
│ │ │ │ ├── switch.md
│ │ │ │ ├── time-picker.md
│ │ │ │ ├── transfer.md
│ │ │ │ └── upload.md
│ │ │ └── README.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __builtins__
│ │ │ │ ├── configs
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── shared
│ │ │ │ │ ├── create-context.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── loading.ts
│ │ │ │ │ ├── portal.ts
│ │ │ │ │ ├── resolve-component.ts
│ │ │ │ │ ├── transform-component.ts
│ │ │ │ │ ├── types.ts
│ │ │ │ │ └── utils.ts
│ │ │ │ └── styles
│ │ │ │ └── common.scss
│ │ │ ├── array-base
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-cards
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-collapse
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-items
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-table
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-tabs
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── cascader
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── checkbox
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── date-picker
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── editable
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── el-form
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── el-form-item
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── form
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-button-group
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-collapse
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-dialog
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── form-drawer
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-grid
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-item
│ │ │ │ ├── animation.scss
│ │ │ │ ├── grid.scss
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ ├── style.ts
│ │ │ │ └── var.scss
│ │ │ ├── form-layout
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ ├── style.ts
│ │ │ │ └── useResponsiveFormLayout.ts
│ │ │ ├── form-step
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── form-tab
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── index.ts
│ │ │ ├── input
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── input-number
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── password
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── preview-text
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── radio
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── reset
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── select
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── space
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── style.ts
│ │ │ ├── submit
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── switch
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── time-picker
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── transfer
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ └── upload
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── transformer.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── grid
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── index.ts
│ │ │ └── observer.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── json-schema
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── schema.spec.ts.snap
│ │ │ │ ├── compiler.spec.ts
│ │ │ │ ├── patches.spec.ts
│ │ │ │ ├── schema.spec.ts
│ │ │ │ ├── server-validate.spec.ts
│ │ │ │ ├── shared.spec.ts
│ │ │ │ ├── transformer.spec.ts
│ │ │ │ └── traverse.spec.ts
│ │ │ ├── compiler.ts
│ │ │ ├── global.d.ts
│ │ │ ├── index.ts
│ │ │ ├── patches.ts
│ │ │ ├── polyfills
│ │ │ │ ├── index.ts
│ │ │ │ └── SPECIFICATION_1_0.ts
│ │ │ ├── schema.ts
│ │ │ ├── shared.ts
│ │ │ ├── transformer.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── next
│ │ ├── __tests__
│ │ │ ├── moment.spec.ts
│ │ │ └── sideEffects.spec.ts
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── build-style.ts
│ │ ├── create-style.ts
│ │ ├── docs
│ │ │ ├── components
│ │ │ │ ├── ArrayCards.md
│ │ │ │ ├── ArrayCards.zh-CN.md
│ │ │ │ ├── ArrayCollapse.md
│ │ │ │ ├── ArrayCollapse.zh-CN.md
│ │ │ │ ├── ArrayItems.md
│ │ │ │ ├── ArrayItems.zh-CN.md
│ │ │ │ ├── ArrayTable.md
│ │ │ │ ├── ArrayTable.zh-CN.md
│ │ │ │ ├── Cascader.md
│ │ │ │ ├── Cascader.zh-CN.md
│ │ │ │ ├── Checkbox.md
│ │ │ │ ├── Checkbox.zh-CN.md
│ │ │ │ ├── DatePicker.md
│ │ │ │ ├── DatePicker.zh-CN.md
│ │ │ │ ├── DatePicker2.md
│ │ │ │ ├── DatePicker2.zh-CN.md
│ │ │ │ ├── Editable.md
│ │ │ │ ├── Editable.zh-CN.md
│ │ │ │ ├── Form.md
│ │ │ │ ├── Form.zh-CN.md
│ │ │ │ ├── FormButtonGroup.md
│ │ │ │ ├── FormButtonGroup.zh-CN.md
│ │ │ │ ├── FormCollapse.md
│ │ │ │ ├── FormCollapse.zh-CN.md
│ │ │ │ ├── FormDialog.md
│ │ │ │ ├── FormDialog.zh-CN.md
│ │ │ │ ├── FormDrawer.md
│ │ │ │ ├── FormDrawer.zh-CN.md
│ │ │ │ ├── FormGrid.md
│ │ │ │ ├── FormGrid.zh-CN.md
│ │ │ │ ├── FormItem.md
│ │ │ │ ├── FormItem.zh-CN.md
│ │ │ │ ├── FormLayout.md
│ │ │ │ ├── FormLayout.zh-CN.md
│ │ │ │ ├── FormStep.md
│ │ │ │ ├── FormStep.zh-CN.md
│ │ │ │ ├── FormTab.md
│ │ │ │ ├── FormTab.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ ├── Input.md
│ │ │ │ ├── Input.zh-CN.md
│ │ │ │ ├── NumberPicker.md
│ │ │ │ ├── NumberPicker.zh-CN.md
│ │ │ │ ├── Password.md
│ │ │ │ ├── Password.zh-CN.md
│ │ │ │ ├── PreviewText.md
│ │ │ │ ├── PreviewText.zh-CN.md
│ │ │ │ ├── Radio.md
│ │ │ │ ├── Radio.zh-CN.md
│ │ │ │ ├── Reset.md
│ │ │ │ ├── Reset.zh-CN.md
│ │ │ │ ├── Select.md
│ │ │ │ ├── Select.zh-CN.md
│ │ │ │ ├── SelectTable.md
│ │ │ │ ├── SelectTable.zh-CN.md
│ │ │ │ ├── Space.md
│ │ │ │ ├── Space.zh-CN.md
│ │ │ │ ├── Submit.md
│ │ │ │ ├── Submit.zh-CN.md
│ │ │ │ ├── Switch.md
│ │ │ │ ├── Switch.zh-CN.md
│ │ │ │ ├── TimePicker.md
│ │ │ │ ├── TimePicker.zh-CN.md
│ │ │ │ ├── TimePicker2.md
│ │ │ │ ├── TimePicker2.zh-CN.md
│ │ │ │ ├── Transfer.md
│ │ │ │ ├── Transfer.zh-CN.md
│ │ │ │ ├── TreeSelect.md
│ │ │ │ ├── TreeSelect.zh-CN.md
│ │ │ │ ├── Upload.md
│ │ │ │ └── Upload.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LESENCE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __builtins__
│ │ │ │ ├── empty.tsx
│ │ │ │ ├── hooks
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── useClickAway.ts
│ │ │ │ │ └── usePrefixCls.ts
│ │ │ │ ├── icons.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── loading.ts
│ │ │ │ ├── mapSize.ts
│ │ │ │ ├── mapStatus.ts
│ │ │ │ ├── moment.ts
│ │ │ │ ├── pickDataProps.ts
│ │ │ │ ├── portal.tsx
│ │ │ │ ├── render.ts
│ │ │ │ └── toArray.ts
│ │ │ ├── array-base
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-cards
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-collapse
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-items
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-table
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── cascader
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── checkbox
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── date-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── date-picker2
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── editable
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── form
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-button-group
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-collapse
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-dialog
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-drawer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-grid
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-item
│ │ │ │ ├── animation.scss
│ │ │ │ ├── grid.scss
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ ├── scss
│ │ │ │ │ └── variable.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-layout
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ ├── style.ts
│ │ │ │ └── useResponsiveFormLayout.ts
│ │ │ ├── form-step
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-tab
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── index.ts
│ │ │ ├── input
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── main.scss
│ │ │ ├── number-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── password
│ │ │ │ ├── index.tsx
│ │ │ │ ├── PasswordStrength.tsx
│ │ │ │ └── style.ts
│ │ │ ├── preview-text
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── radio
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── reset
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── select-table
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ ├── style.ts
│ │ │ │ ├── useCheckSlackly.tsx
│ │ │ │ ├── useFilterOptions.tsx
│ │ │ │ ├── useFlatOptions.tsx
│ │ │ │ ├── useSize.tsx
│ │ │ │ ├── useTitleAddon.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── space
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── style.ts
│ │ │ ├── submit
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── switch
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── time-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── time-picker2
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── transfer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── tree-select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── upload
│ │ │ ├── index.tsx
│ │ │ ├── main.scss
│ │ │ ├── placeholder.ts
│ │ │ └── style.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── path
│ │ ├── .npmignore
│ │ ├── benchmark.ts
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── accessor.spec.ts
│ │ │ │ ├── basic.spec.ts
│ │ │ │ ├── match.spec.ts
│ │ │ │ ├── parser.spec.ts
│ │ │ │ └── share.spec.ts
│ │ │ ├── contexts.ts
│ │ │ ├── destructor.ts
│ │ │ ├── index.ts
│ │ │ ├── matcher.ts
│ │ │ ├── parser.ts
│ │ │ ├── shared.ts
│ │ │ ├── tokenizer.ts
│ │ │ ├── tokens.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── react
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── docs
│ │ │ ├── api
│ │ │ │ ├── components
│ │ │ │ │ ├── ArrayField.md
│ │ │ │ │ ├── ArrayField.zh-CN.md
│ │ │ │ │ ├── ExpressionScope.md
│ │ │ │ │ ├── ExpressionScope.zh-CN.md
│ │ │ │ │ ├── Field.md
│ │ │ │ │ ├── Field.zh-CN.md
│ │ │ │ │ ├── FormConsumer.md
│ │ │ │ │ ├── FormConsumer.zh-CN.md
│ │ │ │ │ ├── FormProvider.md
│ │ │ │ │ ├── FormProvider.zh-CN.md
│ │ │ │ │ ├── ObjectField.md
│ │ │ │ │ ├── ObjectField.zh-CN.md
│ │ │ │ │ ├── RecordScope.md
│ │ │ │ │ ├── RecordScope.zh-CN.md
│ │ │ │ │ ├── RecordsScope.md
│ │ │ │ │ ├── RecordsScope.zh-CN.md
│ │ │ │ │ ├── RecursionField.md
│ │ │ │ │ ├── RecursionField.zh-CN.md
│ │ │ │ │ ├── SchemaField.md
│ │ │ │ │ ├── SchemaField.zh-CN.md
│ │ │ │ │ ├── VoidField.md
│ │ │ │ │ └── VoidField.zh-CN.md
│ │ │ │ ├── hooks
│ │ │ │ │ ├── useExpressionScope.md
│ │ │ │ │ ├── useExpressionScope.zh-CN.md
│ │ │ │ │ ├── useField.md
│ │ │ │ │ ├── useField.zh-CN.md
│ │ │ │ │ ├── useFieldSchema.md
│ │ │ │ │ ├── useFieldSchema.zh-CN.md
│ │ │ │ │ ├── useForm.md
│ │ │ │ │ ├── useForm.zh-CN.md
│ │ │ │ │ ├── useFormEffects.md
│ │ │ │ │ ├── useFormEffects.zh-CN.md
│ │ │ │ │ ├── useParentForm.md
│ │ │ │ │ └── useParentForm.zh-CN.md
│ │ │ │ └── shared
│ │ │ │ ├── connect.md
│ │ │ │ ├── connect.zh-CN.md
│ │ │ │ ├── context.md
│ │ │ │ ├── context.zh-CN.md
│ │ │ │ ├── mapProps.md
│ │ │ │ ├── mapProps.zh-CN.md
│ │ │ │ ├── mapReadPretty.md
│ │ │ │ ├── mapReadPretty.zh-CN.md
│ │ │ │ ├── observer.md
│ │ │ │ ├── observer.zh-CN.md
│ │ │ │ ├── Schema.md
│ │ │ │ └── Schema.zh-CN.md
│ │ │ ├── guide
│ │ │ │ ├── architecture.md
│ │ │ │ ├── architecture.zh-CN.md
│ │ │ │ ├── concept.md
│ │ │ │ ├── concept.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── expression.spec.tsx
│ │ │ │ ├── field.spec.tsx
│ │ │ │ ├── form.spec.tsx
│ │ │ │ ├── schema.json.spec.tsx
│ │ │ │ ├── schema.markup.spec.tsx
│ │ │ │ └── shared.tsx
│ │ │ ├── components
│ │ │ │ ├── ArrayField.tsx
│ │ │ │ ├── ExpressionScope.tsx
│ │ │ │ ├── Field.tsx
│ │ │ │ ├── FormConsumer.tsx
│ │ │ │ ├── FormProvider.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── ObjectField.tsx
│ │ │ │ ├── ReactiveField.tsx
│ │ │ │ ├── RecordScope.tsx
│ │ │ │ ├── RecordsScope.tsx
│ │ │ │ ├── RecursionField.tsx
│ │ │ │ ├── SchemaField.tsx
│ │ │ │ └── VoidField.tsx
│ │ │ ├── global.d.ts
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ ├── useAttach.ts
│ │ │ │ ├── useExpressionScope.ts
│ │ │ │ ├── useField.ts
│ │ │ │ ├── useFieldSchema.ts
│ │ │ │ ├── useForm.ts
│ │ │ │ ├── useFormEffects.ts
│ │ │ │ └── useParentForm.ts
│ │ │ ├── index.ts
│ │ │ ├── shared
│ │ │ │ ├── connect.ts
│ │ │ │ ├── context.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── render.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── reactive
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── benchmark.ts
│ │ ├── docs
│ │ │ ├── api
│ │ │ │ ├── action.md
│ │ │ │ ├── action.zh-CN.md
│ │ │ │ ├── autorun.md
│ │ │ │ ├── autorun.zh-CN.md
│ │ │ │ ├── batch.md
│ │ │ │ ├── batch.zh-CN.md
│ │ │ │ ├── define.md
│ │ │ │ ├── define.zh-CN.md
│ │ │ │ ├── hasCollected.md
│ │ │ │ ├── hasCollected.zh-CN.md
│ │ │ │ ├── markObservable.md
│ │ │ │ ├── markObservable.zh-CN.md
│ │ │ │ ├── markRaw.md
│ │ │ │ ├── markRaw.zh-CN.md
│ │ │ │ ├── model.md
│ │ │ │ ├── model.zh-CN.md
│ │ │ │ ├── observable.md
│ │ │ │ ├── observable.zh-CN.md
│ │ │ │ ├── observe.md
│ │ │ │ ├── observe.zh-CN.md
│ │ │ │ ├── raw.md
│ │ │ │ ├── raw.zh-CN.md
│ │ │ │ ├── react
│ │ │ │ │ ├── observer.md
│ │ │ │ │ └── observer.zh-CN.md
│ │ │ │ ├── reaction.md
│ │ │ │ ├── reaction.zh-CN.md
│ │ │ │ ├── toJS.md
│ │ │ │ ├── toJS.zh-CN.md
│ │ │ │ ├── tracker.md
│ │ │ │ ├── tracker.zh-CN.md
│ │ │ │ ├── typeChecker.md
│ │ │ │ ├── typeChecker.zh-CN.md
│ │ │ │ ├── untracked.md
│ │ │ │ ├── untracked.zh-CN.md
│ │ │ │ └── vue
│ │ │ │ ├── observer.md
│ │ │ │ └── observer.zh-CN.md
│ │ │ ├── guide
│ │ │ │ ├── best-practice.md
│ │ │ │ ├── best-practice.zh-CN.md
│ │ │ │ ├── concept.md
│ │ │ │ ├── concept.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── action.spec.ts
│ │ │ │ ├── annotations.spec.ts
│ │ │ │ ├── array.spec.ts
│ │ │ │ ├── autorun.spec.ts
│ │ │ │ ├── batch.spec.ts
│ │ │ │ ├── collections-map.spec.ts
│ │ │ │ ├── collections-set.spec.ts
│ │ │ │ ├── collections-weakmap.spec.ts
│ │ │ │ ├── collections-weakset.spec.ts
│ │ │ │ ├── define.spec.ts
│ │ │ │ ├── externals.spec.ts
│ │ │ │ ├── hasCollected.spec.ts
│ │ │ │ ├── observable.spec.ts
│ │ │ │ ├── observe.spec.ts
│ │ │ │ ├── tracker.spec.ts
│ │ │ │ └── untracked.spec.ts
│ │ │ ├── action.ts
│ │ │ ├── annotations
│ │ │ │ ├── box.ts
│ │ │ │ ├── computed.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── observable.ts
│ │ │ │ ├── ref.ts
│ │ │ │ └── shallow.ts
│ │ │ ├── array.ts
│ │ │ ├── autorun.ts
│ │ │ ├── batch.ts
│ │ │ ├── checkers.ts
│ │ │ ├── environment.ts
│ │ │ ├── externals.ts
│ │ │ ├── global.d.ts
│ │ │ ├── handlers.ts
│ │ │ ├── index.ts
│ │ │ ├── internals.ts
│ │ │ ├── model.ts
│ │ │ ├── observable.ts
│ │ │ ├── observe.ts
│ │ │ ├── reaction.ts
│ │ │ ├── tracker.ts
│ │ │ ├── tree.ts
│ │ │ ├── types.ts
│ │ │ └── untracked.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── reactive-react
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ ├── useCompatEffect.ts
│ │ │ │ ├── useCompatFactory.ts
│ │ │ │ ├── useDidUpdate.ts
│ │ │ │ ├── useForceUpdate.ts
│ │ │ │ ├── useLayoutEffect.ts
│ │ │ │ └── useObserver.ts
│ │ │ ├── index.ts
│ │ │ ├── observer.ts
│ │ │ ├── shared
│ │ │ │ ├── gc.ts
│ │ │ │ ├── global.ts
│ │ │ │ ├── immediate.ts
│ │ │ │ └── index.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── reactive-test-cases-for-react18
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── index.js
│ │ │ └── MySlowList.js
│ │ ├── template.ejs
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ ├── webpack.base.ts
│ │ ├── webpack.dev.ts
│ │ └── webpack.prod.ts
│ ├── reactive-vue
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ └── observer.spec.ts
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ └── useObserver.ts
│ │ │ ├── index.ts
│ │ │ ├── observer
│ │ │ │ ├── collectData.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── observerInVue2.ts
│ │ │ │ └── observerInVue3.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── shared
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ └── index.spec.ts
│ │ │ ├── array.ts
│ │ │ ├── case.ts
│ │ │ ├── checkers.ts
│ │ │ ├── clone.ts
│ │ │ ├── compare.ts
│ │ │ ├── defaults.ts
│ │ │ ├── deprecate.ts
│ │ │ ├── global.ts
│ │ │ ├── index.ts
│ │ │ ├── instanceof.ts
│ │ │ ├── isEmpty.ts
│ │ │ ├── merge.ts
│ │ │ ├── middleware.ts
│ │ │ ├── path.ts
│ │ │ ├── string.ts
│ │ │ ├── subscribable.ts
│ │ │ └── uid.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── validator
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── parser.spec.ts
│ │ │ │ ├── registry.spec.ts
│ │ │ │ └── validator.spec.ts
│ │ │ ├── formats.ts
│ │ │ ├── index.ts
│ │ │ ├── locale.ts
│ │ │ ├── parser.ts
│ │ │ ├── registry.ts
│ │ │ ├── rules.ts
│ │ │ ├── template.ts
│ │ │ ├── types.ts
│ │ │ └── validator.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ └── vue
│ ├── .npmignore
│ ├── bin
│ │ ├── formily-vue-fix.js
│ │ └── formily-vue-switch.js
│ ├── docs
│ │ ├── .vuepress
│ │ │ ├── components
│ │ │ │ ├── createCodeSandBox.js
│ │ │ │ ├── dumi-previewer.vue
│ │ │ │ └── highlight.js
│ │ │ ├── config.js
│ │ │ ├── enhanceApp.js
│ │ │ └── styles
│ │ │ └── index.styl
│ │ ├── api
│ │ │ ├── components
│ │ │ │ ├── array-field.md
│ │ │ │ ├── expression-scope.md
│ │ │ │ ├── field.md
│ │ │ │ ├── form-consumer.md
│ │ │ │ ├── form-provider.md
│ │ │ │ ├── object-field.md
│ │ │ │ ├── recursion-field-with-component.md
│ │ │ │ ├── recursion-field.md
│ │ │ │ ├── schema-field-with-schema.md
│ │ │ │ ├── schema-field.md
│ │ │ │ └── void-field.md
│ │ │ ├── hooks
│ │ │ │ ├── use-field-schema.md
│ │ │ │ ├── use-field.md
│ │ │ │ ├── use-form-effects.md
│ │ │ │ ├── use-form.md
│ │ │ │ └── use-parent-form.md
│ │ │ └── shared
│ │ │ ├── connect.md
│ │ │ ├── injections.md
│ │ │ ├── map-props.md
│ │ │ ├── map-read-pretty.md
│ │ │ ├── observer.md
│ │ │ └── schema.md
│ │ ├── demos
│ │ │ ├── api
│ │ │ │ ├── components
│ │ │ │ │ ├── array-field.vue
│ │ │ │ │ ├── expression-scope.vue
│ │ │ │ │ ├── field.vue
│ │ │ │ │ ├── form-consumer.vue
│ │ │ │ │ ├── form-provider.vue
│ │ │ │ │ ├── object-field.vue
│ │ │ │ │ ├── recursion-field-with-component.vue
│ │ │ │ │ ├── recursion-field.vue
│ │ │ │ │ ├── schema-field-with-schema.vue
│ │ │ │ │ ├── schema-field.vue
│ │ │ │ │ └── void-field.vue
│ │ │ │ ├── hooks
│ │ │ │ │ ├── use-field-schema.vue
│ │ │ │ │ ├── use-field.vue
│ │ │ │ │ ├── use-form-effects.vue
│ │ │ │ │ ├── use-form.vue
│ │ │ │ │ └── use-parent-form.vue
│ │ │ │ └── shared
│ │ │ │ ├── connect.vue
│ │ │ │ ├── map-props.vue
│ │ │ │ ├── map-read-pretty.vue
│ │ │ │ └── observer.vue
│ │ │ ├── index.vue
│ │ │ └── questions
│ │ │ ├── default-slot.vue
│ │ │ ├── events.vue
│ │ │ ├── named-slot.vue
│ │ │ └── scoped-slot.vue
│ │ ├── guide
│ │ │ ├── architecture.md
│ │ │ ├── concept.md
│ │ │ └── README.md
│ │ ├── questions
│ │ │ └── README.md
│ │ └── README.md
│ ├── package.json
│ ├── README.md
│ ├── rollup.config.js
│ ├── scripts
│ │ ├── postinstall.js
│ │ ├── switch-cli.js
│ │ └── utils.js
│ ├── src
│ │ ├── __tests__
│ │ │ ├── expression.scope.spec.ts
│ │ │ ├── field.spec.ts
│ │ │ ├── form.spec.ts
│ │ │ ├── schema.json.spec.ts
│ │ │ ├── schema.markup.spec.ts
│ │ │ ├── shared.spec.ts
│ │ │ └── utils.spec.ts
│ │ ├── components
│ │ │ ├── ArrayField.ts
│ │ │ ├── ExpressionScope.ts
│ │ │ ├── Field.ts
│ │ │ ├── FormConsumer.ts
│ │ │ ├── FormProvider.ts
│ │ │ ├── index.ts
│ │ │ ├── ObjectField.ts
│ │ │ ├── ReactiveField.ts
│ │ │ ├── RecursionField.ts
│ │ │ ├── SchemaField.ts
│ │ │ └── VoidField.ts
│ │ ├── global.d.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useAttach.ts
│ │ │ ├── useField.ts
│ │ │ ├── useFieldSchema.ts
│ │ │ ├── useForm.ts
│ │ │ ├── useFormEffects.ts
│ │ │ ├── useInjectionCleaner.ts
│ │ │ └── useParentForm.ts
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── connect.ts
│ │ │ ├── context.ts
│ │ │ ├── createForm.ts
│ │ │ ├── fragment.ts
│ │ │ ├── h.ts
│ │ │ └── index.ts
│ │ ├── types
│ │ │ └── index.ts
│ │ ├── utils
│ │ │ ├── formatVNodeData.ts
│ │ │ ├── getFieldProps.ts
│ │ │ ├── getRawComponent.ts
│ │ │ └── resolveSchemaProps.ts
│ │ └── vue2-components.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ └── tsconfig.types.json
├── README.md
├── README.zh-cn.md
├── scripts
│ ├── build-style
│ │ ├── buildAllStyles.ts
│ │ ├── copy.ts
│ │ ├── helper.ts
│ │ └── index.ts
│ └── rollup.base.js
├── tsconfig.build.json
├── tsconfig.jest.json
├── tsconfig.json
└── yarn.lock
```
# Files
--------------------------------------------------------------------------------
/packages/react/docs/api/components/ObjectField.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | order: 2
3 | ---
4 |
5 | # ObjectField
6 |
7 | ## 描述
8 |
9 | 作为@formily/core 的 [createObjectField](https://core.formilyjs.org/zh-CN/api/models/form#createobjectfield) React 实现,它是专门用于将 ViewModel 与输入控件做绑定的桥接组件,ObjectField 组件属性参考[IFieldFactoryProps](https://core.formilyjs.org/zh-CN/api/models/form#ifieldfactoryprops)
10 |
11 | <Alert>
12 | 我们在使用 ObjectField 组件的时候,一定要记得传name属性。同时要使用render props形式来组织子组件
13 | </Alert>
14 |
15 | ## 签名
16 |
17 | ```ts
18 | type ObjectField = React.FC<React.PropsWithChildren<IFieldFactoryProps>>
19 | ```
20 |
21 | ## 自定义组件用例
22 |
23 | ```tsx
24 | import React from 'react'
25 | import { createForm, ObjectField as ObjectFieldType } from '@formily/core'
26 | import {
27 | FormProvider,
28 | Field,
29 | ObjectField,
30 | useField,
31 | observer,
32 | } from '@formily/react'
33 | import { Input, Button, Space } from 'antd'
34 |
35 | const form = createForm()
36 |
37 | const ObjectComponent = observer(() => {
38 | const field = useField<ObjectFieldType>()
39 | return (
40 | <>
41 | <div>
42 | {Object.keys(field.value || {}).map((key) => (
43 | <div key={key} style={{ display: 'flex-block', marginBottom: 10 }}>
44 | <Space>
45 | <Field name={key} component={[Input, { placeholder: key }]} />
46 | <Button
47 | onClick={() => {
48 | field.removeProperty(key)
49 | }}
50 | >
51 | Remove
52 | </Button>
53 | </Space>
54 | </div>
55 | ))}
56 | </div>
57 | <Space>
58 | <Field
59 | name="propertyName"
60 | basePath={''}
61 | required
62 | component={[Input, { placeholder: 'Property Name' }]}
63 | />
64 | <Button
65 | onClick={() => {
66 | const name = form.values.propertyName
67 | if (name && !form.existValuesIn(`${field.path}.${name}`)) {
68 | field.addProperty(name, '')
69 | form.deleteValuesIn('propertyName')
70 | }
71 | }}
72 | >
73 | Add
74 | </Button>
75 | </Space>
76 | </>
77 | )
78 | })
79 |
80 | export default () => (
81 | <FormProvider form={form}>
82 | <ObjectField name="object" component={[ObjectComponent]} />
83 | </FormProvider>
84 | )
85 | ```
86 |
87 | ## RenderProps 用例
88 |
89 | ```tsx
90 | import React from 'react'
91 | import { createForm } from '@formily/core'
92 | import { FormProvider, Field, ObjectField } from '@formily/react'
93 | import { Input, Button, Space } from 'antd'
94 |
95 | const form = createForm()
96 |
97 | export default () => (
98 | <FormProvider form={form}>
99 | <ObjectField name="object">
100 | {(field) => {
101 | return (
102 | <>
103 | <div>
104 | {Object.keys(field.value || {}).map((key) => (
105 | <div
106 | key={key}
107 | style={{ display: 'flex-block', marginBottom: 10 }}
108 | >
109 | <Space>
110 | <Field
111 | name={key}
112 | component={[Input, { placeholder: key }]}
113 | />
114 | <Button
115 | onClick={() => {
116 | field.removeProperty(key)
117 | }}
118 | >
119 | Remove
120 | </Button>
121 | </Space>
122 | </div>
123 | ))}
124 | </div>
125 | <Space>
126 | <Field
127 | name="propertyName"
128 | basePath={''}
129 | required
130 | component={[Input, { placeholder: 'Property Name' }]}
131 | />
132 | <Button
133 | onClick={() => {
134 | const name = form.values.propertyName
135 | if (name && !form.existValuesIn(`${field.path}.${name}`)) {
136 | field.addProperty(name, '')
137 | form.deleteValuesIn('propertyName')
138 | }
139 | }}
140 | >
141 | Add
142 | </Button>
143 | </Space>
144 | </>
145 | )
146 | }}
147 | </ObjectField>
148 | </FormProvider>
149 | )
150 | ```
151 |
```
--------------------------------------------------------------------------------
/packages/antd/src/array-items/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { useRef } from 'react'
2 | import { ArrayField } from '@formily/core'
3 | import {
4 | useField,
5 | observer,
6 | useFieldSchema,
7 | RecursionField,
8 | } from '@formily/react'
9 | import cls from 'classnames'
10 | import { ISchema } from '@formily/json-schema'
11 | import {
12 | usePrefixCls,
13 | SortableContainer,
14 | SortableElement,
15 | } from '../__builtins__'
16 | import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base'
17 |
18 | type ComposedArrayItems = React.FC<
19 | React.PropsWithChildren<
20 | React.HTMLAttributes<HTMLDivElement> & IArrayBaseProps
21 | >
22 | > &
23 | ArrayBaseMixins & {
24 | Item?: React.FC<
25 | React.HTMLAttributes<HTMLDivElement> & {
26 | type?: 'card' | 'divide'
27 | }
28 | >
29 | }
30 |
31 | const SortableItem = SortableElement(
32 | (props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => {
33 | const prefixCls = usePrefixCls('formily-array-items')
34 | return (
35 | <div {...props} className={cls(`${prefixCls}-item`, props.className)}>
36 | {props.children}
37 | </div>
38 | )
39 | }
40 | )
41 |
42 | const SortableList = SortableContainer(
43 | (props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => {
44 | const prefixCls = usePrefixCls('formily-array-items')
45 | return (
46 | <div {...props} className={cls(`${prefixCls}-list`, props.className)}>
47 | {props.children}
48 | </div>
49 | )
50 | }
51 | )
52 |
53 | const isAdditionComponent = (schema: ISchema) => {
54 | return schema['x-component']?.indexOf('Addition') > -1
55 | }
56 |
57 | const useAddition = () => {
58 | const schema = useFieldSchema()
59 | return schema.reduceProperties((addition, schema, key) => {
60 | if (isAdditionComponent(schema)) {
61 | return <RecursionField schema={schema} name={key} />
62 | }
63 | return addition
64 | }, null)
65 | }
66 |
67 | export const ArrayItems: ComposedArrayItems = observer((props) => {
68 | const field = useField<ArrayField>()
69 | const prefixCls = usePrefixCls('formily-array-items')
70 | const ref = useRef<HTMLDivElement>(null)
71 | const schema = useFieldSchema()
72 | const addition = useAddition()
73 | const dataSource = Array.isArray(field.value) ? field.value : []
74 | const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props
75 | if (!schema) throw new Error('can not found schema object')
76 | return (
77 | <ArrayBase
78 | onAdd={onAdd}
79 | onCopy={onCopy}
80 | onRemove={onRemove}
81 | onMoveUp={onMoveUp}
82 | onMoveDown={onMoveDown}
83 | >
84 | <div
85 | {...props}
86 | ref={ref}
87 | onChange={() => {}}
88 | className={cls(prefixCls, props.className)}
89 | >
90 | <SortableList
91 | list={dataSource.slice()}
92 | className={`${prefixCls}-sort-helper`}
93 | onSortEnd={({ oldIndex, newIndex }) => {
94 | field.move(oldIndex, newIndex)
95 | }}
96 | >
97 | {dataSource?.map((item, index) => {
98 | const items = Array.isArray(schema.items)
99 | ? schema.items[index] || schema.items[0]
100 | : schema.items
101 | return (
102 | <ArrayBase.Item
103 | key={index}
104 | index={index}
105 | record={() => field.value?.[index]}
106 | >
107 | <SortableItem key={`item-${index}`} lockAxis="y" index={index}>
108 | <div className={`${prefixCls}-item-inner`}>
109 | <RecursionField schema={items} name={index} />
110 | </div>
111 | </SortableItem>
112 | </ArrayBase.Item>
113 | )
114 | })}
115 | </SortableList>
116 | {addition}
117 | </div>
118 | </ArrayBase>
119 | )
120 | })
121 |
122 | ArrayItems.displayName = 'ArrayItems'
123 |
124 | ArrayItems.Item = (props) => {
125 | const prefixCls = usePrefixCls('formily-array-items')
126 | return (
127 | <div
128 | {...props}
129 | onChange={() => {}}
130 | className={cls(`${prefixCls}-${props.type || 'card'}`, props.className)}
131 | >
132 | {props.children}
133 | </div>
134 | )
135 | }
136 |
137 | ArrayBase.mixin(ArrayItems)
138 |
139 | export default ArrayItems
140 |
```
--------------------------------------------------------------------------------
/scripts/rollup.base.js:
--------------------------------------------------------------------------------
```javascript
1 | import path from 'path'
2 | import typescript from 'rollup-plugin-typescript2'
3 | import resolve from 'rollup-plugin-node-resolve'
4 | import commonjs from '@rollup/plugin-commonjs'
5 | import externalGlobals from 'rollup-plugin-external-globals'
6 | import injectProcessEnv from 'rollup-plugin-inject-process-env'
7 | import dts from 'rollup-plugin-dts'
8 | import { terser } from 'rollup-plugin-terser'
9 |
10 | const presets = () => {
11 | const externals = {
12 | antd: 'antd',
13 | vue: 'Vue',
14 | react: 'React',
15 | moment: 'moment',
16 | 'react-is': 'ReactIs',
17 | '@alifd/next': 'Next',
18 | 'mobx-react-lite': 'mobxReactLite',
19 | 'react-dom': 'ReactDOM',
20 | 'element-ui': 'Element',
21 | '@ant-design/icons': 'icons',
22 | '@vue/composition-api': 'VueCompositionAPI',
23 | '@formily/reactive-react': 'Formily.ReactiveReact',
24 | '@formily/reactive-vue': 'Formily.ReactiveVue',
25 | '@formily/reactive': 'Formily.Reactive',
26 | '@formily/path': 'Formily.Path',
27 | '@formily/shared': 'Formily.Shared',
28 | '@formily/validator': 'Formily.Validator',
29 | '@formily/core': 'Formily.Core',
30 | '@formily/json-schema': 'Formily.JSONSchema',
31 | '@formily/react': 'Formily.React',
32 | '@formily/vue': 'Formily.Vue',
33 | 'vue-demi': 'VueDemi'
34 | }
35 | return [
36 | typescript({
37 | tsconfig: './tsconfig.build.json',
38 | tsconfigOverride: {
39 | compilerOptions: {
40 | module: 'ESNext',
41 | declaration: false,
42 | },
43 | },
44 | }),
45 | resolve(),
46 | commonjs(),
47 | externalGlobals(externals, {
48 | exclude: ['**/*.{less,sass,scss}'],
49 | }),
50 | ]
51 | }
52 |
53 | const createEnvPlugin = (env) => {
54 | return injectProcessEnv(
55 | {
56 | NODE_ENV: env,
57 | },
58 | {
59 | exclude: '**/*.{css,less,sass,scss}',
60 | verbose: false,
61 | }
62 | )
63 | }
64 |
65 | const inputFilePath = path.join(process.cwd(), 'src/index.ts')
66 |
67 | const noUIDtsPackages = [
68 | 'formily.core',
69 | 'formily.validator',
70 | 'formily.shared',
71 | 'formily.path',
72 | 'formily.json-schema',
73 | 'formily.reactive',
74 | ]
75 |
76 | export const removeImportStyleFromInputFilePlugin = () => ({
77 | name: 'remove-import-style-from-input-file',
78 | transform(code, id) {
79 | // 样式由 build:style 进行打包,所以要删除入口文件上的 `import './style'`
80 | if (inputFilePath === id) {
81 | return code.replace(`import './style';`, '')
82 | }
83 |
84 | return code
85 | },
86 | })
87 |
88 | export default (filename, targetName, ...plugins) => {
89 | const base = [
90 | {
91 | input: 'src/index.ts',
92 | output: {
93 | format: 'umd',
94 | file: `dist/${filename}.umd.development.js`,
95 | name: targetName,
96 | sourcemap: true,
97 | amd: {
98 | id: filename,
99 | },
100 | globals: {
101 | '@formily/json-schema': 'Formily.JSONSchema',
102 | },
103 | },
104 | external: ['react', 'react-dom', 'react-is', '@formily/json-schema'],
105 | plugins: [...presets(), ...plugins, createEnvPlugin('development')],
106 | },
107 | {
108 | input: 'src/index.ts',
109 | output: {
110 | format: 'umd',
111 | file: `dist/${filename}.umd.production.js`,
112 | name: targetName,
113 | sourcemap: true,
114 | amd: {
115 | id: filename,
116 | },
117 | globals: {
118 | '@formily/json-schema': 'Formily.JSONSchema',
119 | },
120 | },
121 | external: ['react', 'react-dom', 'react-is', '@formily/json-schema'],
122 | plugins: [
123 | ...presets(),
124 | terser(),
125 | ...plugins,
126 | createEnvPlugin('production'),
127 | ],
128 | },
129 | ]
130 |
131 | if (noUIDtsPackages.includes(filename)) {
132 | base.push({
133 | input: 'esm/index.d.ts',
134 | output: {
135 | format: 'es',
136 | file: `dist/${filename}.d.ts`,
137 | },
138 | plugins: [dts(), ...plugins],
139 | })
140 | base.push({
141 | input: 'esm/index.d.ts',
142 | output: {
143 | format: 'es',
144 | file: `dist/${filename}.all.d.ts`,
145 | },
146 | plugins: [
147 | dts({
148 | respectExternal: true,
149 | }),
150 | ...plugins,
151 | ],
152 | })
153 | }
154 |
155 | return base
156 | }
157 |
```
--------------------------------------------------------------------------------
/packages/reactive/src/autorun.ts:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | batchEnd,
3 | batchStart,
4 | disposeBindingReactions,
5 | releaseBindingReactions,
6 | disposeEffects,
7 | hasDepsChange,
8 | } from './reaction'
9 | import { isFn } from './checkers'
10 | import { ReactionStack } from './environment'
11 | import { Reaction, IReactionOptions, Dispose } from './types'
12 | import { toArray } from './array'
13 |
14 | interface IValue {
15 | currentValue?: any
16 | oldValue?: any
17 | }
18 |
19 | export const autorun = (tracker: Reaction, name = 'AutoRun') => {
20 | const reaction: Reaction = () => {
21 | if (!isFn(tracker)) return
22 | if (reaction._boundary > 0) return
23 | if (ReactionStack.indexOf(reaction) === -1) {
24 | releaseBindingReactions(reaction)
25 | try {
26 | batchStart()
27 | ReactionStack.push(reaction)
28 | tracker()
29 | } finally {
30 | ReactionStack.pop()
31 | reaction._boundary++
32 | batchEnd()
33 | reaction._boundary = 0
34 | reaction._memos.cursor = 0
35 | reaction._effects.cursor = 0
36 | }
37 | }
38 | }
39 | const cleanRefs = () => {
40 | reaction._memos = {
41 | queue: [],
42 | cursor: 0,
43 | }
44 | reaction._effects = {
45 | queue: [],
46 | cursor: 0,
47 | }
48 | }
49 | reaction._boundary = 0
50 | reaction._name = name
51 | cleanRefs()
52 | reaction()
53 | return () => {
54 | disposeBindingReactions(reaction)
55 | disposeEffects(reaction)
56 | cleanRefs()
57 | }
58 | }
59 |
60 | autorun.memo = <T>(callback: () => T, dependencies?: any[]): T => {
61 | if (!isFn(callback)) return
62 | const current = ReactionStack[ReactionStack.length - 1]
63 | if (!current || !current._memos)
64 | throw new Error('autorun.memo must used in autorun function body.')
65 | const deps = toArray(dependencies || [])
66 | const id = current._memos.cursor++
67 | const old = current._memos.queue[id]
68 | if (!old || hasDepsChange(deps, old.deps)) {
69 | const value = callback()
70 | current._memos.queue[id] = {
71 | value,
72 | deps,
73 | }
74 | return value
75 | }
76 | return old.value
77 | }
78 |
79 | autorun.effect = (callback: () => void | Dispose, dependencies?: any[]) => {
80 | if (!isFn(callback)) return
81 | const current = ReactionStack[ReactionStack.length - 1]
82 | if (!current || !current._effects)
83 | throw new Error('autorun.effect must used in autorun function body.')
84 | const effects = current._effects
85 | const deps = toArray(dependencies || [{}])
86 | const id = effects.cursor++
87 | const old = effects.queue[id]
88 | if (!old || hasDepsChange(deps, old.deps)) {
89 | Promise.resolve(0).then(() => {
90 | if (current._disposed) return
91 | const dispose = callback()
92 | if (isFn(dispose)) {
93 | effects.queue[id].dispose = dispose
94 | }
95 | })
96 | effects.queue[id] = {
97 | deps,
98 | }
99 | }
100 | }
101 |
102 | export const reaction = <T>(
103 | tracker: () => T,
104 | subscriber?: (value: T, oldValue: T) => void,
105 | options?: IReactionOptions<T>
106 | ) => {
107 | const realOptions = {
108 | name: 'Reaction',
109 | ...options,
110 | }
111 | const value: IValue = {}
112 | const dirtyCheck = () => {
113 | if (isFn(realOptions.equals))
114 | return !realOptions.equals(value.oldValue, value.currentValue)
115 | return value.oldValue !== value.currentValue
116 | }
117 |
118 | const fireAction = () => {
119 | try {
120 | //如果untrack的话,会导致用户如果在scheduler里同步调用setState影响下次React渲染的依赖收集
121 | batchStart()
122 | if (isFn(subscriber)) subscriber(value.currentValue, value.oldValue)
123 | } finally {
124 | batchEnd()
125 | }
126 | }
127 |
128 | const reaction: Reaction = () => {
129 | if (ReactionStack.indexOf(reaction) === -1) {
130 | releaseBindingReactions(reaction)
131 | try {
132 | ReactionStack.push(reaction)
133 | value.currentValue = tracker()
134 | } finally {
135 | ReactionStack.pop()
136 | }
137 | }
138 | }
139 | reaction._scheduler = (looping) => {
140 | looping()
141 | if (dirtyCheck()) fireAction()
142 | value.oldValue = value.currentValue
143 | }
144 | reaction._name = realOptions.name
145 | reaction()
146 | value.oldValue = value.currentValue
147 | if (realOptions.fireImmediately) {
148 | fireAction()
149 | }
150 | return () => {
151 | disposeBindingReactions(reaction)
152 | }
153 | }
154 |
```
--------------------------------------------------------------------------------
/packages/antd/src/password/PasswordStrength.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { Fragment } from 'react'
2 | import { ReactFC } from '@formily/react'
3 | import { isFn } from '@formily/shared'
4 |
5 | type ReactRenderPropsChildren<T = any> =
6 | | React.ReactNode
7 | | ((props: T) => React.ReactElement)
8 |
9 | interface IPasswordStrengthProps {
10 | value?: React.ReactText
11 | children?: ReactRenderPropsChildren<number>
12 | }
13 |
14 | const isNum = function (c) {
15 | return c >= 48 && c <= 57
16 | }
17 | const isLower = function (c) {
18 | return c >= 97 && c <= 122
19 | }
20 | const isUpper = function (c) {
21 | return c >= 65 && c <= 90
22 | }
23 | const isSymbol = function (c) {
24 | return !(isLower(c) || isUpper(c) || isNum(c))
25 | }
26 | const isLetter = function (c) {
27 | return isLower(c) || isUpper(c)
28 | }
29 |
30 | const getStrength = (val) => {
31 | if (!val) return 0
32 | let num = 0
33 | let lower = 0
34 | let upper = 0
35 | let symbol = 0
36 | let MNS = 0
37 | let rep = 0
38 | let repC = 0
39 | let consecutive = 0
40 | let sequential = 0
41 | const len = () => num + lower + upper + symbol
42 | const callme = () => {
43 | let re = num > 0 ? 1 : 0
44 | re += lower > 0 ? 1 : 0
45 | re += upper > 0 ? 1 : 0
46 | re += symbol > 0 ? 1 : 0
47 | if (re > 2 && len() >= 8) {
48 | return re + 1
49 | } else {
50 | return 0
51 | }
52 | }
53 | for (let i = 0; i < val.length; i++) {
54 | const c = val.charCodeAt(i)
55 | if (isNum(c)) {
56 | num++
57 | if (i !== 0 && i !== val.length - 1) {
58 | MNS++
59 | }
60 | if (i > 0 && isNum(val.charCodeAt(i - 1))) {
61 | consecutive++
62 | }
63 | } else if (isLower(c)) {
64 | lower++
65 | if (i > 0 && isLower(val.charCodeAt(i - 1))) {
66 | consecutive++
67 | }
68 | } else if (isUpper(c)) {
69 | upper++
70 | if (i > 0 && isUpper(val.charCodeAt(i - 1))) {
71 | consecutive++
72 | }
73 | } else {
74 | symbol++
75 | if (i !== 0 && i !== val.length - 1) {
76 | MNS++
77 | }
78 | }
79 | let exists = false
80 | for (let j = 0; j < val.length; j++) {
81 | if (val[i] === val[j] && i !== j) {
82 | exists = true
83 | repC += Math.abs(val.length / (j - i))
84 | }
85 | }
86 | if (exists) {
87 | rep++
88 | const unique = val.length - rep
89 | repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC)
90 | }
91 | if (i > 1) {
92 | const last1 = val.charCodeAt(i - 1)
93 | const last2 = val.charCodeAt(i - 2)
94 | if (isLetter(c)) {
95 | if (isLetter(last1) && isLetter(last2)) {
96 | const v = val.toLowerCase()
97 | const vi = v.charCodeAt(i)
98 | const vi1 = v.charCodeAt(i - 1)
99 | const vi2 = v.charCodeAt(i - 2)
100 | if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) {
101 | sequential++
102 | }
103 | }
104 | } else if (isNum(c)) {
105 | if (isNum(last1) && isNum(last2)) {
106 | if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
107 | sequential++
108 | }
109 | }
110 | } else {
111 | if (isSymbol(last1) && isSymbol(last2)) {
112 | if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
113 | sequential++
114 | }
115 | }
116 | }
117 | }
118 | }
119 | let sum = 0
120 | const length = len()
121 | sum += 4 * length
122 | if (lower > 0) {
123 | sum += 2 * (length - lower)
124 | }
125 | if (upper > 0) {
126 | sum += 2 * (length - upper)
127 | }
128 | if (num !== length) {
129 | sum += 4 * num
130 | }
131 | sum += 6 * symbol
132 | sum += 2 * MNS
133 | sum += 2 * callme()
134 | if (length === lower + upper) {
135 | sum -= length
136 | }
137 | if (length === num) {
138 | sum -= num
139 | }
140 | sum -= repC
141 | sum -= 2 * consecutive
142 | sum -= 3 * sequential
143 | sum = sum < 0 ? 0 : sum
144 | sum = sum > 100 ? 100 : sum
145 |
146 | if (sum >= 80) {
147 | return 100
148 | } else if (sum >= 60) {
149 | return 80
150 | } else if (sum >= 40) {
151 | return 60
152 | } else if (sum >= 20) {
153 | return 40
154 | } else {
155 | return 20
156 | }
157 | }
158 |
159 | export const PasswordStrength: ReactFC<IPasswordStrengthProps> = (props) => {
160 | if (isFn(props.children)) {
161 | return props.children(getStrength(String(props.value)))
162 | } else {
163 | return <Fragment>{props.children}</Fragment>
164 | }
165 | }
166 |
```
--------------------------------------------------------------------------------
/packages/next/src/password/PasswordStrength.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { Fragment } from 'react'
2 | import { ReactFC } from '@formily/react'
3 | import { isFn } from '@formily/shared'
4 |
5 | type ReactRenderPropsChildren<T = any> =
6 | | React.ReactNode
7 | | ((props: T) => React.ReactElement)
8 |
9 | interface IPasswordStrengthProps {
10 | value?: React.ReactText
11 | children?: ReactRenderPropsChildren<number>
12 | }
13 |
14 | const isNum = function (c) {
15 | return c >= 48 && c <= 57
16 | }
17 | const isLower = function (c) {
18 | return c >= 97 && c <= 122
19 | }
20 | const isUpper = function (c) {
21 | return c >= 65 && c <= 90
22 | }
23 | const isSymbol = function (c) {
24 | return !(isLower(c) || isUpper(c) || isNum(c))
25 | }
26 | const isLetter = function (c) {
27 | return isLower(c) || isUpper(c)
28 | }
29 |
30 | const getStrength = (val) => {
31 | if (!val) return 0
32 | let num = 0
33 | let lower = 0
34 | let upper = 0
35 | let symbol = 0
36 | let MNS = 0
37 | let rep = 0
38 | let repC = 0
39 | let consecutive = 0
40 | let sequential = 0
41 | const len = () => num + lower + upper + symbol
42 | const callme = () => {
43 | let re = num > 0 ? 1 : 0
44 | re += lower > 0 ? 1 : 0
45 | re += upper > 0 ? 1 : 0
46 | re += symbol > 0 ? 1 : 0
47 | if (re > 2 && len() >= 8) {
48 | return re + 1
49 | } else {
50 | return 0
51 | }
52 | }
53 | for (let i = 0; i < val.length; i++) {
54 | const c = val.charCodeAt(i)
55 | if (isNum(c)) {
56 | num++
57 | if (i !== 0 && i !== val.length - 1) {
58 | MNS++
59 | }
60 | if (i > 0 && isNum(val.charCodeAt(i - 1))) {
61 | consecutive++
62 | }
63 | } else if (isLower(c)) {
64 | lower++
65 | if (i > 0 && isLower(val.charCodeAt(i - 1))) {
66 | consecutive++
67 | }
68 | } else if (isUpper(c)) {
69 | upper++
70 | if (i > 0 && isUpper(val.charCodeAt(i - 1))) {
71 | consecutive++
72 | }
73 | } else {
74 | symbol++
75 | if (i !== 0 && i !== val.length - 1) {
76 | MNS++
77 | }
78 | }
79 | let exists = false
80 | for (let j = 0; j < val.length; j++) {
81 | if (val[i] === val[j] && i !== j) {
82 | exists = true
83 | repC += Math.abs(val.length / (j - i))
84 | }
85 | }
86 | if (exists) {
87 | rep++
88 | const unique = val.length - rep
89 | repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC)
90 | }
91 | if (i > 1) {
92 | const last1 = val.charCodeAt(i - 1)
93 | const last2 = val.charCodeAt(i - 2)
94 | if (isLetter(c)) {
95 | if (isLetter(last1) && isLetter(last2)) {
96 | const v = val.toLowerCase()
97 | const vi = v.charCodeAt(i)
98 | const vi1 = v.charCodeAt(i - 1)
99 | const vi2 = v.charCodeAt(i - 2)
100 | if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) {
101 | sequential++
102 | }
103 | }
104 | } else if (isNum(c)) {
105 | if (isNum(last1) && isNum(last2)) {
106 | if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
107 | sequential++
108 | }
109 | }
110 | } else {
111 | if (isSymbol(last1) && isSymbol(last2)) {
112 | if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
113 | sequential++
114 | }
115 | }
116 | }
117 | }
118 | }
119 | let sum = 0
120 | const length = len()
121 | sum += 4 * length
122 | if (lower > 0) {
123 | sum += 2 * (length - lower)
124 | }
125 | if (upper > 0) {
126 | sum += 2 * (length - upper)
127 | }
128 | if (num !== length) {
129 | sum += 4 * num
130 | }
131 | sum += 6 * symbol
132 | sum += 2 * MNS
133 | sum += 2 * callme()
134 | if (length === lower + upper) {
135 | sum -= length
136 | }
137 | if (length === num) {
138 | sum -= num
139 | }
140 | sum -= repC
141 | sum -= 2 * consecutive
142 | sum -= 3 * sequential
143 | sum = sum < 0 ? 0 : sum
144 | sum = sum > 100 ? 100 : sum
145 |
146 | if (sum >= 80) {
147 | return 100
148 | } else if (sum >= 60) {
149 | return 80
150 | } else if (sum >= 40) {
151 | return 60
152 | } else if (sum >= 20) {
153 | return 40
154 | } else {
155 | return 20
156 | }
157 | }
158 |
159 | export const PasswordStrength: ReactFC<IPasswordStrengthProps> = (props) => {
160 | if (isFn(props.children)) {
161 | return props.children(getStrength(String(props.value)))
162 | } else {
163 | return <Fragment>{props.children}</Fragment>
164 | }
165 | }
166 |
```
--------------------------------------------------------------------------------
/packages/react/docs/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Formily-Alibaba unified front-end form solution
3 | order: 10
4 | hero:
5 | title: React Library
6 | desc: Alibaba Unified Form Solution
7 | actions:
8 | - text: Home Site
9 | link: //formilyjs.org
10 | - text: Development Guide
11 | link: /guide
12 | features:
13 | - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg
14 | title: Ultra High Performance
15 | desc: Dependency tracking, efficient update, on-demand rendering
16 | - icon: https://img.alicdn.com/imgextra/i2/O1CN016i72sH1c5wh1kyy9U_!!6000000003550-55-tps-800-800.svg
17 | title: Out Of The Box
18 | desc: The component status is automatically bound, and the access cost is extremely low
19 | - icon: https://img.alicdn.com/imgextra/i3/O1CN01JHzg8U1FZV5Mvt012_!!6000000000501-55-tps-800-800.svg
20 | title: JSON Schema Driver
21 | desc: Standard JSON-Schema
22 | - icon: https://img.alicdn.com/imgextra/i3/O1CN0194OqFF1ui6mMT4g7O_!!6000000006070-55-tps-800-800.svg
23 | title: Scene Reuse
24 | desc: Based on protocol-driven, abstract scene components
25 | - icon: https://img.alicdn.com/imgextra/i4/O1CN018vDmpl2186xdLu6KI_!!6000000006939-55-tps-800-800.svg
26 | title: Debugging Friendly
27 | desc: Natural docking with Formily DevTools
28 | - icon: https://img.alicdn.com/imgextra/i4/O1CN01u6jHgs1ZMwXpjAYnh_!!6000000003181-55-tps-800-800.svg
29 | title: Smart Tips
30 | desc: Embrace Typescript
31 | footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by self
32 | ---
33 |
34 | ## Installation
35 |
36 | ```bash
37 | $ npm install --save @formily/core @formily/react
38 |
39 | ```
40 |
41 | ## Quick start
42 |
43 | ```tsx
44 | /**
45 | * defaultShowCode: true
46 | */
47 | import React, { useMemo } from 'react'
48 | import { createForm, setValidateLanguage } from '@formily/core'
49 | import {
50 | FormProvider,
51 | FormConsumer,
52 | Field,
53 | useField,
54 | observer,
55 | } from '@formily/react'
56 | import { Input, Form } from 'antd'
57 |
58 | // FormItem UI component
59 | const FormItem = observer(({ children }) => {
60 | const field = useField()
61 | return (
62 | <Form.Item
63 | label={field.title}
64 | help={field.selfErrors?.length ? field.selfErrors : undefined}
65 | extra={field.description}
66 | validateStatus={field.validateStatus}
67 | >
68 | {children}
69 | </Form.Item>
70 | )
71 | })
72 |
73 | /*
74 | * The above logic has been implemented in @formily/antd, and there is no need to rewrite it in actual use
75 | */
76 |
77 | //Switch the built-in check internationalization copy to English
78 | setValidateLanguage('en')
79 |
80 | export default () => {
81 | const form = useMemo(() => createForm({ validateFirst: true }))
82 |
83 | const createPasswordEqualValidate = (equalName) => (field) => {
84 | if (
85 | form.values.confirm_password &&
86 | field.value &&
87 | form.values[equalName] !== field.value
88 | ) {
89 | field.selfErrors = ['Password does not match Confirm Password.']
90 | } else {
91 | field.selfErrors = []
92 | }
93 | }
94 |
95 | return (
96 | <FormProvider form={form}>
97 | <Form layout="vertical">
98 | <Field
99 | name="name"
100 | title="Name"
101 | required
102 | decorator={[FormItem]}
103 | component={[Input, { placeholder: 'Please Input' }]}
104 | />
105 | <Field
106 | name="password"
107 | title="Password"
108 | required
109 | decorator={[FormItem]}
110 | component={[Input, { type: 'password', placeholder: 'Please Input' }]}
111 | reactions={createPasswordEqualValidate('confirm_password')}
112 | />
113 | <Field
114 | name="confirm_password"
115 | title="Confirm Password"
116 | required
117 | decorator={[FormItem]}
118 | component={[Input, { type: 'password', placeholder: 'Please Input' }]}
119 | reactions={createPasswordEqualValidate('password')}
120 | />
121 | <code>
122 | <pre>
123 | <FormConsumer>
124 | {(form) => JSON.stringify(form.values, null, 2)}
125 | </FormConsumer>
126 | </pre>
127 | </code>
128 | </Form>
129 | </FormProvider>
130 | )
131 | }
132 | ```
133 |
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/index.md:
--------------------------------------------------------------------------------
```markdown
1 | # Ant Design
2 |
3 | ## Introduction
4 |
5 | @formily/antd is a professional component library for form scenarios based on Ant Design encapsulation. It has the following characteristics:
6 |
7 | - Only Formily 2.x is supported
8 | - Most components are not backward compatible
9 | - Unfortunately, many components of 1.x have inherent flaws in the API design. This is also because the form scheme has been explored, so there will be version breaks.
10 | - Richer component system
11 | - Layout components
12 | - FormLayout
13 | - FormItem
14 | - FormGrid
15 | - FormButtonGroup
16 | - Space
17 | - Submit
18 | - Reset
19 | - Input controls
20 | - Input
21 | - Password
22 | - Select
23 | - TreeSelect
24 | - DatePicker
25 | - TimePicker
26 | - NumberPicker
27 | - Transfer
28 | - Cascader
29 | - Radio
30 | - Checkbox
31 | - Upload
32 | - Switch
33 | - Scene components
34 | - ArrayCards
35 | - ArrayItems
36 | - ArrayTable
37 | - ArrayTabs
38 | - FormCollapse
39 | - FormStep
40 | - FormTab
41 | - FormDialog
42 | - FormDrawer
43 | - Editable
44 | - Reading state component
45 | - PreviewText
46 | - Theme customization ability
47 | - Completely abandon the 1.x styled-components solution, follow the style system of the component library, it is more convenient to customize the theme
48 | - Support secondary packaging
49 | - All components can be repackaged, and the 1.x component system cannot be repackaged, so providing this capability makes it more convenient for users to do business customization
50 | - Support reading mode
51 | - Although 1.x also supports reading mode, 2.x provides a separate PreviewText component, users can make reading mode encapsulation based on it, which is more flexible
52 | - Type is more friendly
53 | - Each component has an extremely complete type definition, and users can feel an unprecedented intelligent reminder experience during the actual development process
54 | - More complete layout control capabilities
55 | - 1.x's layout capabilities have basically converged to FormMegaLayout. This time, we directly removed Mega. Mega is a standard component and is completely internalized into FormLayout and FormItem components. At the same time, MegaLayout's grid layout capabilities are placed in FormGrid components. In, it also provides smarter layout capabilities.
56 | - More elegant and easy-to-use APIs, such as:
57 | - FormStep in the past has many problems. First, the type is not friendly. Second, the API is too hidden. To control the forward and backwards, you need to understand a bunch of private events. In the new version of FormStep, users only need to pay attention to the FormStep Reactive Model. You can create a Reactive Model through createFormStep and pass it to the FormStep component to quickly communicate. Similarly, FormTab/FormCollapse is the same communication mode.
58 | - Pop-up forms, drawer forms, presumably in the past, users had to write a lot of code on these two scenarios almost every time. This time, an extremely simple API is directly provided for users to use, which maximizes development efficiency.
59 |
60 | ## Installation
61 |
62 | ```bash
63 | $ npm install --save antd moment
64 | $ npm install --save @formily/core @formily/react @formily/antd
65 |
66 | ```
67 |
68 | ## Q/A
69 |
70 | Q: I want to package a set of component libraries by myself, what should I do?
71 |
72 | Answer: If it is an open source component library, you can directly participate in the project co-construction and provide PR. If it is a private component library in the enterprise, you can refer to the source code. The source code does not have too much complicated logic.
73 |
74 | Question: Why do components such as ArrayCards/ArrayTable/FormStep only support Schema mode and not pure JSX mode?
75 |
76 | Answer: This is the core advantage of Schema mode. With the help of protocols, we can do scene-based abstraction. On the contrary, pure JSX mode is limited by the unparseability of JSX. It is difficult for us to achieve UI-level scene-based abstraction. It's just an abstract hook.
77 |
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-table/json-schema.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <FormProvider :form="form">
3 | <SchemaField :schema="schema" />
4 | <Submit @submit="log">提交</Submit>
5 | </FormProvider>
6 | </template>
7 |
8 | <script>
9 | import { createForm } from '@formily/core'
10 | import { FormProvider, createSchemaField } from '@formily/vue'
11 | import { Submit, FormItem, ArrayTable, Input, Editable } from '@formily/element'
12 |
13 | const fields = createSchemaField({
14 | components: {
15 | FormItem,
16 | ArrayTable,
17 | Input,
18 | Editable,
19 | },
20 | })
21 |
22 | export default {
23 | components: { FormProvider, Submit, ...fields },
24 | data() {
25 | const form = createForm()
26 | const schema = {
27 | type: 'object',
28 | properties: {
29 | array: {
30 | type: 'array',
31 | 'x-decorator': 'FormItem',
32 | 'x-component': 'ArrayTable',
33 | items: {
34 | type: 'object',
35 | properties: {
36 | column1: {
37 | type: 'void',
38 | 'x-component': 'ArrayTable.Column',
39 | 'x-component-props': {
40 | width: 80,
41 | title: 'Index',
42 | align: 'center',
43 | },
44 | properties: {
45 | index: {
46 | type: 'void',
47 | 'x-component': 'ArrayTable.Index',
48 | },
49 | },
50 | },
51 | column2: {
52 | type: 'void',
53 | 'x-component': 'ArrayTable.Column',
54 | 'x-component-props': { width: 200, title: 'A1' },
55 | properties: {
56 | a1: {
57 | type: 'string',
58 | 'x-decorator': 'Editable',
59 | 'x-component': 'Input',
60 | },
61 | },
62 | },
63 | column3: {
64 | type: 'void',
65 | 'x-component': 'ArrayTable.Column',
66 | 'x-component-props': { width: 200, title: 'A2' },
67 | properties: {
68 | a2: {
69 | type: 'string',
70 | 'x-decorator': 'FormItem',
71 | 'x-component': 'Input',
72 | },
73 | },
74 | },
75 | column4: {
76 | type: 'void',
77 | 'x-component': 'ArrayTable.Column',
78 | 'x-component-props': { title: 'A3' },
79 | properties: {
80 | a3: {
81 | type: 'string',
82 | 'x-decorator': 'FormItem',
83 | 'x-component': 'Input',
84 | },
85 | },
86 | },
87 | column5: {
88 | type: 'void',
89 | 'x-component': 'ArrayTable.Column',
90 | 'x-component-props': {
91 | title: 'Operations',
92 | prop: 'operations',
93 | width: 200,
94 | fixed: 'right',
95 | },
96 | properties: {
97 | item: {
98 | type: 'void',
99 | 'x-component': 'FormItem',
100 | properties: {
101 | remove: {
102 | type: 'void',
103 | 'x-component': 'ArrayTable.Remove',
104 | },
105 | moveDown: {
106 | type: 'void',
107 | 'x-component': 'ArrayTable.MoveDown',
108 | },
109 | moveUp: {
110 | type: 'void',
111 | 'x-component': 'ArrayTable.MoveUp',
112 | },
113 | },
114 | },
115 | },
116 | },
117 | },
118 | },
119 | properties: {
120 | add: {
121 | type: 'void',
122 | 'x-component': 'ArrayTable.Addition',
123 | title: '添加条目',
124 | },
125 | },
126 | },
127 | },
128 | }
129 | return {
130 | form,
131 | schema,
132 | }
133 | },
134 | methods: {
135 | log(...v) {
136 | console.log(...v)
137 | },
138 | },
139 | }
140 | </script>
141 |
```
--------------------------------------------------------------------------------
/docs/guide/learn-formily.md:
--------------------------------------------------------------------------------
```markdown
1 | # How to learn Formily
2 |
3 | ## Study Suggestion
4 |
5 | To describe Formily in one sentence, it is an MVVM form solution that abstracts the form domain model. Therefore, if you want to use Formily in depth, you must learn and understand what Formily's domain model is like and what problems does it solve. After understanding the domain model, it is actually how to consume the view layer of this domain model. This layer only needs to look at the documentation of the specific components.
6 |
7 | ## About the documentation
8 |
9 | Because Formily’s learning costs are still relatively high, if you want to quickly understand the full picture of Formily, the most important thing is to read the documentation. It's just how to look at the document and where it will be more important. Below we give different document learning routes for different users.
10 |
11 | ### Entry-level user
12 |
13 | - Introduction, because you need to understand Formily's core ideas and whether it is suitable for your business scenario.
14 | - Quick start, learn how to use Formily in practice from the simplest example.
15 | - Component documentation/core library documentation, because Formily has already encapsulated most of the out-of-the-box components for you. If you encounter component-related problems, you can just check the component documentation just like looking up a dictionary.
16 | - Scenario case, starting from the specific scenario, see what is the best practice in this scenario.
17 |
18 | ### Advanced users
19 |
20 | - Digest the core concepts carefully and have a deeper understanding of Formily.
21 | - Advanced guide, mainly to learn more advanced usage methods, such as custom components, from simple custom components to super complex custom components.
22 | - Read component documents/core library documents at any time to deepen memory
23 | - For the details and best practices of custom component development, it is recommended to look directly at the source code of @formily/antd or @formily/next, because this is the boilerplate code and is closely related to the actual business scenario.
24 |
25 | ### Source code co-builder
26 |
27 | - Contribution guide, understand the most basic contribution posture.
28 | - Read the document, if you find that the document is defective, you can submit a PR to fix it.
29 | - Read the unit test to understand the implementation details corresponding to each test case. If you find that there are missing test cases, you can submit a PR.
30 | - Read the source code, if you find a bug in the source code, you can raise a PR.
31 |
32 | <Alert type="error">
33 | Pay attention to modify the source code, you must bring unit tests
34 | </Alert>
35 |
36 | ## About the question
37 |
38 | If you encounter problems during the development process, it is recommended to use the search function at the top of the document to quickly search for the content of the document and solve it quickly. If you can’t find it, I recommend you to ask questions in the [forum](https://github.com/alibaba/formily/discussions). It is convenient to record. If you encounter a very urgent problem, you can help solve it in the Dingding group @白玄. **It is not recommended to ask various basic questions directly without reading the document, which is very inefficient**
39 |
40 | ## About the bug
41 |
42 | If you find behaviors that do not meet expectations during the development process and can be reproduced in the smallest case, you can submit an [issue](https://github.com/alibaba/formily/issues) to Formily
43 | It is strongly not recommended to record the problem in the issue, which will disrupt the information flow of Issue. At the same time, **be sure to bring the smallest reproducible link address when mentioning Issue**, so that developers can quickly locate the problem and fix it quickly, instead of Find bugs in a bunch of codes.
44 |
45 | ## About Feature Request
46 |
47 | If during the development process you find that some of Formily's designs are not good, or can be improved better, you can submit your own ideas in the [forum](https://github.com/alibaba/formily/discussions)
48 |
```
--------------------------------------------------------------------------------
/packages/validator/src/parser.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { isArr, isBool, isFn, isStr } from '@formily/shared'
2 | import {
3 | ValidatorDescription,
4 | ValidatorFunction,
5 | ValidatorParsedFunction,
6 | Validator,
7 | IValidatorRules,
8 | isValidateResult,
9 | IValidatorOptions,
10 | } from './types'
11 | import { getValidateRules, getValidateLocale } from './registry'
12 | import { render } from './template'
13 |
14 | const getRuleMessage = (rule: IValidatorRules, type: string) => {
15 | if (rule.format) {
16 | return rule.message || getValidateLocale(rule.format)
17 | }
18 | return rule.message || getValidateLocale(type)
19 | }
20 |
21 | export const parseValidatorDescription = (
22 | description: ValidatorDescription
23 | ): IValidatorRules => {
24 | if (!description) return {}
25 | let rules: IValidatorRules = {}
26 | if (isStr(description)) {
27 | rules.format = description
28 | } else if (isFn(description)) {
29 | rules.validator = description
30 | } else {
31 | rules = Object.assign(rules, description)
32 | }
33 | return rules
34 | }
35 |
36 | export const parseValidatorDescriptions = <Context = any>(
37 | validator: Validator<Context>
38 | ): IValidatorRules[] => {
39 | if (!validator) return []
40 | const array = isArr(validator) ? validator : [validator]
41 | return array.map((description) => {
42 | return parseValidatorDescription(description)
43 | })
44 | }
45 |
46 | export const parseValidatorRules = (
47 | rules: IValidatorRules = {}
48 | ): ValidatorParsedFunction[] => {
49 | const getRulesKeys = (): string[] => {
50 | const keys = []
51 | if ('required' in rules) {
52 | keys.push('required')
53 | }
54 | for (let key in rules) {
55 | if (key === 'required' || key === 'validator') continue
56 | keys.push(key)
57 | }
58 | if ('validator' in rules) {
59 | keys.push('validator')
60 | }
61 | return keys
62 | }
63 | const getContext = (context: any, value: any) => {
64 | return {
65 | ...rules,
66 | ...context,
67 | value,
68 | }
69 | }
70 | const createValidate =
71 | (callback: ValidatorFunction, message: string) =>
72 | async (value: any, context: any) => {
73 | const context_ = getContext(context, value)
74 | try {
75 | const results = await callback(
76 | value,
77 | { ...rules, message },
78 | context_,
79 | (message: string, scope: any) => {
80 | return render(
81 | {
82 | type: 'error',
83 | message,
84 | },
85 | Object.assign(context_, scope)
86 | )?.message
87 | }
88 | )
89 | if (isBool(results)) {
90 | if (!results) {
91 | return render(
92 | {
93 | type: 'error',
94 | message,
95 | },
96 | context_
97 | )
98 | }
99 | return {
100 | type: 'error',
101 | message: undefined,
102 | }
103 | } else if (results) {
104 | if (isValidateResult(results)) {
105 | return render(results, context_)
106 | }
107 | return render(
108 | {
109 | type: 'error',
110 | message: results,
111 | },
112 | context_
113 | )
114 | }
115 |
116 | return {
117 | type: 'error',
118 | message: undefined,
119 | }
120 | } catch (e) {
121 | return {
122 | type: 'error',
123 | message: e?.message || e,
124 | }
125 | }
126 | }
127 | return getRulesKeys().reduce((buf, key) => {
128 | const callback = getValidateRules(key)
129 | if (callback) {
130 | const validator = createValidate(callback, getRuleMessage(rules, key))
131 | return buf.concat(validator)
132 | }
133 | return buf
134 | }, [])
135 | }
136 |
137 | export const parseValidator = <Context = any>(
138 | validator: Validator<Context>,
139 | options: IValidatorOptions = {}
140 | ) => {
141 | if (!validator) return []
142 | const array = isArr(validator) ? validator : [validator]
143 | return array.reduce<ValidatorParsedFunction<Context>[]>(
144 | (buf, description) => {
145 | const rules = parseValidatorDescription(description)
146 | const triggerType = rules.triggerType ?? 'onInput'
147 | if (options?.triggerType && options.triggerType !== triggerType)
148 | return buf
149 | return rules ? buf.concat(parseValidatorRules(rules)) : buf
150 | },
151 | []
152 | )
153 | }
154 |
```
--------------------------------------------------------------------------------
/docs/guide/contribution.md:
--------------------------------------------------------------------------------
```markdown
1 | # Contribution Guide
2 |
3 | ## Why become a contributor?
4 |
5 | Welcome to our community!**Formily** It is the only official open-source form framework announced by Alibaba. Its functions and quality are guaranteed. It has a large number of community users. Participating in contributions can make **Formily** stronger and allow more developers to enjoy a better experience of developing forms. we are very grateful to any people who initiated **Pull Request** for this project.
6 |
7 | ## What can I contribute?
8 |
9 | - Add&Update features
10 | - Add/Update unit test cases
11 | - Fix the existing issue
12 | - Documentation improvements
13 | - Other
14 |
15 | ## How to contribute?
16 |
17 | #### Pull Repository
18 |
19 | - Original repository: https://github.com/alibaba/formily
20 | - Target repository: fork to your own github 
21 |
22 | #### Pull Branch
23 |
24 | The original branch is alibaba/formily master, The branch after pulling should be quirkyshop/formily master
25 |
26 | > Note: The recommended branch name is [feat]-[name], [feat] is the type of this branch. Featdoc[other] is optional, and [name] is the name, just customize it. eg. unittest-core (meaning: add single test to the core)
27 |
28 | #### Submit Code
29 |
30 | The code style follows 2 spaces and no semicolons. Please do not include any console-related methods and debuggers in the code unless it is explained. After the development is completed, submit a pull request to the repository you forked.
31 |
32 | > Note the target repository on the left here(base repository is alibaba/formily master) . And then the doc-wiki of the current branch own repository on the right.
33 |
34 | #### PR Specification
35 |
36 | Reference documents: https://github.com/alibaba/formily/blob/master/.github/GIT_COMMIT_SPECIFIC.md
37 |
38 | - PR name: format: `<type>(<scope>): <subject>` For example: `feat(core): add unit test`
39 | - PR content: List the content of this change
40 | - PR requirements: the added feat content, as far as possible, make clear comments. And the corresponding single test coverage should be covered as much 关注梁帅抽大奖 possible.
41 | - BUGFIX requirements: If the modified issue is related to issues, please include the relevant issueID in the content.
42 |
43 | #### Review&Merge
44 |
45 | The review phase will enter a multi-review process,`@janryWang` is responsible for reviewing whether this change is merged, and other people will also participate in the discussion. The discussion will be stored in the PR of github, and the DingTalk group will also receive corresponding notifications.
46 |
47 | When you see that the status in the Pull requests list changes to Closed, the merge is successful. 
48 |
49 | #### Synchronize source repository changes to repository after fork
50 |
51 | ```
52 | # First, add "upstream" to your branch, that is, the source repository
53 | $ git remote add upstream https://github.com/alibaba/formily.git
54 | # Get the latest changes to the source repository
55 | $ git fetch upstream
56 | # Synchronize the changes of the source repository to the local branch
57 | $ git pull upstream master [The current local target branch, if not filled in, the current branch will be]
58 | ```
59 |
60 | #### Project Development
61 |
62 | ```bash
63 | $ cd formily
64 | $ yarn install # Install overall project dependencies
65 | $ yarn build # Build all projects
66 | $ yarn test # Perform unit tests
67 | ```
68 |
69 | #### Development Document
70 |
71 | Main project document
72 |
73 | ```bash
74 | $ yarn start
75 | ```
76 |
77 | Core project documentation
78 |
79 | ```bash
80 | $ yarn workspace @formily/core start
81 | ```
82 |
83 | React project documentation
84 |
85 | ```bash
86 | $ yarn workspace @formily/react start
87 | ```
88 |
89 | Vue project documentation
90 |
91 | ```bash
92 | $ yarn workspace @formily/vue start
93 | ```
94 |
95 | Antd project documentation
96 |
97 | ```bash
98 | $ yarn workspace @formily/antd start
99 | ```
100 |
101 | Fusion project documentation
102 |
103 | ```bash
104 | $ yarn workspace @formily/next start
105 | ```
106 |
107 | Reactive project documentation
108 |
109 | ```bash
110 | $ yarn workspace @formily/reactive start
111 | ```
112 |
```
--------------------------------------------------------------------------------
/packages/next/src/array-items/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React from 'react'
2 | import { ArrayField } from '@formily/core'
3 | import {
4 | useField,
5 | observer,
6 | useFieldSchema,
7 | RecursionField,
8 | } from '@formily/react'
9 | import cls from 'classnames'
10 | import {
11 | SortableContainer,
12 | SortableElement,
13 | SortableContainerProps,
14 | SortableElementProps,
15 | } from 'react-sortable-hoc'
16 | import { ISchema } from '@formily/json-schema'
17 | import { usePrefixCls } from '../__builtins__'
18 | import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base'
19 |
20 | type ComposedArrayItems = React.FC<
21 | React.PropsWithChildren<
22 | React.HTMLAttributes<HTMLDivElement> & IArrayBaseProps
23 | >
24 | > &
25 | ArrayBaseMixins & {
26 | Item?: React.FC<
27 | React.HTMLAttributes<HTMLDivElement> & {
28 | type?: 'card' | 'divide'
29 | }
30 | >
31 | }
32 |
33 | const SortableItem: React.FC<
34 | React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>> &
35 | SortableElementProps
36 | > = SortableElement(
37 | (props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => {
38 | const prefixCls = usePrefixCls('formily-array-items')
39 | return (
40 | <div {...props} className={cls(`${prefixCls}-item`, props.className)}>
41 | {props.children}
42 | </div>
43 | )
44 | }
45 | ) as any
46 |
47 | const SortableList: React.FC<
48 | React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>> &
49 | SortableContainerProps
50 | > = SortableContainer(
51 | (props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => {
52 | const prefixCls = usePrefixCls('formily-array-items')
53 | return (
54 | <div {...props} className={cls(`${prefixCls}-list`, props.className)}>
55 | {props.children}
56 | </div>
57 | )
58 | }
59 | ) as any
60 |
61 | const isAdditionComponent = (schema: ISchema) => {
62 | return schema['x-component']?.indexOf('Addition') > -1
63 | }
64 |
65 | const useAddition = () => {
66 | const schema = useFieldSchema()
67 | return schema.reduceProperties((addition, schema, key) => {
68 | if (isAdditionComponent(schema)) {
69 | return <RecursionField schema={schema} name={key} />
70 | }
71 | return addition
72 | }, null)
73 | }
74 |
75 | export const ArrayItems: ComposedArrayItems = observer((props) => {
76 | const field = useField<ArrayField>()
77 | const prefixCls = usePrefixCls('formily-array-items')
78 | const schema = useFieldSchema()
79 | const addition = useAddition()
80 | const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props
81 | const dataSource = Array.isArray(field.value) ? field.value : []
82 | return (
83 | <ArrayBase
84 | onAdd={onAdd}
85 | onCopy={onCopy}
86 | onRemove={onRemove}
87 | onMoveUp={onMoveUp}
88 | onMoveDown={onMoveDown}
89 | >
90 | <div
91 | {...props}
92 | onChange={() => {}}
93 | className={cls(prefixCls, props.className)}
94 | >
95 | <SortableList
96 | useDragHandle
97 | lockAxis="y"
98 | helperClass={`${prefixCls}-sort-helper`}
99 | onSortEnd={({ oldIndex, newIndex }) => {
100 | field.move(oldIndex, newIndex)
101 | }}
102 | >
103 | {dataSource?.map((item, index) => {
104 | const items = Array.isArray(schema.items)
105 | ? schema.items[index] || schema.items[0]
106 | : schema.items
107 | return (
108 | <ArrayBase.Item
109 | key={index}
110 | index={index}
111 | record={() => field.value?.[index]}
112 | >
113 | <SortableItem key={`item-${index}`} index={index}>
114 | <div className={`${prefixCls}-item-inner`}>
115 | <RecursionField schema={items} name={index} />
116 | </div>
117 | </SortableItem>
118 | </ArrayBase.Item>
119 | )
120 | })}
121 | </SortableList>
122 | {addition}
123 | </div>
124 | </ArrayBase>
125 | )
126 | })
127 |
128 | ArrayItems.displayName = 'ArrayItems'
129 |
130 | ArrayItems.Item = (props) => {
131 | const prefixCls = usePrefixCls('formily-array-items')
132 | return (
133 | <div
134 | {...props}
135 | onChange={() => {}}
136 | className={cls(`${prefixCls}-${props.type || 'card'}`, props.className)}
137 | >
138 | {props.children}
139 | </div>
140 | )
141 | }
142 |
143 | ArrayBase.mixin(ArrayItems)
144 |
145 | export default ArrayItems
146 |
```
--------------------------------------------------------------------------------
/packages/react/docs/api/components/ObjectField.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | order: 2
3 | ---
4 |
5 | # ObjectField
6 |
7 | ## Description
8 |
9 | As @formily/core's [createObjectField](https://core.formilyjs.org/api/models/form#createobjectfield) React implementation, it is a bridge component specifically used to bind ViewModel and input controls, ObjectField component Property reference [IFieldFactoryProps](https://core.formilyjs.org/api/models/form#ifieldfactoryprops)
10 |
11 | <Alert>
12 | When we use the ObjectField component, we must remember to pass the name attribute. At the same time, use render props to organize sub-components
13 | </Alert>
14 |
15 | ## Signature
16 |
17 | ```ts
18 | type ObjectField = React.FC<React.PropsWithChildren<IFieldFactoryProps>>
19 | ```
20 |
21 | ## Custom component use case
22 |
23 | ```tsx
24 | import React from 'react'
25 | import { createForm, ObjectField as ObjectFieldType } from '@formily/core'
26 | import {
27 | FormProvider,
28 | Field,
29 | ObjectField,
30 | useField,
31 | observer,
32 | } from '@formily/react'
33 | import { Input, Button, Space } from 'antd'
34 |
35 | const form = createForm()
36 |
37 | const ObjectComponent = observer(() => {
38 | const field = useField<ObjectFieldType>()
39 | return (
40 | <>
41 | <div>
42 | {Object.keys(field.value || {}).map((key) => (
43 | <div key={key} style={{ display: 'flex-block', marginBottom: 10 }}>
44 | <Space>
45 | <Field name={key} component={[Input, { placeholder: key }]} />
46 | <Button
47 | onClick={() => {
48 | field.removeProperty(key)
49 | }}
50 | >
51 | Remove
52 | </Button>
53 | </Space>
54 | </div>
55 | ))}
56 | </div>
57 | <Space>
58 | <Field
59 | name="propertyName"
60 | basePath={''}
61 | required
62 | component={[Input, { placeholder: 'Property Name' }]}
63 | />
64 | <Button
65 | onClick={() => {
66 | const name = form.values.propertyName
67 | if (name && !form.existValuesIn(`${field.path}.${name}`)) {
68 | field.addProperty(name, '')
69 | form.deleteValuesIn('propertyName')
70 | }
71 | }}
72 | >
73 | Add
74 | </Button>
75 | </Space>
76 | </>
77 | )
78 | })
79 |
80 | export default () => (
81 | <FormProvider form={form}>
82 | <ObjectField name="object" component={[ObjectComponent]} />
83 | </FormProvider>
84 | )
85 | ```
86 |
87 | ## RenderProps use cases
88 |
89 | ```tsx
90 | import React from 'react'
91 | import { createForm } from '@formily/core'
92 | import { FormProvider, Field, ObjectField } from '@formily/react'
93 | import { Input, Button, Space } from 'antd'
94 |
95 | const form = createForm()
96 |
97 | export default () => (
98 | <FormProvider form={form}>
99 | <ObjectField name="object">
100 | {(field) => {
101 | return (
102 | <>
103 | <div>
104 | {Object.keys(field.value || {}).map((key) => (
105 | <div
106 | key={key}
107 | style={{ display: 'flex-block', marginBottom: 10 }}
108 | >
109 | <Space>
110 | <Field
111 | name={key}
112 | component={[Input, { placeholder: key }]}
113 | />
114 | <Button
115 | onClick={() => {
116 | field.removeProperty(key)
117 | }}
118 | >
119 | Remove
120 | </Button>
121 | </Space>
122 | </div>
123 | ))}
124 | </div>
125 | <Space>
126 | <Field
127 | name="propertyName"
128 | basePath={''}
129 | required
130 | component={[Input, { placeholder: 'Property Name' }]}
131 | />
132 | <Button
133 | onClick={() => {
134 | const name = form.values.propertyName
135 | if (name && !form.existValuesIn(`${field.path}.${name}`)) {
136 | field.addProperty(name, '')
137 | form.deleteValuesIn('propertyName')
138 | }
139 | }}
140 | >
141 | Add
142 | </Button>
143 | </Space>
144 | </>
145 | )
146 | }}
147 | </ObjectField>
148 | </FormProvider>
149 | )
150 | ```
151 |
```
--------------------------------------------------------------------------------
/packages/vue/src/__tests__/form.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import Vue from 'vue'
2 | import { render, fireEvent } from '@testing-library/vue'
3 | import { mount } from '@vue/test-utils'
4 | import { createForm } from '@formily/core'
5 | import {
6 | FormProvider,
7 | FormConsumer,
8 | Field,
9 | ObjectField,
10 | VoidField,
11 | } from '../vue2-components'
12 | import { defineComponent } from 'vue-demi'
13 | import { useParentForm, useField } from '../hooks'
14 | import { h } from 'vue-demi'
15 |
16 | Vue.component('FormProvider', FormProvider)
17 | Vue.component('FormConsumer', FormConsumer)
18 | Vue.component('ObjectField', ObjectField)
19 | Vue.component('VoidField', VoidField)
20 | Vue.component('Field', Field)
21 |
22 | const Input = defineComponent({
23 | props: ['value'],
24 | setup(props, { attrs, listeners }) {
25 | const fieldRef = useField()
26 | return () => {
27 | const field = fieldRef.value
28 | return h('input', {
29 | class: 'test-input',
30 | attrs: {
31 | ...attrs,
32 | value: props.value,
33 | 'data-testid': field.path.toString(),
34 | },
35 | on: {
36 | ...listeners,
37 | input: listeners.change,
38 | },
39 | })
40 | }
41 | },
42 | })
43 |
44 | test('render form', () => {
45 | const form = createForm()
46 | render({
47 | data() {
48 | return { form }
49 | },
50 | template: `<FormProvider :form="form">
51 | <FormConsumer>
52 | <template #default="{ form }">
53 | {{ form.mounted }}
54 | </template>
55 | </FormConsumer>
56 | <FormConsumer />
57 | </FormProvider>`,
58 | })
59 |
60 | expect(form.mounted).toBeTruthy()
61 | })
62 |
63 | const DisplayParentForm = defineComponent({
64 | setup() {
65 | const form = useParentForm()
66 |
67 | return () => h('div', [form.value.displayName])
68 | },
69 | })
70 |
71 | test('useParentForm', () => {
72 | const { queryByTestId } = render({
73 | components: {
74 | DisplayParentForm,
75 | },
76 | data() {
77 | const form = createForm()
78 | return { form }
79 | },
80 | template: `<FormProvider :form="form">
81 | <ObjectField name="aa">
82 | <Field name="bb">
83 | <DisplayParentForm data-testid="111" />
84 | </Field>
85 | </ObjectField>
86 | <VoidField name="cc">
87 | <Field name="dd">
88 | <DisplayParentForm data-testid="222" />
89 | </Field>
90 | </VoidField>
91 | <DisplayParentForm data-testid="333" />
92 | </FormProvider>`,
93 | })
94 | expect(queryByTestId('111').textContent).toBe('ObjectField')
95 | expect(queryByTestId('222').textContent).toBe('Form')
96 | expect(queryByTestId('333').textContent).toBe('Form')
97 | })
98 |
99 | test('useInjectionCleaner', async () => {
100 | const form = createForm()
101 |
102 | const { getByTestId } = render({
103 | name: 'TestComponent',
104 | setup() {
105 | return {
106 | form,
107 | Input,
108 | }
109 | },
110 | template: `<FormProvider :form="form">
111 | <Field name="parent">
112 | <FormProvider :form="form">
113 | <Field name="inner" :component="[Input]" />
114 | </FormProvider>
115 | <Field name="outer" :component="[Input]" />
116 | </Field>
117 | </FormProvider>`,
118 | })
119 | expect(form.mounted).toBeTruthy()
120 | expect(form.query('inner').take().mounted).toBeTruthy()
121 | expect(form.query('parent.outer').take().mounted).toBeTruthy()
122 | await fireEvent.update(getByTestId('parent.outer'), '123')
123 | expect(form.getValuesIn('parent.outer')).toBe('123')
124 | await fireEvent.update(getByTestId('inner'), '123')
125 | expect(form.getValuesIn('inner')).toBe('123')
126 | })
127 |
128 | test('FormConsumer', async () => {
129 | const form = createForm({
130 | values: {
131 | a: 'abc',
132 | },
133 | })
134 | const wrapper = mount({
135 | data() {
136 | return { form, Input }
137 | },
138 | template: `<FormProvider :form="form">
139 | <Field name="a" :component="[Input]" />
140 | <FormConsumer ref="consumer">
141 | <template #default="{ form }">
142 | <div class="consumer">{{JSON.stringify(form.values)}}</div>
143 | </template>
144 | </FormConsumer>
145 | </FormProvider>`,
146 | })
147 | expect(form.getValuesIn('a')).toBe('abc')
148 | expect(wrapper.find('.consumer').text()).toBe('{"a":"abc"}')
149 | form.setDisplay('none')
150 | expect(form.getValuesIn('a')).toBeUndefined()
151 | const $consumer = wrapper.vm.$refs.consumer as Vue
152 | $consumer.$forceUpdate()
153 | expect(wrapper.find('.consumer').text()).toBe('{}')
154 | })
155 |
```
--------------------------------------------------------------------------------
/packages/element/src/form-grid/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Grid, IGridOptions } from '@formily/grid'
2 | import { markRaw } from '@formily/reactive'
3 | import { observer } from '@formily/reactive-vue'
4 | import { h } from '@formily/vue'
5 | import {
6 | computed,
7 | defineComponent,
8 | inject,
9 | InjectionKey,
10 | onMounted,
11 | PropType,
12 | provide,
13 | ref,
14 | Ref,
15 | watchEffect,
16 | } from 'vue-demi'
17 | import { useFormLayout } from '../form-layout'
18 | import { stylePrefix } from '../__builtins__/configs'
19 | import { composeExport } from '../__builtins__/shared'
20 |
21 | export interface IFormGridProps extends IGridOptions {
22 | grid?: Grid<HTMLElement>
23 | prefixCls?: string
24 | className?: string
25 | style?: React.CSSProperties
26 | }
27 |
28 | const FormGridSymbol: InjectionKey<Ref<Grid<HTMLElement>>> =
29 | Symbol('FormGridContext')
30 |
31 | interface GridColumnProps {
32 | gridSpan: number
33 | }
34 |
35 | export const createFormGrid = (props: IFormGridProps): Grid<HTMLElement> => {
36 | return markRaw(new Grid(props))
37 | }
38 |
39 | export const useFormGrid = (): Ref<Grid<HTMLElement>> => inject(FormGridSymbol)
40 |
41 | /**
42 | * @deprecated
43 | */
44 | const useGridSpan = (gridSpan: number) => {
45 | return gridSpan
46 | }
47 |
48 | /**
49 | * @deprecated
50 | */
51 | export const useGridColumn = (gridSpan = 1) => {
52 | return gridSpan
53 | }
54 |
55 | const FormGridInner = observer(
56 | defineComponent({
57 | name: 'FFormGrid',
58 | props: {
59 | columnGap: {
60 | type: Number,
61 | },
62 | rowGap: {
63 | type: Number,
64 | },
65 | minColumns: {
66 | type: [Number, Array],
67 | },
68 | minWidth: {
69 | type: [Number, Array],
70 | },
71 | maxColumns: {
72 | type: [Number, Array],
73 | },
74 | maxWidth: {
75 | type: [Number, Array],
76 | },
77 | breakpoints: {
78 | type: Array,
79 | },
80 | colWrap: {
81 | type: Boolean,
82 | default: true,
83 | },
84 | strictAutoFit: {
85 | type: Boolean,
86 | default: false,
87 | },
88 | shouldVisible: {
89 | type: Function as PropType<IGridOptions['shouldVisible']>,
90 | default() {
91 | return () => true
92 | },
93 | },
94 | grid: {
95 | type: Object as PropType<Grid<HTMLElement>>,
96 | },
97 | },
98 | setup(props: IFormGridProps) {
99 | const layout = useFormLayout()
100 |
101 | const gridInstance = computed(() => {
102 | const newProps: IFormGridProps = {}
103 | Object.keys(props).forEach((key) => {
104 | if (typeof props[key] !== 'undefined') {
105 | newProps[key] = props[key]
106 | }
107 | })
108 | const options = {
109 | columnGap: layout.value?.gridColumnGap ?? 8,
110 | rowGap: layout.value?.gridRowGap ?? 4,
111 | ...newProps,
112 | }
113 | return markRaw(options?.grid ? options.grid : new Grid(options))
114 | })
115 | const prefixCls = `${stylePrefix}-form-grid`
116 | const root = ref(null)
117 |
118 | provide(FormGridSymbol, gridInstance)
119 |
120 | onMounted(() => {
121 | watchEffect((onInvalidate) => {
122 | const dispose = gridInstance.value.connect(root.value)
123 | onInvalidate(() => {
124 | dispose()
125 | })
126 | })
127 | })
128 |
129 | return {
130 | prefixCls,
131 | root,
132 | gridInstance,
133 | }
134 | },
135 | render() {
136 | const { prefixCls, gridInstance } = this
137 | return h(
138 | 'div',
139 | {
140 | attrs: {
141 | class: `${prefixCls}`,
142 | },
143 | style: {
144 | gridTemplateColumns: gridInstance.templateColumns,
145 | gap: gridInstance.gap,
146 | },
147 | ref: 'root',
148 | },
149 | {
150 | default: () => this.$slots.default,
151 | }
152 | )
153 | },
154 | })
155 | ) as any
156 |
157 | const FormGridColumn = observer(
158 | defineComponent({
159 | name: 'FFormGridColumn',
160 | props: {
161 | gridSpan: {
162 | type: Number,
163 | default: 1,
164 | },
165 | },
166 | setup(props: GridColumnProps, { slots }) {
167 | return () => {
168 | return h(
169 | 'div',
170 | {
171 | attrs: {
172 | 'data-grid-span': props.gridSpan,
173 | },
174 | },
175 | slots
176 | )
177 | }
178 | },
179 | })
180 | )
181 |
182 | export const FormGrid = composeExport(FormGridInner, {
183 | GridColumn: FormGridColumn,
184 | useGridSpan,
185 | useFormGrid,
186 | createFormGrid,
187 | })
188 |
189 | export default FormGrid
190 |
```
--------------------------------------------------------------------------------
/docs/guide/advanced/business-logic.md:
--------------------------------------------------------------------------------
```markdown
1 | # Manage Business Logic
2 |
3 | In the previous document, we can actually find that Formily has provided the ability to describe the logic locally, that is, the x-reactions/reactions property of the field component. And in Schema, x-reactions can pass both functions and a structured object. Of course, there are also effects inherited from Formily 1.x, So to summarize, the ways to describe logic in Formily 2.x are:
4 |
5 | - Effects or reactions property in pure JSX mode
6 | - Effects or structured x-reactions property in Schema mode
7 | - Effects or functional x-reactions property in Schema mode
8 |
9 | With so many ways of describing logic, how should we choose? What scenarios are best practices? First, we need to understand the positioning of effects and reactions.
10 |
11 | First of all, reactions are responders used on specific field properties. They will be executed repeatedly based on the data changes that the function depends on. Its biggest advantage is that it is simple, straightforward and easy to understand, such as:
12 |
13 | ```tsx pure
14 | /* eslint-disable */
15 | <Field
16 | name="A"
17 | reactions={(field) => {
18 | /**specific logic implementation**/
19 | }}
20 | />
21 | ```
22 |
23 | Then, effects are used to implement the side-effect isolation logic management model. Its biggest advantage is that it can make the view code easier to maintain in a scenario with a large number of fields. At the same time, it also has the ability to process fields in batches. For example, we declare x-reactions in the field properties of A, B, C. If the x-reactions logic of these three fields are exactly the same, then we only need to write this in effects:
24 |
25 | ```ts
26 | onFieldReact('*(A,B,C)', (field) => {
27 | //...logic
28 | })
29 | ```
30 |
31 | Another advantage of using effects is that a series of reusable logic plug-ins can be implemented, which can be very convenient logic pluggable, and at the same time can do some things like global monitoring.
32 |
33 | In this way, do we not need to define the logic locally?
34 |
35 | No, the premise of the above writing is that for a large number of fields, if the view layer is full of reactions, it looks uncomfortable, so it is a better strategy to consider extracting logic from unified maintenance.
36 | On the contrary, if the number of fields is small and the logic is relatively simple, it is also good to write reactions directly on the field attributes, which is clear.
37 |
38 | At the same time, because JSON Schema can be consumed by the configuration system, we need to logically configure a specific field on the configuration interface. So we still need to support local definition logic capabilities, and also need to support structured description logic, such as:
39 |
40 | ```json
41 | {
42 | "x-reactions": {
43 | "dependencies": ["aa"],
44 | "fulfill": {
45 | "state": {
46 | "visible": "{{$deps[0] == '123'}}"
47 | }
48 | }
49 | }
50 | }
51 | ```
52 |
53 | This can well solve the linkage requirements of most configuration scenarios. However, there is another scenario, that is, our linkage process is asynchronous, the logic is very complicated, or there is a large amount of data processing, then we can only consider open up the ability to describe functional states, such as:
54 |
55 | ```json
56 | {
57 | "x-reactions": "{{(field)=>{/**specific logic implementation**/}}}"
58 | }
59 | ```
60 |
61 | This is very similar to a low-code configuration. Of course, we can also register a series of general logic functions in the context scope:
62 |
63 | ```json
64 | {
65 | "x-reactions": "{{customFunction}}"
66 | }
67 | ```
68 |
69 | In conclusion, the way we manage business logic has the following priorities:
70 |
71 | - Pure source mode
72 | - The number of fields is huge and the logic is complex, and the logic defined in effects is preferred.
73 | - The number of fields is small, the logic is simple, and the logic defined in reactions is preferred
74 | - Schema mode
75 | - There is no asynchronous logic, structured reactions are preferred to define logic.
76 | - There is asynchronous logic, or a large number of calculations, the functional state reactions are preferred to define logic.
77 |
78 | For how to play with effects in effects, we mainly look at the [@formily/core](https://core.formilyjs.org) document.
79 |
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/form-grid/native.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <div>
3 | <p>maxColumns 3 + minColumns 2</p>
4 | <FormGrid :maxColumns="3" :minColumns="2" :columnGap="4">
5 | <FormGridColumn :gridSpan="4">
6 | <Cell>1</Cell>
7 | </FormGridColumn>
8 | <FormGridColumn>
9 | <Cell>2</Cell>
10 | </FormGridColumn>
11 | <FormGridColumn>
12 | <Cell>3</Cell>
13 | </FormGridColumn>
14 | <FormGridColumn>
15 | <Cell>4</Cell>
16 | </FormGridColumn>
17 | <FormGridColumn>
18 | <Cell>5</Cell>
19 | </FormGridColumn>
20 | <FormGridColumn>
21 | <Cell>6</Cell>
22 | </FormGridColumn>
23 | </FormGrid>
24 | <p>maxColumns 3</p>
25 | <FormGrid :maxColumns="3" :columnGap="4">
26 | <FormGridColumn :gridSpan="2">
27 | <Cell>1</Cell>
28 | </FormGridColumn>
29 | <FormGridColumn>
30 | <Cell>2</Cell>
31 | </FormGridColumn>
32 | <FormGridColumn>
33 | <Cell>3</Cell>
34 | </FormGridColumn>
35 | <FormGridColumn>
36 | <Cell>4</Cell>
37 | </FormGridColumn>
38 | <FormGridColumn>
39 | <Cell>5</Cell>
40 | </FormGridColumn>
41 | <FormGridColumn>
42 | <Cell>6</Cell>
43 | </FormGridColumn>
44 | </FormGrid>
45 | <p>minColumns 2</p>
46 | <FormGrid :minColumns="2" :columnGap="4">
47 | <FormGridColumn :gridSpan="2">
48 | <Cell>1</Cell>
49 | </FormGridColumn>
50 | <FormGridColumn>
51 | <Cell>2</Cell>
52 | </FormGridColumn>
53 | <FormGridColumn>
54 | <Cell>3</Cell>
55 | </FormGridColumn>
56 | <FormGridColumn>
57 | <Cell>4</Cell>
58 | </FormGridColumn>
59 | <FormGridColumn>
60 | <Cell>5</Cell>
61 | </FormGridColumn>
62 | <FormGridColumn>
63 | <Cell>6</Cell>
64 | </FormGridColumn>
65 | </FormGrid>
66 | <p>Null</p>
67 | <FormGrid :columnGap="4">
68 | <FormGridColumn :gridSpan="2">
69 | <Cell>1</Cell>
70 | </FormGridColumn>
71 | <FormGridColumn>
72 | <Cell>2</Cell>
73 | </FormGridColumn>
74 | <FormGridColumn>
75 | <Cell>3</Cell>
76 | </FormGridColumn>
77 | <FormGridColumn>
78 | <Cell>4</Cell>
79 | </FormGridColumn>
80 | <FormGridColumn>
81 | <Cell>5</Cell>
82 | </FormGridColumn>
83 | <FormGridColumn>
84 | <Cell>6</Cell>
85 | </FormGridColumn>
86 | </FormGrid>
87 | <p>minWidth 150 +maxColumns 3</p>
88 | <FormGrid :minWidth="150" :maxColumns="3" :columnGap="4">
89 | <FormGridColumn :gridSpan="2">
90 | <Cell>1</Cell>
91 | </FormGridColumn>
92 | <FormGridColumn>
93 | <Cell>2</Cell>
94 | </FormGridColumn>
95 | <FormGridColumn>
96 | <Cell>3</Cell>
97 | </FormGridColumn>
98 | <FormGridColumn>
99 | <Cell>4</Cell>
100 | </FormGridColumn>
101 | <FormGridColumn>
102 | <Cell>5</Cell>
103 | </FormGridColumn>
104 | <FormGridColumn>
105 | <Cell>6</Cell>
106 | </FormGridColumn>
107 | </FormGrid>
108 | <p>maxWidth 120+minColumns 2</p>
109 | <FormGrid :maxWidth="120" :minColumns="2" :columnGap="4">
110 | <FormGridColumn :gridSpan="2">
111 | <Cell>1</Cell>
112 | </FormGridColumn>
113 | <FormGridColumn>
114 | <Cell>2</Cell>
115 | </FormGridColumn>
116 | <FormGridColumn>
117 | <Cell>3</Cell>
118 | </FormGridColumn>
119 | <FormGridColumn>
120 | <Cell>4</Cell>
121 | </FormGridColumn>
122 | <FormGridColumn>
123 | <Cell>5</Cell>
124 | </FormGridColumn>
125 | <FormGridColumn>
126 | <Cell>6</Cell>
127 | </FormGridColumn>
128 | </FormGrid>
129 | <p>maxWidth 120 + gridSpan -1</p>
130 | <FormGrid :maxWidth="120" :columnGap="4">
131 | <FormGridColumn :gridSpan="2">
132 | <Cell>1</Cell>
133 | </FormGridColumn>
134 | <FormGridColumn>
135 | <Cell>2</Cell>
136 | </FormGridColumn>
137 | <FormGridColumn :gridSpan="-1">
138 | <Cell>3</Cell>
139 | </FormGridColumn>
140 | </FormGrid>
141 | </div>
142 | </template>
143 |
144 | <script>
145 | import { FormGrid } from '@formily/element'
146 |
147 | const Cell = {
148 | functional: true,
149 | render(h, context) {
150 | return h(
151 | 'div',
152 | {
153 | style: {
154 | backgroundColor: '#AAA',
155 | color: '#FFF',
156 | height: '30px',
157 | display: 'flex',
158 | alignItems: 'center',
159 | padding: '0 10px',
160 | },
161 | },
162 | context.children
163 | )
164 | },
165 | }
166 |
167 | export default {
168 | components: { FormGrid, FormGridColumn: FormGrid.GridColumn, Cell },
169 | }
170 | </script>
171 |
```
--------------------------------------------------------------------------------
/packages/benchmark/src/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { useMemo, useState } from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { createForm } from '@formily/core'
4 | import { Field, createSchemaField } from '@formily/react'
5 | import { Input, Form, FormItem } from '@formily/antd'
6 | import { Form as AntdForm, Input as AntdInput } from 'antd'
7 | const SchemaField = createSchemaField({
8 | components: {
9 | Input,
10 | FormItem,
11 | },
12 | })
13 |
14 | const PureAntdInput = () => {
15 | return (
16 | <>
17 | {Array.from({ length: 2000 }).map((_, i) => {
18 | return <AntdInput key={i} />
19 | })}
20 | </>
21 | )
22 | }
23 |
24 | const PureAntd = () => {
25 | return (
26 | <AntdForm>
27 | <h1>Please pay attention to the performance of the form input</h1>
28 | {Array.from({ length: 2000 }).map((_, i) => {
29 | return (
30 | <AntdForm.Item
31 | key={i}
32 | name={`name_${i}`}
33 | required
34 | label={`name ${i + 1}`}
35 | >
36 | <AntdInput />
37 | </AntdForm.Item>
38 | )
39 | })}
40 | </AntdForm>
41 | )
42 | }
43 |
44 | const PureJSX = () => {
45 | const form = useMemo(() => createForm(), [])
46 | return (
47 | <Form form={form}>
48 | <h1>Please pay attention to the performance of the form input</h1>
49 | {Array.from({ length: 2000 }).map((_, i) => {
50 | return (
51 | <Field
52 | key={i}
53 | name={`name_${i}`}
54 | title={`name ${i + 1}`}
55 | required
56 | decorator={[FormItem]}
57 | component={[Input, { placeholder: 'Please Input' }]}
58 | />
59 | )
60 | })}
61 | </Form>
62 | )
63 | }
64 |
65 | const PureJSONSchema = () => {
66 | const form = useMemo(() => createForm(), [])
67 | const schema = {
68 | type: 'object',
69 | properties: {},
70 | }
71 | Array.from({ length: 2000 }).forEach((_, i) => {
72 | schema.properties[`name_${i}`] = {
73 | type: 'string',
74 | title: `name ${i + 1}`,
75 | 'x-decorator': 'FormItem',
76 | 'x-component': 'Input',
77 | 'x-component-props': {
78 | placeholder: 'Please Input',
79 | },
80 | }
81 | })
82 | return (
83 | <Form form={form}>
84 | <h1>Please pay attention to the performance of the form input</h1>
85 | <SchemaField schema={schema} />
86 | </Form>
87 | )
88 | }
89 |
90 | const PureMarkupSchema = () => {
91 | const form = useMemo(() => createForm(), [])
92 | return (
93 | <Form form={form}>
94 | <h1>Please pay attention to the performance of the form input</h1>
95 | <SchemaField>
96 | {Array.from({ length: 2000 }).map((_, i) => {
97 | return (
98 | <SchemaField.String
99 | key={i}
100 | name={`name_${i}`}
101 | title={`name ${i + 1}`}
102 | required
103 | x-decorator="FormItem"
104 | x-component="Input"
105 | x-component-props={{
106 | placeholder: 'Please Input',
107 | }}
108 | />
109 | )
110 | })}
111 | </SchemaField>
112 | </Form>
113 | )
114 | }
115 |
116 | const App = () => {
117 | const [visibleAntd, setVisibleAntd] = useState(false)
118 | const [visibleJSX, setVisibleJSX] = useState(false)
119 | const [visibleMarkupSchema, setVisibleMarkupSchema] = useState(false)
120 | const [visibleJSONSchema, setVisibleJSONSchema] = useState(false)
121 | const [visibleAntdInput, setVisibleAntdInput] = useState(false)
122 | return (
123 | <div>
124 | <button
125 | onClick={() => {
126 | setVisibleJSX(!visibleJSX)
127 | }}
128 | >
129 | Show JSX
130 | </button>
131 | <button
132 | onClick={() => {
133 | setVisibleMarkupSchema(!visibleMarkupSchema)
134 | }}
135 | >
136 | Show Markup Schema
137 | </button>
138 | <button
139 | onClick={() => {
140 | setVisibleJSONSchema(!visibleJSONSchema)
141 | }}
142 | >
143 | Show JSON Schema
144 | </button>
145 | <button
146 | onClick={() => {
147 | setVisibleAntd(!visibleAntd)
148 | }}
149 | >
150 | Show Antd
151 | </button>
152 | <button
153 | onClick={() => {
154 | setVisibleAntdInput(!visibleAntdInput)
155 | }}
156 | >
157 | Show Antd Input
158 | </button>
159 | {visibleJSX && <PureJSX />}
160 | {visibleMarkupSchema && <PureMarkupSchema />}
161 | {visibleJSONSchema && <PureJSONSchema />}
162 | {visibleAntd && <PureAntd />}
163 | {visibleAntdInput && <PureAntdInput />}
164 | </div>
165 | )
166 | }
167 | ReactDOM.render(<App />, document.getElementById('root'))
168 |
```
--------------------------------------------------------------------------------
/packages/element/src/form-layout/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { h } from '@formily/vue'
2 | import {
3 | defineComponent,
4 | inject,
5 | InjectionKey,
6 | provide,
7 | Ref,
8 | ref,
9 | watch,
10 | } from 'vue-demi'
11 | import { stylePrefix } from '../__builtins__/configs'
12 | import { useCompatRef } from '../__builtins__/shared'
13 | import { useResponsiveFormLayout } from './useResponsiveFormLayout'
14 |
15 | export type FormLayoutProps = {
16 | className?: string
17 | colon?: boolean
18 | labelAlign?: 'right' | 'left' | ('right' | 'left')[]
19 | wrapperAlign?: 'right' | 'left' | ('right' | 'left')[]
20 | labelWrap?: boolean
21 | labelWidth?: number
22 | wrapperWidth?: number
23 | wrapperWrap?: boolean
24 | labelCol?: number | number[]
25 | wrapperCol?: number | number[]
26 | fullness?: boolean
27 | size?: 'small' | 'default' | 'large'
28 | layout?:
29 | | 'vertical'
30 | | 'horizontal'
31 | | 'inline'
32 | | ('vertical' | 'horizontal' | 'inline')[]
33 | direction?: 'rtl' | 'ltr'
34 | shallow?: boolean
35 | feedbackLayout?: 'loose' | 'terse' | 'popover'
36 | tooltipLayout?: 'icon' | 'text'
37 | bordered?: boolean
38 | breakpoints?: number[]
39 | inset?: boolean
40 | spaceGap?: number
41 | gridColumnGap?: number
42 | gridRowGap?: number
43 | }
44 |
45 | export const FormLayoutDeepContext: InjectionKey<Ref<FormLayoutProps>> = Symbol(
46 | 'FormLayoutDeepContext'
47 | )
48 |
49 | export const FormLayoutShallowContext: InjectionKey<Ref<FormLayoutProps>> =
50 | Symbol('FormLayoutShallowContext')
51 |
52 | export const useFormDeepLayout = (): Ref<FormLayoutProps> =>
53 | inject(FormLayoutDeepContext, ref({}))
54 |
55 | export const useFormShallowLayout = (): Ref<FormLayoutProps> =>
56 | inject(FormLayoutShallowContext, ref({}))
57 |
58 | export const useFormLayout = (): Ref<FormLayoutProps> => {
59 | const shallowLayout = useFormShallowLayout()
60 | const deepLayout = useFormDeepLayout()
61 | const formLayout = ref({
62 | ...deepLayout.value,
63 | ...shallowLayout.value,
64 | })
65 |
66 | watch(
67 | [shallowLayout, deepLayout],
68 | () => {
69 | formLayout.value = {
70 | ...deepLayout.value,
71 | ...shallowLayout.value,
72 | }
73 | },
74 | {
75 | deep: true,
76 | }
77 | )
78 | return formLayout
79 | }
80 |
81 | export const FormLayout = defineComponent<FormLayoutProps>({
82 | name: 'FFormLayout',
83 | props: {
84 | className: {},
85 | colon: { default: true },
86 | labelAlign: {},
87 | wrapperAlign: {},
88 | labelWrap: { default: false },
89 | labelWidth: {},
90 | wrapperWidth: {},
91 | wrapperWrap: { default: false },
92 | labelCol: {},
93 | wrapperCol: {},
94 | fullness: { default: false },
95 | size: { default: 'default' },
96 | layout: { default: 'horizontal' },
97 | direction: { default: 'ltr' },
98 | shallow: { default: true },
99 | feedbackLayout: {},
100 | tooltipLayout: {},
101 | bordered: { default: true },
102 | inset: { default: false },
103 | breakpoints: {},
104 | spaceGap: {},
105 | gridColumnGap: {},
106 | gridRowGap: {},
107 | },
108 | setup(customProps, { slots, refs }) {
109 | const { elRef: root, elRefBinder } = useCompatRef(refs)
110 | const { props } = useResponsiveFormLayout(customProps, root)
111 |
112 | const deepLayout = useFormDeepLayout()
113 | const newDeepLayout = ref({
114 | ...deepLayout,
115 | })
116 | const shallowProps = ref({})
117 |
118 | watch(
119 | [props, deepLayout],
120 | () => {
121 | shallowProps.value = props.value.shallow ? props.value : undefined
122 | if (!props.value.shallow) {
123 | Object.assign(newDeepLayout.value, props.value)
124 | } else {
125 | if (props.value.size) {
126 | newDeepLayout.value.size = props.value.size
127 | }
128 | if (props.value.colon) {
129 | newDeepLayout.value.colon = props.value.colon
130 | }
131 | }
132 | },
133 | { deep: true, immediate: true }
134 | )
135 |
136 | provide(FormLayoutDeepContext, newDeepLayout)
137 | provide(FormLayoutShallowContext, shallowProps)
138 |
139 | const formPrefixCls = `${stylePrefix}-form`
140 | return () => {
141 | const classNames = {
142 | [`${formPrefixCls}-${props.value.layout}`]: true,
143 | [`${formPrefixCls}-rtl`]: props.value.direction === 'rtl',
144 | [`${formPrefixCls}-${props.value.size}`]:
145 | props.value.size !== undefined,
146 | [`${props.value.className}`]: props.value.className !== undefined,
147 | }
148 | return h(
149 | 'div',
150 | {
151 | ref: elRefBinder,
152 | class: classNames,
153 | },
154 | slots
155 | )
156 | }
157 | },
158 | })
159 |
160 | export default FormLayout
161 |
```
--------------------------------------------------------------------------------
/packages/element/src/array-tabs/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ArrayField } from '@formily/core'
2 | import { observer } from '@formily/reactive-vue'
3 | import { h, RecursionField, useField, useFieldSchema } from '@formily/vue'
4 | import { Badge, TabPane, Tabs } from 'element-ui'
5 | import { defineComponent, ref } from 'vue-demi'
6 | import { stylePrefix } from '../__builtins__/configs'
7 |
8 | import type { Tabs as TabsProps } from 'element-ui'
9 |
10 | export const ArrayTabs = observer(
11 | defineComponent<TabsProps>({
12 | name: 'ArrayTabs',
13 | props: [],
14 | setup(props, { attrs, listeners }) {
15 | const fieldRef = useField<ArrayField>()
16 | const schemaRef = useFieldSchema()
17 |
18 | const prefixCls = `${stylePrefix}-array-tabs`
19 | const activeKey = ref('tab-0')
20 |
21 | return () => {
22 | const field = fieldRef.value
23 | const schema = schemaRef.value
24 | const value = Array.isArray(field.value) ? field.value : []
25 | const dataSource = value?.length ? value : [{}]
26 |
27 | const onEdit = (targetKey: any, type: 'add' | 'remove') => {
28 | if (type == 'add') {
29 | const id = dataSource.length
30 | if (field?.value?.length) {
31 | field.push(null)
32 | } else {
33 | field.push(null, null)
34 | }
35 | activeKey.value = `tab-${id}`
36 | } else if (type == 'remove') {
37 | const index = targetKey.match(/-(\d+)/)?.[1]
38 | field.remove(Number(index))
39 | if (activeKey.value === targetKey) {
40 | activeKey.value = `tab-${index - 1}`
41 | }
42 | }
43 | }
44 |
45 | const badgedTab = (index: number) => {
46 | const tab = `${field.title || 'Untitled'} ${index + 1}`
47 | const path = field.address.concat(index)
48 | const errors = field.form.queryFeedbacks({
49 | type: 'error',
50 | address: `${path}.**`,
51 | })
52 | if (errors.length) {
53 | return h(
54 | 'span',
55 | {},
56 | {
57 | default: () => [
58 | h(
59 | Badge,
60 | {
61 | class: [`${prefixCls}-errors-badge`],
62 | props: {
63 | value: errors.length,
64 | },
65 | },
66 | {
67 | default: () => [tab],
68 | }
69 | ),
70 | ],
71 | }
72 | )
73 | }
74 | return h(
75 | 'span',
76 | {},
77 | {
78 | default: () => [tab],
79 | }
80 | )
81 | }
82 |
83 | const renderItems = () =>
84 | dataSource?.map((item, index) => {
85 | const items = Array.isArray(schema.items)
86 | ? schema.items[index]
87 | : schema.items
88 | const key = `tab-${index}`
89 |
90 | return h(
91 | TabPane,
92 | {
93 | key,
94 | attrs: {
95 | closable: index !== 0,
96 | name: key,
97 | },
98 | },
99 | {
100 | default: () =>
101 | h(
102 | RecursionField,
103 | {
104 | props: {
105 | schema: items,
106 | name: index,
107 | },
108 | },
109 | {}
110 | ),
111 |
112 | label: () => [badgedTab(index)],
113 | }
114 | )
115 | })
116 | return h(
117 | Tabs,
118 | {
119 | class: [prefixCls],
120 | attrs: {
121 | ...attrs,
122 | type: 'card',
123 | value: activeKey.value,
124 | addable: true,
125 | },
126 | on: {
127 | ...listeners,
128 | input: (key) => {
129 | activeKey.value = key
130 | },
131 | 'tab-remove': (target) => {
132 | onEdit(target, 'remove')
133 | listeners?.['tab-remove']?.(target)
134 | },
135 | 'tab-add': () => {
136 | onEdit(null, 'add')
137 | listeners?.['tab-add']?.()
138 | },
139 | },
140 | },
141 | {
142 | default: () => [renderItems()],
143 | }
144 | )
145 | }
146 | },
147 | })
148 | )
149 |
150 | export default ArrayTabs
151 |
```
--------------------------------------------------------------------------------
/packages/next/docs/components/index.md:
--------------------------------------------------------------------------------
```markdown
1 | # Alibaba Fusion
2 |
3 | ## Introduction
4 |
5 | @formily/next is a professional component library for form scenarios based on Fusion Design encapsulation. It has the following characteristics:
6 |
7 | - Only Formily 2.x is supported
8 | - Most components are not backward compatible
9 | - Unfortunately, many components of 1.x have inherent flaws in the API design. This is also because the form scheme has been explored, so there will be version breaks.
10 | - Richer component system
11 | - Layout components
12 | - FormLayout
13 | - FormItem
14 | - FormGrid
15 | - FormButtonGroup
16 | - Space
17 | - Submit
18 | - Reset
19 | - Input controls
20 | - Input
21 | - Password
22 | - Select
23 | - TreeSelect
24 | - DatePicker
25 | - TimePicker
26 | - NumberPicker
27 | - Transfer
28 | - Cascader
29 | - Radio
30 | - Checkbox
31 | - Upload
32 | - Switch
33 | - Scene components
34 | - ArrayCards
35 | - ArrayItems
36 | - ArrayTable
37 | - FormCollapse
38 | - FormStep
39 | - FormTab
40 | - FormDialog
41 | - FormDrawer
42 | - Editable
43 | - LogicDiagram
44 | - Reading state component
45 | - PreviewText
46 | - Theme customization ability
47 | - Completely abandon the 1.x styled-components solution, follow the style system of the component library, it is more convenient to customize the theme
48 | - Support secondary packaging
49 | - All components can be repackaged, and the 1.x component system cannot be repackaged, so providing this capability makes it more convenient for users to do business customization
50 | - Support reading mode
51 | - Although 1.x also supports reading mode, 2.x provides a separate PreviewText component, users can make reading mode encapsulation based on it, which is more flexible
52 | - Type is more friendly
53 | - Each component has an extremely complete type definition, and users can feel an unprecedented intelligent reminder experience during the actual development process
54 | - More complete layout control capabilities
55 | - 1.x's layout capabilities have basically converged to FormMegaLayout. This time, we directly removed Mega. Mega is a standard component and is completely internalized into FormLayout and FormItem components. At the same time, MegaLayout's grid layout capabilities are placed in FormGrid components. In, it also provides smarter layout capabilities.
56 | - More elegant and easy-to-use APIs, such as:
57 | - FormStep in the past has many problems. First, the type is not friendly. Second, the API is too hidden. To control the forward and backwards, you need to understand a bunch of private events. In the new version of FormStep, users only need to pay attention to the FormStep Reactive Model. You can create a Reactive Model through createFormStep and pass it to the FormStep component to quickly communicate. Similarly, FormTab/FormCollapse is the same communication mode.
58 | - Pop-up forms, drawer forms, presumably in the past, users had to write a lot of code on these two scenarios almost every time. This time, an extremely simple API is directly provided for users to use, which maximizes development efficiency.
59 |
60 | ## Note
61 |
62 | Because Fusion is built on Sass, if you use Webpack configuration, please use the following two Sass tools
63 |
64 | ```
65 | "sass": "^1.32.11",
66 | "sass-loader": "^8.0.2"
67 | ```
68 |
69 | ## Installation
70 |
71 | ```bash
72 | $ npm install --save @alifd/next moment
73 | $ npm install --save @formily/next @formily/react
74 |
75 | ```
76 |
77 | ## Q/A
78 |
79 | Q: I want to package a set of component libraries by myself, what should I do?
80 |
81 | Answer: If it is an open source component library, you can directly participate in the project co-construction and provide PR. If it is a private component library in the enterprise, you can refer to the source code. The source code does not have too much complicated logic.
82 |
83 | Question: Why do components such as ArrayCards/ArrayTable/FormStep only support Schema mode and not pure JSX mode?
84 |
85 | Answer: This is the core advantage of Schema mode. With the help of protocols, we can do scene-based abstraction. On the contrary, pure JSX mode is limited by the unparseability of JSX. It is difficult for us to achieve UI-level scene-based abstraction. It's just an abstract hook.
86 |
87 | Q: Why is there no ArrayTabs component?
88 |
89 | Answer: Because Fusion's Tab component does not support the ability to add Tabs, the ArrayTabs component is temporarily not supported.
90 |
```