This is page 11 of 35. Use http://codebase.md/alibaba/formily?lines=false&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/next/src/form-step/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { Fragment } from 'react'
import { define, observable, model, markRaw, action } from '@formily/reactive'
import cls from 'classnames'
import {
StepProps as StepsProps,
ItemProps as StepProps,
} from '@alifd/next/lib/step'
import { Form, VoidField } from '@formily/core'
import {
connect,
useField,
observer,
useFieldSchema,
RecursionField,
} from '@formily/react'
import { Schema, SchemaKey } from '@formily/json-schema'
import { Step as Steps } from '@alifd/next'
import { usePrefixCls } from '../__builtins__'
export interface IFormStep {
connect: (steps: SchemaStep[], field: VoidField) => void
current: number
allowNext: boolean
allowBack: boolean
setCurrent(key: number): void
submit: Form['submit']
next(): void
back(): void
}
export interface IFormStepProps extends StepsProps {
formStep?: IFormStep
}
type ComposedFormTab = React.FC<React.PropsWithChildren<IFormStepProps>> & {
StepPane?: React.FC<React.PropsWithChildren<StepProps>>
createFormStep?: (defaultCurrent?: number) => IFormStep
}
type SchemaStep = {
name: SchemaKey
props: any
schema: Schema
}
type FormStepEnv = {
form: Form
field: VoidField
steps: SchemaStep[]
}
const parseSteps = (schema: Schema) => {
const steps: SchemaStep[] = []
schema.mapProperties((schema, name) => {
if (schema['x-component']?.indexOf('StepPane') > -1) {
steps.push({
name,
props: schema['x-component-props'],
schema,
})
}
})
return steps
}
const createFormStep = (defaultCurrent = 0): IFormStep => {
const env: FormStepEnv = define(
{
form: null,
field: null,
steps: [],
},
{
form: observable.ref,
field: observable.ref,
steps: observable.shallow,
}
)
const setDisplay = action.bound((target: number) => {
const currentStep = env.steps[target]
env.steps.forEach(({ name }) => {
env.form.query(`${env.field.address}.${name}`).take((field) => {
if (name === currentStep.name) {
field.setDisplay('visible')
} else {
field.setDisplay('hidden')
}
})
})
})
const next = () => {
if (formStep.allowNext) {
formStep.setCurrent(formStep.current + 1)
}
}
const back = () => {
if (formStep.allowBack) {
formStep.setCurrent(formStep.current - 1)
}
}
const formStep: IFormStep = model({
connect(steps, field) {
env.steps = steps
env.form = field?.form
env.field = field
},
current: defaultCurrent,
setCurrent(key: number) {
setDisplay(key)
formStep.current = key
},
get allowNext() {
return formStep.current < env.steps.length - 1
},
get allowBack() {
return formStep.current > 0
},
async next() {
try {
await env.form.validate()
if (env.form.valid) {
next()
}
} catch {}
},
async back() {
back()
},
async submit(onSubmit) {
return env.form?.submit?.(onSubmit)
},
})
return markRaw(formStep)
}
export const FormStep: ComposedFormTab = connect(
observer(({ formStep, className, ...props }: IFormStepProps) => {
const field = useField<VoidField>()
const prefixCls = usePrefixCls('formily-step', props)
const schema = useFieldSchema()
const steps = parseSteps(schema)
const current = props.current || formStep?.current || 0
formStep?.connect?.(steps, field)
return (
<div className={cls(prefixCls, className)}>
<Steps
{...props}
style={{ marginBottom: 10, ...props.style }}
current={current}
>
{steps.map(({ props }, key) => {
return <Steps.Item {...props} key={key} />
})}
</Steps>
{steps.map(({ name, schema }, key) => {
if (key !== current) return
return <RecursionField key={key} name={name} schema={schema} />
})}
</div>
)
})
)
const StepPane: React.FC<React.PropsWithChildren<StepProps>> = ({
children,
}) => {
return <Fragment>{children}</Fragment>
}
FormStep.StepPane = StepPane
FormStep.createFormStep = createFormStep
export default FormStep
```
--------------------------------------------------------------------------------
/packages/element/src/form-tab/index.ts:
--------------------------------------------------------------------------------
```typescript
import { Schema, SchemaKey } from '@formily/json-schema'
import { model } from '@formily/reactive'
import { observer } from '@formily/reactive-vue'
import {
Fragment,
h,
RecursionField,
useField,
useFieldSchema,
} from '@formily/vue'
import { Badge, TabPane, Tabs } from 'element-ui'
import { computed, defineComponent, reactive } from 'vue-demi'
import { stylePrefix } from '../__builtins__/configs'
import type { TabPane as TabPaneProps, Tabs as TabsProps } from 'element-ui'
import { composeExport } from '../__builtins__/shared'
export interface IFormTab {
activeKey: string
setActiveKey(key: string): void
}
export interface IFormTabProps extends TabsProps {
formTab?: IFormTab
}
export interface IFormTabPaneProps extends TabPaneProps {
key: string | number
}
const useTabs = () => {
const tabsField = useField().value
const schema = useFieldSchema().value
const tabs: { name: SchemaKey; props: any; schema: Schema }[] = reactive([])
schema.mapProperties((schema, name) => {
const field = tabsField.query(tabsField.address.concat(name)).take()
if (field?.display === 'none' || field?.display === 'hidden') return
if (schema['x-component']?.indexOf('TabPane') > -1) {
tabs.push({
name,
props: {
name: schema?.['x-component-props']?.name || name,
...schema?.['x-component-props'],
},
schema,
})
}
})
return tabs
}
const createFormTab = (defaultActiveKey?: string) => {
const formTab = model({
activeKey: defaultActiveKey,
setActiveKey(key: string) {
formTab.activeKey = key
},
})
return formTab
}
const FormTabInner = observer(
defineComponent<IFormTabProps>({
name: 'FFormTab',
props: ['formTab'],
setup(props, { attrs, listeners }) {
const field = useField().value
const formTabRef = computed(() => props.formTab ?? createFormTab())
const prefixCls = `${stylePrefix}-form-tab`
return () => {
const formTab = formTabRef.value
const tabs = useTabs()
const activeKey = props.value || formTab?.activeKey || tabs?.[0]?.name
const badgedTab = (key: SchemaKey, props: any) => {
const errors = field.form.queryFeedbacks({
type: 'error',
address: `${field.address.concat(key)}.*`,
})
if (errors.length) {
return () =>
h(
Badge,
{
class: [`${prefixCls}-errors-badge`],
props: {
value: errors.length,
},
},
{ default: () => props.label }
)
}
return () => props.label
}
const getTabs = (tabs) => {
return tabs.map(({ props, schema, name }, key) => {
return h(
TabPane,
{
key,
props,
},
{
default: () => [
h(
RecursionField,
{
props: {
schema,
name,
},
},
{}
),
],
label: () => [
h('div', {}, { default: badgedTab(name, props) }),
],
}
)
})
}
return h(
Tabs,
{
class: [prefixCls],
style: attrs.style,
props: {
...attrs,
value: activeKey,
},
on: {
...listeners,
input: (key) => {
listeners.input?.(key)
formTab.setActiveKey?.(key)
},
},
},
{
default: () => getTabs(tabs),
}
)
}
},
})
)
const FormTabPane = defineComponent<IFormTabPaneProps>({
name: 'FFormTabPane',
setup(_props, { slots }) {
return () => h(Fragment, {}, slots)
},
})
export const FormTab = composeExport(FormTabInner, {
TabPane: FormTabPane,
createFormTab,
})
export default FormTab
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-collapse/markup-schema.vue:
--------------------------------------------------------------------------------
```vue
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaArrayField
name="string_array"
:maxItems="3"
x-decorator="FormItem"
x-component="ArrayCollapse"
:x-component-props="{
accordion: true,
defaultOpenPanelCount: 3,
}"
>
<SchemaVoidField
x-component="ArrayCollapse.Item"
:x-component-props="{
title: '字符串数组',
}"
>
<SchemaVoidField x-component="ArrayCollapse.Index" />
<SchemaStringField
name="input"
x-decorator="FormItem"
title="Input"
required
x-component="Input"
/>
<SchemaVoidField x-component="ArrayCollapse.Remove" />
<SchemaVoidField x-component="ArrayCollapse.MoveUp" />
<SchemaVoidField x-component="ArrayCollapse.MoveDown" />
</SchemaVoidField>
<SchemaVoidField
x-component="ArrayCollapse.Addition"
title="添加条目"
/>
</SchemaArrayField>
<SchemaArrayField
name="array"
:maxItems="3"
x-decorator="FormItem"
x-component="ArrayCollapse"
>
<SchemaObjectField
x-component="ArrayCollapse.Item"
:x-component-props="{
title: '对象数组',
}"
>
<SchemaVoidField x-component="ArrayCollapse.Index" />
<SchemaStringField
name="input"
x-decorator="FormItem"
title="Input"
required
x-component="Input"
/>
<SchemaVoidField x-component="ArrayCollapse.Remove" />
<SchemaVoidField x-component="ArrayCollapse.MoveUp" />
<SchemaVoidField x-component="ArrayCollapse.MoveDown" />
</SchemaObjectField>
<SchemaVoidField
x-component="ArrayCollapse.Addition"
title="添加条目"
/>
</SchemaArrayField>
<SchemaArrayField
name="string_array_unshift"
:maxItems="3"
x-decorator="FormItem"
x-component="ArrayCollapse"
:x-component-props="{
defaultOpenPanelCount: 8,
}"
>
<SchemaVoidField
x-component="ArrayCollapse.Item"
:x-component-props="{
title: '字符串数组',
}"
>
<SchemaVoidField x-component="ArrayCollapse.Index" />
<SchemaStringField
name="input"
x-decorator="FormItem"
title="Input"
required
x-component="Input"
/>
<SchemaVoidField x-component="ArrayCollapse.Remove" />
<SchemaVoidField x-component="ArrayCollapse.MoveUp" />
<SchemaVoidField x-component="ArrayCollapse.MoveDown" />
</SchemaVoidField>
<SchemaVoidField
x-component="ArrayCollapse.Addition"
title="添加条目(unshift)"
:x-component-props="{
method: 'unshift',
}"
/>
</SchemaArrayField>
</SchemaField>
<FormButtonGroup>
<Button
@click="
() => {
form.setInitialValues({
array: Array.from({ length: 10 }).map(() => ({
input: 'default value',
})),
string_array: Array.from({ length: 10 }).map(
() => 'default value'
),
string_array_unshift: Array.from({ length: 10 }).map(
() => 'default value'
),
})
}
"
>
加载默认数据
</Button>
<Submit @submit="log">提交</Submit>
</FormButtonGroup>
</FormProvider>
</template>
<script>
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/vue'
import {
FormItem,
FormButtonGroup,
Submit,
Input,
ArrayCollapse,
} from '@formily/element'
import { Button } from 'element-ui'
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
ArrayCollapse,
},
})
export default {
components: {
FormProvider,
FormButtonGroup,
Button,
Submit,
...SchemaField,
},
data() {
const form = createForm()
return {
form,
}
},
methods: {
log(values) {
console.log(values)
},
},
}
</script>
<style lang="scss" scoped></style>
```
--------------------------------------------------------------------------------
/packages/element/src/checkbox/index.ts:
--------------------------------------------------------------------------------
```typescript
import { connect, h, mapProps, mapReadPretty } from '@formily/vue'
import type {
Checkbox as _ElCheckboxProps,
CheckboxGroup as ElCheckboxGroupProps,
} from 'element-ui'
import {
Checkbox as ElCheckbox,
CheckboxButton as ElCheckboxButton,
CheckboxGroup as ElCheckboxGroup,
} from 'element-ui'
import { defineComponent, PropType } from 'vue-demi'
import { PreviewText } from '../preview-text'
import {
composeExport,
resolveComponent,
SlotTypes,
transformComponent,
} from '../__builtins__/shared'
type ElCheckboxProps = Omit<_ElCheckboxProps, 'value'> & {
value: ElCheckboxProps['label']
}
export interface CheckboxProps extends ElCheckboxProps {
option: Omit<_ElCheckboxProps, 'value'> & {
value: ElCheckboxProps['label']
label: SlotTypes
}
}
const CheckboxOption = defineComponent<CheckboxProps>({
name: 'Checkbox',
inheritAttrs: false,
props: {
option: {
type: Object,
default: null,
},
},
setup(curtomProps, { attrs, slots, listeners }) {
return () => {
const props = attrs as unknown as CheckboxProps
const option = curtomProps?.option
if (option) {
const children = {
default: () => [
resolveComponent(slots.default ?? option.label, { option }),
],
}
const newProps = {} as Partial<ElCheckboxProps>
Object.assign(newProps, option)
newProps.label = option.value
delete newProps.value
return h(
attrs.optionType === 'button' ? ElCheckboxButton : ElCheckbox,
{
attrs: {
...newProps,
},
},
children
)
}
return h(
ElCheckbox,
{
attrs: {
...props,
},
on: listeners,
},
slots
)
}
},
})
export type CheckboxGroupProps = ElCheckboxGroupProps & {
value: any[]
options?: Array<CheckboxProps | string>
optionType: 'default' | 'button'
}
const TransformElCheckboxGroup = transformComponent(ElCheckboxGroup, {
change: 'input',
uselessChange: 'change'
})
const CheckboxGroupOption = defineComponent<CheckboxGroupProps>({
name: 'CheckboxGroup',
props: {
options: {
type: Array,
default: () => [],
},
optionType: {
type: String as PropType<CheckboxGroupProps['optionType']>,
default: 'default',
},
},
setup(customProps, { attrs, slots, listeners }) {
return () => {
const options = customProps.options || []
const children =
options.length !== 0
? {
default: () =>
options.map((option) => {
if (typeof option === 'string') {
return h(
Checkbox,
{
props: {
option: {
label: option,
value: option,
},
},
attrs: {
optionType: customProps.optionType,
},
},
slots?.option
? { default: () => slots.option({ option }) }
: {}
)
} else {
return h(
Checkbox,
{
props: {
option,
},
attrs: {
optionType: customProps.optionType,
},
},
slots?.option
? { default: () => slots.option({ option }) }
: {}
)
}
}),
}
: slots
return h(
TransformElCheckboxGroup,
{
attrs: {
...attrs,
},
on: listeners,
},
children
)
}
},
})
const CheckboxGroup = connect(
CheckboxGroupOption,
mapProps({ dataSource: 'options' }),
mapReadPretty(PreviewText.Select, {
multiple: true,
})
)
export const Checkbox = composeExport(connect(CheckboxOption), {
Group: CheckboxGroup,
})
export default Checkbox
```
--------------------------------------------------------------------------------
/packages/react/src/components/RecursionField.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { Fragment, useMemo } from 'react'
import { FormPath, isBool, isFn, isValid } from '@formily/shared'
import { GeneralField } from '@formily/core'
import { Schema } from '@formily/json-schema'
import { SchemaContext } from '../shared'
import { IRecursionFieldProps, ReactFC } from '../types'
import { useField, useExpressionScope } from '../hooks'
import { ObjectField } from './ObjectField'
import { ArrayField } from './ArrayField'
import { Field } from './Field'
import { VoidField } from './VoidField'
import { ExpressionScope } from './ExpressionScope'
import { observable } from '@formily/reactive'
const useFieldProps = (schema: Schema) => {
const scope = useExpressionScope()
return schema.toFieldProps({
scope,
}) as any
}
const useBasePath = (props: IRecursionFieldProps) => {
const parent = useField()
if (props.onlyRenderProperties) {
return props.basePath || parent?.address.concat(props.name)
}
return props.basePath || parent?.address
}
export const RecursionField: ReactFC<IRecursionFieldProps> = (props) => {
const basePath = useBasePath(props)
const fieldSchema = useMemo(() => new Schema(props.schema), [props.schema])
const fieldProps = useFieldProps(fieldSchema)
const renderSlots = (innerSchema, key) => {
const slot = innerSchema['x-slot-node']
const { target, isRenderProp } = slot
if (isRenderProp) {
const args = observable({ $slotArgs: [] })
FormPath.setIn(fieldSchema.properties, target, (..._args: any) => {
args.$slotArgs = _args
return (
<ExpressionScope value={args}>
<RecursionField schema={innerSchema} name={key} />
</ExpressionScope>
)
})
} else {
FormPath.setIn(
fieldSchema.properties,
target,
<RecursionField schema={innerSchema} name={key} />
)
}
}
const renderProperties = (field?: GeneralField) => {
if (props.onlyRenderSelf) return
const properties = Schema.getOrderProperties(fieldSchema)
if (!properties.length) return
return (
<Fragment>
{properties.map(({ schema: item, key: name }, index) => {
const base = field?.address || basePath
let schema: Schema = item
if (schema['x-slot-node']) {
renderSlots(schema, name)
return null
}
if (isFn(props.mapProperties)) {
const mapped = props.mapProperties(item, name)
if (mapped) {
schema = mapped
}
}
if (isFn(props.filterProperties)) {
if (props.filterProperties(schema, name) === false) {
return null
}
}
if (isBool(props.propsRecursion) && props.propsRecursion) {
return (
<RecursionField
propsRecursion={true}
filterProperties={props.filterProperties}
mapProperties={props.mapProperties}
schema={schema}
key={`${index}-${name}`}
name={name}
basePath={base}
/>
)
}
return (
<RecursionField
schema={schema}
key={`${index}-${name}`}
name={name}
basePath={base}
/>
)
})}
</Fragment>
)
}
const render = () => {
if (!isValid(props.name)) return renderProperties()
if (fieldSchema.type === 'object') {
if (props.onlyRenderProperties) return renderProperties()
return (
<ObjectField {...fieldProps} name={props.name} basePath={basePath}>
{renderProperties}
</ObjectField>
)
} else if (fieldSchema.type === 'array') {
return (
<ArrayField {...fieldProps} name={props.name} basePath={basePath} />
)
} else if (fieldSchema.type === 'void') {
if (props.onlyRenderProperties) return renderProperties()
return (
<VoidField {...fieldProps} name={props.name} basePath={basePath}>
{renderProperties}
</VoidField>
)
}
return <Field {...fieldProps} name={props.name} basePath={basePath} />
}
if (!fieldSchema) return <Fragment />
return (
<SchemaContext.Provider value={fieldSchema}>
{render()}
</SchemaContext.Provider>
)
}
```
--------------------------------------------------------------------------------
/packages/antd/src/array-cards/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React from 'react'
import { Card, Empty } from 'antd'
import { CardProps } from 'antd/lib/card'
import { ArrayField } from '@formily/core'
import {
useField,
observer,
useFieldSchema,
RecursionField,
} from '@formily/react'
import cls from 'classnames'
import { ISchema } from '@formily/json-schema'
import { usePrefixCls } from '../__builtins__'
import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base'
type ComposedArrayCards = React.FC<
React.PropsWithChildren<CardProps & IArrayBaseProps>
> &
ArrayBaseMixins
const isAdditionComponent = (schema: ISchema) => {
return schema['x-component']?.indexOf('Addition') > -1
}
const isIndexComponent = (schema: ISchema) => {
return schema['x-component']?.indexOf?.('Index') > -1
}
const isRemoveComponent = (schema: ISchema) => {
return schema['x-component']?.indexOf?.('Remove') > -1
}
const isCopyComponent = (schema: ISchema) => {
return schema['x-component']?.indexOf?.('Copy') > -1
}
const isMoveUpComponent = (schema: ISchema) => {
return schema['x-component']?.indexOf?.('MoveUp') > -1
}
const isMoveDownComponent = (schema: ISchema) => {
return schema['x-component']?.indexOf?.('MoveDown') > -1
}
const isOperationComponent = (schema: ISchema) => {
return (
isAdditionComponent(schema) ||
isRemoveComponent(schema) ||
isCopyComponent(schema) ||
isMoveDownComponent(schema) ||
isMoveUpComponent(schema)
)
}
export const ArrayCards: ComposedArrayCards = observer((props) => {
const field = useField<ArrayField>()
const schema = useFieldSchema()
const dataSource = Array.isArray(field.value) ? field.value : []
const prefixCls = usePrefixCls('formily-array-cards', props)
const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props
if (!schema) throw new Error('can not found schema object')
const renderItems = () => {
return dataSource?.map((item, index) => {
const items = Array.isArray(schema.items)
? schema.items[index] || schema.items[0]
: schema.items
const title = (
<span>
<RecursionField
schema={items}
name={index}
filterProperties={(schema) => {
if (!isIndexComponent(schema)) return false
return true
}}
onlyRenderProperties
/>
{props.title || field.title}
</span>
)
const extra = (
<span>
<RecursionField
schema={items}
name={index}
filterProperties={(schema) => {
if (!isOperationComponent(schema)) return false
return true
}}
onlyRenderProperties
/>
{props.extra}
</span>
)
const content = (
<RecursionField
schema={items}
name={index}
filterProperties={(schema) => {
if (isIndexComponent(schema)) return false
if (isOperationComponent(schema)) return false
return true
}}
/>
)
return (
<ArrayBase.Item
key={index}
index={index}
record={() => field.value?.[index]}
>
<Card
{...props}
onChange={() => {}}
className={cls(`${prefixCls}-item`, props.className)}
title={title}
extra={extra}
>
{content}
</Card>
</ArrayBase.Item>
)
})
}
const renderAddition = () => {
return schema.reduceProperties((addition, schema, key) => {
if (isAdditionComponent(schema)) {
return <RecursionField schema={schema} name={key} />
}
return addition
}, null)
}
const renderEmpty = () => {
if (dataSource?.length) return
return (
<Card
{...props}
onChange={() => {}}
className={cls(`${prefixCls}-item`, props.className)}
title={props.title || field.title}
>
<Empty />
</Card>
)
}
return (
<ArrayBase
onAdd={onAdd}
onCopy={onCopy}
onRemove={onRemove}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
>
{renderEmpty()}
{renderItems()}
{renderAddition()}
</ArrayBase>
)
})
ArrayCards.displayName = 'ArrayCards'
ArrayBase.mixin(ArrayCards)
export default ArrayCards
```
--------------------------------------------------------------------------------
/packages/reactive/src/__tests__/define.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { define, model, observable, autorun } from '..'
import { observe } from '../observe'
import { FormPath } from '@formily/shared'
import { batch } from '../batch'
describe('makeObservable', () => {
test('observable annotation', () => {
const target: any = {
aa: {},
}
define(target, {
aa: observable,
})
const handler = jest.fn()
const handler1 = jest.fn()
const handler2 = jest.fn()
autorun(() => {
handler(FormPath.getIn(target, 'aa.bb.cc'))
})
observe(target, handler1)
observe(target.aa, handler2)
target.aa.bb = { cc: { dd: { ee: 123 } } }
target.aa = { hh: 123 }
expect(handler).toBeCalledTimes(3)
expect(handler).nthCalledWith(1, undefined)
expect(handler).nthCalledWith(2, { dd: { ee: 123 } })
expect(handler).nthCalledWith(3, undefined)
expect(handler1).toBeCalledTimes(2)
expect(handler2).toBeCalledTimes(2)
})
test('shallow annotation', () => {
const target: any = {
aa: {},
}
define(target, {
aa: observable.shallow,
})
const handler = jest.fn()
const handler1 = jest.fn()
const handler2 = jest.fn()
autorun(() => {
handler(FormPath.getIn(target, 'aa.bb.cc'))
})
observe(target, handler1)
observe(target.aa, handler2)
target.aa.bb = { cc: { dd: { ee: 123 } } }
target.aa.bb.cc.kk = 333
target.aa = { hh: 123 }
expect(handler).toBeCalledTimes(3)
expect(handler).nthCalledWith(1, undefined)
expect(handler).nthCalledWith(2, { dd: { ee: 123 }, kk: 333 })
expect(handler).nthCalledWith(3, undefined)
expect(handler1).toBeCalledTimes(2)
expect(handler2).toBeCalledTimes(2)
})
test('box annotation', () => {
const target: any = {}
define(target, {
aa: observable.box,
})
const handler = jest.fn()
const handler1 = jest.fn()
const handler2 = jest.fn()
autorun(() => {
handler(target.aa.get())
})
observe(target, handler1)
observe(target.aa, handler2)
expect(handler).lastCalledWith(undefined)
target.aa.set(123)
expect(handler).toBeCalledTimes(2)
expect(handler).lastCalledWith(123)
expect(handler1).toBeCalledTimes(1)
expect(handler2).toBeCalledTimes(1)
})
test('ref annotation', () => {
const target: any = {}
define(target, {
aa: observable.ref,
})
const handler = jest.fn()
const handler1 = jest.fn()
autorun(() => {
handler(target.aa)
})
observe(target, handler1)
expect(handler).lastCalledWith(undefined)
target.aa = 123
expect(handler).toBeCalledTimes(2)
expect(handler).lastCalledWith(123)
expect(handler1).toBeCalledTimes(1)
})
test('action annotation', () => {
const target = {
aa: {
bb: null,
cc: null,
},
setData() {
target.aa.bb = 123
target.aa.cc = 312
},
}
define(target, {
aa: observable,
setData: batch,
})
const handler = jest.fn()
autorun(() => {
handler([target.aa.bb, target.aa.cc])
})
expect(handler).toBeCalledTimes(1)
target.setData()
expect(handler).toBeCalledTimes(2)
})
test('computed annotation', () => {
const handler = jest.fn()
const target = {
aa: 11,
bb: 22,
get cc() {
handler()
return this.aa + this.bb
},
}
define(target, {
aa: observable,
bb: observable,
cc: observable.computed,
})
autorun(() => {
target.cc
})
expect(handler).toBeCalledTimes(1)
expect(target.cc).toEqual(33)
target.aa = 22
expect(handler).toBeCalledTimes(2)
expect(target.cc).toEqual(44)
})
test('unexpect target', () => {
const testFn = jest.fn()
const testArr = []
const obs1 = define(4 as any, {
value: observable.computed,
})
const obs2 = define('123' as any, {
value: observable.computed,
})
const obs3 = define(testFn as any, {
value: observable.computed,
})
const obs4 = define(testArr as any, {
value: observable.computed,
})
expect(obs1).toBe(4)
expect(obs2).toBe('123')
expect(obs3).toBe(testFn)
expect(obs4).toBe(testArr)
})
})
test('define model', () => {
const obs = model({
aa: 1,
action() {
this.aa++
},
})
const { action } = obs
action()
expect(obs.aa).toEqual(2)
})
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/Reset.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# Reset
> 重置按钮
## 普通重置
> 有默认值的控件无法被清空
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="输入框"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset>重置</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## 强制清空重置
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="输入框"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset forceClear>重置</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## 重置并校验
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="输入框"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset validate>重置</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## 强制清空重置并校验
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="输入框"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset forceClear validate>
重置
</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## API
### Reset
其余 API 参考 https://ant.design/components/button-cn/
| 属性名 | 类型 | 描述 | 默认值 |
| ---------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------- | ------ |
| onClick | `(event: MouseEvent) => void \| boolean` | 点击事件,如果返回 false 可以阻塞重置 | - |
| onResetValidateSuccess | (payload: any) => void | 重置校验成功事件 | - |
| onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/zh-CN/api/models/form#iformfeedback)[]) => void | 重置校验失败事件 | - |
```
--------------------------------------------------------------------------------
/packages/next/docs/components/Reset.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# Reset
> 重置按钮
## 普通重置
> 有默认值的控件无法被清空
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="输入框"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset>重置</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## 强制清空重置
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="输入框"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset forceClear>重置</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## 重置并校验
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="输入框"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset validate>重置</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## 强制清空重置并校验
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="输入框"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="输入框"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset forceClear validate>
重置
</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## API
### Reset
其余 API 参考 https://fusion.design/pc/component/basic/button
| 属性名 | 类型 | 描述 | 默认值 |
| ---------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------- | ------ |
| onClick | `(event: MouseEvent) => void \| boolean` | 点击事件,如果返回 false 可以阻塞重置 | - |
| onResetValidateSuccess | (payload: any) => void | 重置校验成功事件 | - |
| onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/zh-CN/api/models/form#iformfeedback)[]) => void | 重置校验失败事件 | - |
```
--------------------------------------------------------------------------------
/packages/next/docs/components/Form.md:
--------------------------------------------------------------------------------
```markdown
# Form
> The combination of FormProvider + FormLayout + form tags can help us quickly implement forms that are submitted with carriage return and can be laid out in batches
## Use Cases
```tsx
import React from 'react'
import {
Input,
Select,
Form,
FormItem,
FormGrid,
FormButtonGroup,
Submit,
} from '@formily/next'
import { createForm } from '@formily/core'
import { Field } from '@formily/react'
const form = createForm()
export default () => (
<Form
form={form}
layout="vertical"
feedbackLayout="terse"
onAutoSubmit={console.log}
onAutoSubmitFailed={console.log}
>
<FormGrid maxColumns={4}>
<Field
name="aa"
title="select box"
decorator={[FormItem]}
component={[Select]}
dataSource={[
{
label: 'Option 1',
value: 1,
},
{
label: 'Option 2',
value: 2,
},
]}
/>
<Field
name="bb"
title="input box"
required
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="cc"
title="input box"
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="dd"
title="input box"
decorator={[FormItem]}
component={[Input]}
/>
<Field
name="ee"
title="input box"
decorator={[FormItem]}
component={[Input]}
/>
<FormButtonGroup.FormItem>
<Submit>Query</Submit>
</FormButtonGroup.FormItem>
</FormGrid>
</Form>
)
```
## Fusion Multilingual
```tsx
import React from 'react'
import { Input, Form, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { Field } from '@formily/react'
import { ConfigProvider } from '@alifd/next'
import enUS from '@alifd/next/lib/locale/en-us'
const form = createForm()
export default () => (
<ConfigProvider locale={enUS}>
<Form
form={form}
layout="vertical"
feedbackLayout="terse"
onAutoSubmit={console.log}
>
<Field
name="bb"
title="User Name"
required
decorator={[FormItem]}
component={[Input]}
/>
<FormButtonGroup.FormItem>
<Submit>Submit</Submit>
</FormButtonGroup.FormItem>
</Form>
</ConfigProvider>
)
```
<Alert style="margin-top:20px">
Note: To realize the carriage return submission, we cannot pass the onSubmit event to it when using the Submit component, otherwise the carriage return submission will become invalid. The purpose of this is to prevent users from writing onSubmit event listeners in multiple places at the same time, and processing logic If they are inconsistent, it is difficult to locate the problem when submitting.
</Alert>
## API
For layout-related API properties, we can refer to [FormLayout](./form-layout), and the rest are the unique API properties of the Form component
| Property name | Type | Description | Default value |
| ---------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ------------- |
| form | [Form](https://core.formilyjs.org/api/models/form) | Form example | - |
| component | string | Rendering component, can be specified as custom component rendering | `form` |
| previewTextPlaceholder | ReactNode | Preview State Placeholder | `N/A` |
| onAutoSubmit | `(values:any)=>any` | Carriage return submit event callback | - |
| onAutoSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Carriage return submission verification failure event callback | - |
```
--------------------------------------------------------------------------------
/packages/validator/src/rules.ts:
--------------------------------------------------------------------------------
```typescript
import {
isEmpty,
isValid,
stringLength,
isStr,
isArr,
isFn,
toArr,
isBool,
isNum,
isEqual,
each,
} from '@formily/shared'
import { getValidateFormats } from './registry'
import { IRegistryRules } from './types'
const isValidateEmpty = (value: any) => {
if (isArr(value)) {
for (let i = 0; i < value.length; i++) {
if (isValid(value[i])) return false
}
return true
} else {
//compat to draft-js
if (value?.getCurrentContent) {
/* istanbul ignore next */
return !value.getCurrentContent()?.hasText()
}
return isEmpty(value)
}
}
const getLength = (value: any) =>
isStr(value) ? stringLength(value) : value ? value.length : 0
const extendSameRules = (
rules: IRegistryRules,
names: Record<string, string>
) => {
each(names, (realName, name) => {
rules[name] = (value, rule, ...args) =>
rules[realName](value, { ...rule, [realName]: rule[name] }, ...args)
})
}
const RULES: IRegistryRules = {
format(value, rule) {
if (isValidateEmpty(value)) return ''
if (rule.format) {
const format = getValidateFormats(rule.format)
if (format) {
return !new RegExp(format).test(value) ? rule.message : ''
}
}
return ''
},
required(value, rule) {
if (rule.required !== true) return ''
return isValidateEmpty(value) ? rule.message : ''
},
max(value, rule) {
if (isValidateEmpty(value)) return ''
const length = isNum(value) ? value : getLength(value)
const max = Number(rule.max)
return length > max ? rule.message : ''
},
min(value, rule) {
if (isValidateEmpty(value)) return ''
const length = isNum(value) ? value : getLength(value)
const min = Number(rule.min)
return length < min ? rule.message : ''
},
exclusiveMaximum(value, rule) {
if (isValidateEmpty(value)) return ''
const length = isNum(value) ? value : getLength(value)
const max = Number(rule.exclusiveMaximum)
return length >= max ? rule.message : ''
},
exclusiveMinimum(value, rule) {
if (isValidateEmpty(value)) return ''
const length = isNum(value) ? value : getLength(value)
const min = Number(rule.exclusiveMinimum)
return length <= min ? rule.message : ''
},
len(value, rule) {
if (isValidateEmpty(value)) return ''
const length = getLength(value)
const len = Number(rule.len)
return length !== len ? rule.message : ''
},
pattern(value, rule) {
if (isValidateEmpty(value)) return ''
return !new RegExp(rule.pattern).test(value) ? rule.message : ''
},
async validator(value, rule, context, format) {
if (isFn(rule.validator)) {
const response = await Promise.resolve(
rule.validator(value, rule, context, format)
)
if (isBool(response)) {
return !response ? rule.message : ''
} else {
return response
}
}
/* istanbul ignore next */
throw new Error("The rule's validator property must be a function.")
},
whitespace(value, rule) {
if (isValidateEmpty(value)) return ''
if (rule.whitespace) {
return /^\s+$/.test(value) ? rule.message : ''
}
},
enum(value, rule) {
if (isValidateEmpty(value)) return ''
const enums = toArr(rule.enum)
return enums.indexOf(value) === -1 ? rule.message : ''
},
const(value, rule) {
if (isValidateEmpty(value)) return ''
return rule.const !== value ? rule.message : ''
},
multipleOf(value, rule) {
if (isValidateEmpty(value)) return ''
return Number(value) % Number(rule.multipleOf) !== 0 ? rule.message : ''
},
uniqueItems(value, rule) {
if (isValidateEmpty(value)) return ''
value = toArr(value)
return value.some((item: any, index: number) => {
for (let i = 0; i < value.length; i++) {
if (i !== index && !isEqual(value[i], item)) {
return false
}
}
return true
})
? ''
: rule.message
},
maxProperties(value, rule) {
if (isValidateEmpty(value)) return ''
return Object.keys(value || {}).length <= Number(rule.maxProperties)
? ''
: rule.message
},
minProperties(value, rule) {
if (isValidateEmpty(value)) return ''
return Object.keys(value || {}).length >= Number(rule.minProperties)
? ''
: rule.message
},
}
extendSameRules(RULES, {
maximum: 'max',
minimum: 'min',
maxItems: 'max',
minItems: 'min',
maxLength: 'max',
minLength: 'min',
})
export default RULES
```
--------------------------------------------------------------------------------
/packages/core/src/effects/onFieldEffects.ts:
--------------------------------------------------------------------------------
```typescript
import { FormPath, isFn, toArr } from '@formily/shared'
import { autorun, reaction, batch } from '@formily/reactive'
import { Form } from '../models'
import {
LifeCycleTypes,
FormPathPattern,
GeneralField,
DataField,
IFieldState,
} from '../types'
import { createEffectHook, useEffectForm } from '../shared/effective'
function createFieldEffect<Result extends GeneralField = GeneralField>(
type: LifeCycleTypes
) {
return createEffectHook(
type,
(field: Result, form: Form) =>
(
pattern: FormPathPattern,
callback: (field: Result, form: Form) => void
) => {
if (
FormPath.parse(pattern).matchAliasGroup(field.address, field.path)
) {
batch(() => {
callback(field, form)
})
}
}
)
}
const _onFieldInit = createFieldEffect(LifeCycleTypes.ON_FIELD_INIT)
export const onFieldMount = createFieldEffect(LifeCycleTypes.ON_FIELD_MOUNT)
export const onFieldUnmount = createFieldEffect(LifeCycleTypes.ON_FIELD_UNMOUNT)
export const onFieldValueChange = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_VALUE_CHANGE
)
export const onFieldInitialValueChange = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE
)
export const onFieldInputValueChange = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_INPUT_VALUE_CHANGE
)
export const onFieldValidateStart = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_VALIDATE_START
)
export const onFieldValidateEnd = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_VALIDATE_END
)
export const onFieldValidating = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_VALIDATING
)
export const onFieldValidateFailed = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_VALIDATE_FAILED
)
export const onFieldValidateSuccess = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_VALIDATE_SUCCESS
)
export const onFieldSubmit = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT
)
export const onFieldSubmitStart = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT_START
)
export const onFieldSubmitEnd = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT_END
)
export const onFieldSubmitValidateStart = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_START
)
export const onFieldSubmitValidateEnd = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_END
)
export const onFieldSubmitSuccess = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT_SUCCESS
)
export const onFieldSubmitFailed = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT_FAILED
)
export const onFieldSubmitValidateSuccess = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_SUCCESS
)
export const onFieldSubmitValidateFailed = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_FAILED
)
export const onFieldReset = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_RESET
)
export const onFieldLoading = createFieldEffect<DataField>(
LifeCycleTypes.ON_FIELD_LOADING
)
export function onFieldInit(
pattern: FormPathPattern,
callback?: (field: GeneralField, form: Form) => void
) {
const form = useEffectForm()
const count = form.query(pattern).reduce((count, field) => {
callback(field, form)
return count + 1
}, 0)
if (count === 0) {
_onFieldInit(pattern, callback)
}
}
export function onFieldReact(
pattern: FormPathPattern,
callback?: (field: GeneralField, form: Form) => void
) {
onFieldInit(pattern, (field, form) => {
field.disposers.push(
autorun(() => {
if (isFn(callback)) callback(field, form)
})
)
})
}
export function onFieldChange(
pattern: FormPathPattern,
callback?: (field: GeneralField, form: Form) => void
): void
export function onFieldChange(
pattern: FormPathPattern,
watches: (keyof IFieldState)[],
callback?: (field: GeneralField, form: Form) => void
): void
export function onFieldChange(
pattern: FormPathPattern,
watches: any,
callback?: (field: GeneralField, form: Form) => void
): void {
if (isFn(watches)) {
callback = watches
watches = ['value']
} else {
watches = watches || ['value']
}
onFieldInit(pattern, (field, form) => {
if (isFn(callback)) callback(field, form)
const dispose = reaction(
() => {
return toArr(watches).map((key) => {
return field[key]
})
},
() => {
if (isFn(callback)) callback(field, form)
}
)
field.disposers.push(dispose)
})
}
```
--------------------------------------------------------------------------------
/packages/reactive-vue/src/__tests__/observer.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { shallowMount, createLocalVue } from '@vue/test-utils'
import { observable, autorun } from '@formily/reactive'
import { CreateElement } from 'vue'
import CompositionAPI, { defineComponent, h } from '@vue/composition-api'
import { observer } from '../'
import collectData from '../observer/collectData'
import { observer as observerInVue2 } from '../observer/observerInVue2'
import expect from 'expect'
test('observer: component', async () => {
const model = observable<any>({
age: 10,
setAge() {
model.age++
},
})
const Component = observer({
data() {
return {
model,
}
},
render(this: any, h: CreateElement) {
return h('button', {
on: { click: this.model.setAge },
domProps: { textContent: this.model.age },
})
},
})
const wrapper = shallowMount(Component)
expect(wrapper.find('button').text()).toBe('10')
wrapper.find('button').trigger('click')
expect(wrapper.find('button').text()).toBe('11')
wrapper.destroy()
})
test('observer: component with setup', async () => {
const Vue = createLocalVue()
Vue.use(CompositionAPI)
const model = observable<any>({
age: 30,
get sub10() {
return model.age - 10
},
get sub20() {
return model.sub10 - 10
},
setAge() {
model.age++
},
})
const Component = observer(
defineComponent({
setup() {
return () => {
return h('button', {
on: { click: model.setAge },
domProps: { textContent: model.sub20 },
})
}
},
// to fix 'Maximum call stack size exceeded' error of @vue/test-utils
render() {
return null
},
})
)
const wrapper = shallowMount(Component)
expect(wrapper.find('button').text()).toBe('10')
wrapper.find('button').trigger('click')
expect(wrapper.find('button').text()).toBe('11')
model.age++
expect(wrapper.find('button').text()).toBe('12')
wrapper.destroy()
})
test('observer: component scheduler', async () => {
let schedulerRequest = null
const model = observable<any>({
age: 10,
setAge() {
model.age++
},
})
const Component = observer(
{
data() {
return {
model,
}
},
render(this: any, h: CreateElement) {
return h('button', {
on: { click: this.model.setAge },
domProps: { textContent: this.model.age },
})
},
},
{
scheduler: (update) => {
clearTimeout(schedulerRequest)
schedulerRequest = setTimeout(() => {
update()
}, 100)
},
}
)
const wrapper = shallowMount(Component)
expect(wrapper.find('button').text()).toBe('10')
wrapper.find('button').trigger('click')
await new Promise((r) => setTimeout(r, 150))
expect(wrapper.find('button').text()).toBe('11')
// test second render
wrapper.find('button').trigger('click')
await new Promise((r) => setTimeout(r, 150))
expect(wrapper.find('button').text()).toBe('12')
wrapper.destroy()
})
test('observer: stop tracking if watcher is destroyed', async () => {
let count = 0
const model = observable<any>({
age: 10,
name: 'test',
})
const Component = observer({
name: 'test',
data() {
return {
model: model,
}
},
render() {
count++
return h('div', [this.model.name, this.model.age])
},
})
const wrapper = shallowMount(Component)
const childInst = wrapper.find({ name: 'test' })
expect(childInst.exists()).toBe(true)
;(childInst.vm as any)._isDestroyed = true
model.age++
wrapper.destroy()
expect(count).toEqual(1) // 不触发 reactiveRender
})
test('collectData', async () => {
const model = observable<any>({
age: 10,
name: 'test',
})
const target = {
value: 1,
}
const data = collectData(
{},
{
model,
target,
}
)
const fn1 = jest.fn()
const fn2 = jest.fn()
autorun(() => fn1(model.age))
autorun(() => fn2(data.target.value))
model.age++
expect(fn1).toBeCalledTimes(2)
target.value++
expect(fn2).toBeCalledTimes(1)
})
test('observerInVue2', () => {
const componentObj = Object.create(null)
componentObj.data = () => {
return {}
}
const ExtendedComponent1 = observerInVue2(componentObj)
expect(ExtendedComponent1.name).toEqual('<component>')
function Component() {}
Component.options = {
data: () => {
return {}
},
}
const ExtendedComponent2 = observerInVue2(Component, { name: 'abc' })
expect(ExtendedComponent2.name).toEqual('abc')
})
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-table/effects-json-schema.vue:
--------------------------------------------------------------------------------
```vue
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
<Submit @submit="log">提交</Submit>
</FormProvider>
</template>
<script>
import { createForm, onFieldChange, onFieldReact } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/vue'
import {
Submit,
FormItem,
ArrayTable,
Input,
Editable,
Switch,
} from '@formily/element'
const fields = createSchemaField({
components: {
FormItem,
ArrayTable,
Input,
Editable,
Switch,
},
})
export default {
components: { FormProvider, Submit, ...fields },
data() {
const form = createForm({
effects: () => {
//主动联动模式
onFieldChange('hideFirstColumn', ['value'], (field) => {
field.query('array.column3').take((target) => {
target.visible = !field.value
})
field.query('array.*.a2').take((target) => {
target.visible = !field.value
})
})
//被动联动模式
onFieldReact('array.*.a2', (field) => {
field.visible = !field.query('.a1').get('value')
})
},
})
const schema = {
type: 'object',
properties: {
hideFirstColumn: {
type: 'boolean',
title: '隐藏A2',
'x-decorator': 'FormItem',
'x-component': 'Switch',
},
array: {
type: 'array',
'x-decorator': 'FormItem',
'x-component': 'ArrayTable',
items: {
type: 'object',
properties: {
column1: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': {
width: 80,
title: 'Index',
align: 'center',
},
properties: {
index: {
type: 'void',
'x-component': 'ArrayTable.Index',
},
},
},
column2: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 100, title: '显隐->A2' },
properties: {
a1: {
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'Switch',
},
},
},
column3: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 200, title: 'A2' },
properties: {
a2: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
column4: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: 'A3' },
properties: {
a3: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
column5: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': {
title: 'Operations',
prop: 'operations',
width: 200,
fixed: 'right',
},
properties: {
item: {
type: 'void',
'x-component': 'FormItem',
properties: {
remove: {
type: 'void',
'x-component': 'ArrayTable.Remove',
},
moveDown: {
type: 'void',
'x-component': 'ArrayTable.MoveDown',
},
moveUp: {
type: 'void',
'x-component': 'ArrayTable.MoveUp',
},
},
},
},
},
},
},
properties: {
add: {
type: 'void',
'x-component': 'ArrayTable.Addition',
title: '添加条目',
},
},
},
},
}
return {
form,
schema,
}
},
methods: {
log(...v) {
console.log(...v)
},
},
}
</script>
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-items/markup-schema.vue:
--------------------------------------------------------------------------------
```vue
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaArrayField
name="string_array"
title="字符串数组"
x-decorator="FormItem"
x-component="ArrayItems"
>
<SchemaVoidField x-component="Space">
<SchemaVoidField
x-decorator="FormItem"
x-component="ArrayItems.SortHandle"
/>
<SchemaStringField
x-decorator="FormItem"
required
name="input"
x-component="Input"
:x-component-props="{
style: {
width: '160px',
},
}"
/>
<SchemaVoidField
x-decorator="FormItem"
x-component="ArrayItems.Remove"
/>
</SchemaVoidField>
<SchemaVoidField x-component="ArrayItems.Addition" title="添加条目" />
</SchemaArrayField>
<SchemaArrayField
name="array"
title="对象数组"
x-decorator="FormItem"
x-component="ArrayItems"
>
<SchemaObjectField>
<SchemaVoidField x-component="Space">
<SchemaVoidField
x-decorator="FormItem"
x-component="ArrayItems.SortHandle"
/>
<SchemaStringField
x-decorator="FormItem"
required
title="日期"
name="date"
x-component="DatePicker"
:x-component-props="{
type: 'daterange',
style: {
width: '160px',
},
}"
/>
<SchemaStringField
x-decorator="FormItem"
required
title="输入框"
name="input"
x-component="Input"
/>
<SchemaStringField
x-decorator="FormItem"
required
title="选择框"
name="select"
:enum="[
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2 },
]"
x-component="Select"
:x-component-props="{
style: {
width: 160,
},
}"
/>
<SchemaVoidField
x-decorator="FormItem"
x-component="ArrayItems.Remove"
/>
</SchemaVoidField>
</SchemaObjectField>
<SchemaVoidField x-component="ArrayItems.Addition" title="添加条目" />
</SchemaArrayField>
<SchemaArrayField
name="array2"
title="对象数组"
x-decorator="FormItem"
x-component="ArrayItems"
:x-component-props="{ style: { width: '600px' } }"
>
<SchemaObjectField x-decorator="ArrayItems.Item">
<SchemaVoidField x-component="Space">
<SchemaVoidField
x-decorator="FormItem"
x-component="ArrayItems.SortHandle"
/>
<SchemaStringField
x-decorator="FormItem"
required
title="日期"
name="date"
x-component="DatePicker"
:x-component-props="{
type: 'daterange',
style: {
width: '250px',
},
}"
/>
<SchemaStringField
x-decorator="FormItem"
required
title="输入框"
name="input"
x-component="Input"
/>
<SchemaVoidField
x-decorator="FormItem"
x-component="ArrayItems.Remove"
/>
</SchemaVoidField>
</SchemaObjectField>
<SchemaVoidField x-component="ArrayItems.Addition" title="添加条目" />
</SchemaArrayField>
</SchemaField>
<FormButtonGroup>
<Submit @submit="log">提交</Submit>
</FormButtonGroup>
</FormProvider>
</template>
<script>
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/vue'
import {
FormItem,
FormButtonGroup,
Submit,
Input,
Select,
Space,
DatePicker,
ArrayItems,
} from '@formily/element'
import { Button } from 'element-ui'
const SchemaField = createSchemaField({
components: {
FormItem,
Space,
Input,
Select,
DatePicker,
ArrayItems,
},
})
export default {
components: {
FormProvider,
FormButtonGroup,
Button,
Submit,
...SchemaField,
},
data() {
const form = createForm()
return {
form,
}
},
methods: {
log(values) {
console.log(values)
},
},
}
</script>
<style lang="scss" scoped></style>
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/Reset.md:
--------------------------------------------------------------------------------
```markdown
# Reset
> Reset button
## Normal reset
> Controls with default values cannot be cleared
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="input box"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset>Reset</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## Force empty reset
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="input box"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset forceClear>Reset</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## Reset and verify
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="input box"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset validate>Reset</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## Force empty reset and verify
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="input box"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset forceClear validate>
Reset
</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## API
### Reset
Other API reference https://ant.design/components/button-cn/
| Property name | Type | Description | Default value |
| ---------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ------------- |
| onClick | `(event: MouseEvent) => void \| boolean` | Click event, if it returns false, it can block resetting | - |
| onResetValidateSuccess | (payload: any) => void | Reset validation success event | - |
| onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Reset validation failure event | - |
```
--------------------------------------------------------------------------------
/packages/next/docs/components/Reset.md:
--------------------------------------------------------------------------------
```markdown
# Reset
> Reset button
## Normal reset
> Controls with default values cannot be cleared
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="input box"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset>Reset</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## Force empty reset
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="input box"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset forceClear>Reset</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## Reset and verify
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="input box"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset validate>Reset</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## Force empty reset and verify
```tsx
import React from 'react'
import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="input"
title="input box"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="input2"
title="input box"
default="123"
required
x-decorator="FormItem"
x-component="Input"
/>
</SchemaField>
<FormButtonGroup>
<Reset forceClear validate>
Reset
</Reset>
</FormButtonGroup>
</FormProvider>
)
```
## API
### Reset
Other API reference https://fusion.design/pc/component/basic/button
| Property name | Type | Description | Default value |
| ---------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ------------- |
| onClick | `(event: MouseEvent) => void \| boolean` | Click event, if it returns false, it can block resetting | - |
| onResetValidateSuccess | (payload: any) => void | Reset validation success event | - |
| onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Reset validation failure event | - |
```
--------------------------------------------------------------------------------
/packages/element/src/form-step/index.ts:
--------------------------------------------------------------------------------
```typescript
import { Form, VoidField } from '@formily/core'
import { Schema, SchemaKey } from '@formily/json-schema'
import { action, model, observable } from '@formily/reactive'
import { observer } from '@formily/reactive-vue'
import {
Fragment,
h,
RecursionField,
useField,
useFieldSchema,
} from '@formily/vue'
import { Step, Steps } from 'element-ui'
import { defineComponent, PropType } from 'vue-demi'
import { stylePrefix } from '../__builtins__/configs'
import type { Step as StepProps, Steps as StepsProps } from 'element-ui'
import { composeExport } from '../__builtins__/shared'
export interface IFormStep {
connect: (steps: SchemaStep[], field: VoidField) => void
current: number
allowNext: boolean
allowBack: boolean
setCurrent(key: number): void
submit: Form['submit']
next(): void
back(): void
}
export interface IFormStepProps extends StepsProps {
formStep?: IFormStep
}
type SchemaStep = {
name: SchemaKey
props: any
schema: Schema
}
type FormStepEnv = {
form: Form
field: VoidField
steps: SchemaStep[]
}
const parseSteps = (schema: Schema) => {
const steps: SchemaStep[] = []
schema.mapProperties((schema, name) => {
if (schema['x-component']?.indexOf('StepPane') > -1) {
steps.push({
name,
props: schema['x-component-props'],
schema,
})
}
})
return steps
}
const createFormStep = (defaultCurrent = 0): IFormStep => {
const env: FormStepEnv = observable({
form: null,
field: null,
steps: [],
})
const setDisplay = action.bound((target: number) => {
const currentStep = env.steps[target]
env.steps.forEach(({ name }) => {
env.form.query(`${env.field.address}.${name}`).take((field) => {
if (name === currentStep.name) {
field.setDisplay('visible')
} else {
field.setDisplay('hidden')
}
})
})
})
const next = action.bound(() => {
if (formStep.allowNext) {
setDisplay(formStep.current + 1)
formStep.setCurrent(formStep.current + 1)
}
})
const back = action.bound(() => {
if (formStep.allowBack) {
setDisplay(formStep.current - 1)
formStep.setCurrent(formStep.current - 1)
}
})
const formStep: IFormStep = model({
connect(steps, field) {
env.steps = steps
env.form = field?.form
env.field = field
},
current: defaultCurrent,
setCurrent(key: number) {
formStep.current = key
},
get allowNext() {
return formStep.current < env.steps.length - 1
},
get allowBack() {
return formStep.current > 0
},
async next() {
try {
await env.form.validate()
next()
} catch {}
},
async back() {
back()
},
async submit(onSubmit) {
return env.form?.submit?.(onSubmit)
},
})
return formStep
}
const FormStepInner = observer(
defineComponent<IFormStepProps>({
name: 'FFormStep',
props: {
formStep: {
type: Object as PropType<IFormStep>,
default() {
return {
current: 0,
}
},
},
},
setup(props, { attrs }) {
const field = useField<VoidField>().value
const prefixCls = `${stylePrefix}-form-step`
const fieldSchemaRef = useFieldSchema()
const steps = parseSteps(fieldSchemaRef.value)
props.formStep.connect?.(steps, field)
return () => {
const current = props.active || props.formStep?.current || 0
const renderSteps = (steps: SchemaStep[], callback) => {
return steps.map(callback)
}
return h(
'div',
{
class: [prefixCls],
},
{
default: () => [
h(
Steps,
{
props: {
active: current,
},
style: [{ marginBottom: '10px' }, attrs.style],
attrs,
},
{
default: () =>
renderSteps(steps, ({ props }, key) => {
return h(Step, { props, key }, {})
}),
}
),
renderSteps(steps, ({ name, schema }, key) => {
if (key !== current) return
return h(RecursionField, { props: { name, schema }, key }, {})
}),
],
}
)
}
},
})
)
const StepPane = defineComponent<StepProps>({
name: 'FFormStepPane',
setup(_props, { slots }) {
return () => h(Fragment, {}, slots)
},
})
export const FormStep = composeExport(FormStepInner, {
StepPane,
createFormStep,
})
export default FormStep
```
--------------------------------------------------------------------------------
/packages/reactive/docs/guide/concept.md:
--------------------------------------------------------------------------------
```markdown
# Core idea
## Observable
Observable is the most important part of the reactive programming model. Its core concepts are:
An observable object, literally means a subscribable object, **we create a subscribable object, each time we manipulate the attribute data of the object, we will automatically notify the subscriber**, @formily/reactive creates an observable object mainly It is created by ES Proxy, which can perfectly hijack data operations
We mainly use the following APIs to create observable objects in @formily/reactive:
- The observable function creates a deep observable object
- The observable.deep function creates a deep hijacking observable object
- The observable.shallow function creates shallow hijacked observable objects
- The observable.computed function creates a cache calculator
- The observable.box function creates observable objects with get/set methods
- The observable.ref function creates a reference-level observable object
- The define function defines the observable domain model, which can be combined with the observable function and its static attribute (such as observable.computed) function to complete the definition of the domain model
- The model function defines an automatic observable domain model. It will wrap the getter setter attribute as a computed attribute, wrap the function as an action, and wrap other data attributes with observable (note that this is a deep hijacking)
## Reaction
In the reactive programming model, reaction is equivalent to the subscriber of the subscribeable object. It receives a tracker function. When this function is executed, if there is a **read operation* on an attribute in the observable object inside the function. * (Dependency collection), then the current reaction will be bound to the attribute (dependency tracking), until the attribute has a **write operation\*\* in other places, it will trigger the tracker function to repeat execution, using a picture Means:

You can see that from subscribing to dispatching subscriptions, it is actually a closed loop state machine. Each time the tracker function is executed, the dependencies are re-collected, and the tracker execution is re-triggered when the dependencies change. So, if we don't want to subscribe to the reaction anymore, we must manually dispose, otherwise there will be memory leaks.
In @formily/reactive, we mainly use the following APIs to create reactions:
- autorun creates an automatically executed responder
- reaction creates a responder that can implement dirty checks
- Tracker creates a dependency tracker that requires users to manually perform tracking
## Computed
Computed is also a relatively important concept in the reactive programming model. In one sentence, **computed is a Reaction that can cache calculation results**
Its caching strategy is: as long as the observable data that the computed function relies on changes, the function will re-execute the calculation, otherwise the cached result will always be read
The requirement here is that the computed function must be a pure function. The internally dependent data is either observable data or external constant data. If it is external variable data (non-observable), then if the external variable data changes, the computed will not be re-executed computational.
## Batch
As mentioned earlier, @formily/reactive is a reactive programming model based on Proxy hijacking. Therefore, any atomic operation will trigger the execution of Reaction, which is obviously a waste of computing resources. For example, we have a function for multiple observables. Property to operate:
```ts
import { observable, autorun } from '@formily/reactive'
const obs = observable({})
const handler = () => {
obs.aa = 123
obs.bb = 321
}
autorun(() => {
console.log(obs.aa, obs.bb)
})
handler()
```
This will execute 3 prints, autorun is executed once by default, plus the assignment of obs.aa is executed once, and the assignment of obs.bb is executed once. If there are more atomic operations, the number of executions will be more. Therefore, we recommend using batch mode To merge the updates:
```ts
import { observable, autorun, batch } from '@formily/reactive'
const obs = observable({})
const handler = () => {
obs.aa = 123
obs.bb = 321
}
autorun(() => {
console.log(obs.aa, obs.bb)
})
batch(() => {
handler()
})
```
Of course, we can also use action for high-level packaging:
```ts
import { observable, autorun, action } from '@formily/reactive'
const obs = observable({})
const handler = action.bound(() => {
obs.aa = 123
obs.bb = 321
})
autorun(() => {
console.log(obs.aa, obs.bb)
})
handler()
```
The final number of executions is 2 times, even if there are more operations inside the handler, it is still 2 times
```
--------------------------------------------------------------------------------
/packages/antd/src/form-collapse/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { Fragment, useMemo } from 'react'
import { Collapse, Badge } from 'antd'
import { model, markRaw } from '@formily/reactive'
import { CollapseProps, CollapsePanelProps } from 'antd/lib/collapse'
import {
useField,
observer,
useFieldSchema,
RecursionField,
} from '@formily/react'
import { Schema, SchemaKey } from '@formily/json-schema'
import cls from 'classnames'
import { usePrefixCls } from '../__builtins__'
import { toArr } from '@formily/shared'
type ActiveKeys = string | number | Array<string | number>
type ActiveKey = string | number
export interface IFormCollapse {
activeKeys: ActiveKeys
hasActiveKey(key: ActiveKey): boolean
setActiveKeys(key: ActiveKeys): void
addActiveKey(key: ActiveKey): void
removeActiveKey(key: ActiveKey): void
toggleActiveKey(key: ActiveKey): void
}
export interface IFormCollapseProps extends CollapseProps {
formCollapse?: IFormCollapse
}
type ComposedFormCollapse = React.FC<
React.PropsWithChildren<IFormCollapseProps>
> & {
CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>>
createFormCollapse?: (defaultActiveKeys?: ActiveKeys) => IFormCollapse
}
const usePanels = () => {
const collapseField = useField()
const schema = useFieldSchema()
const panels: { name: SchemaKey; props: any; schema: Schema }[] = []
schema.mapProperties((schema, name) => {
const field = collapseField.query(collapseField.address.concat(name)).take()
if (field?.display === 'none' || field?.display === 'hidden') return
if (schema['x-component']?.indexOf('CollapsePanel') > -1) {
const componentProps = field?.componentProps
panels.push({
name,
props: {
...componentProps,
key: componentProps?.key || name,
},
schema,
})
}
})
return panels
}
const createFormCollapse = (defaultActiveKeys?: ActiveKeys) => {
const formCollapse = model({
activeKeys: defaultActiveKeys,
setActiveKeys(keys: ActiveKeys) {
formCollapse.activeKeys = keys
},
hasActiveKey(key: ActiveKey) {
if (Array.isArray(formCollapse.activeKeys)) {
if (formCollapse.activeKeys.includes(key)) {
return true
}
} else if (formCollapse.activeKeys == key) {
return true
}
return false
},
addActiveKey(key: ActiveKey) {
if (formCollapse.hasActiveKey(key)) return
formCollapse.activeKeys = toArr(formCollapse.activeKeys).concat(key)
},
removeActiveKey(key: ActiveKey) {
if (Array.isArray(formCollapse.activeKeys)) {
formCollapse.activeKeys = formCollapse.activeKeys.filter(
(item) => item != key
)
} else {
formCollapse.activeKeys = ''
}
},
toggleActiveKey(key: ActiveKey) {
if (formCollapse.hasActiveKey(key)) {
formCollapse.removeActiveKey(key)
} else {
formCollapse.addActiveKey(key)
}
},
})
return markRaw(formCollapse)
}
export const FormCollapse: ComposedFormCollapse = observer(
({ formCollapse, ...props }) => {
const field = useField()
const panels = usePanels()
const prefixCls = usePrefixCls('formily-collapse', props)
const _formCollapse = useMemo(() => {
return formCollapse
? formCollapse
: createFormCollapse(props.defaultActiveKey)
}, [])
const takeActiveKeys = () => {
if (props.activeKey) return props.activeKey
if (_formCollapse?.activeKeys) return _formCollapse?.activeKeys
if (props.accordion) return panels[0]?.name
return panels.map((item) => item.name)
}
const badgedHeader = (key: SchemaKey, props: any) => {
const errors = field.form.queryFeedbacks({
type: 'error',
address: `${field.address.concat(key)}.*`,
})
if (errors.length) {
return (
<Badge size="small" className="errors-badge" count={errors.length}>
{props.header}
</Badge>
)
}
return props.header
}
return (
<Collapse
{...props}
className={cls(prefixCls, props.className)}
activeKey={takeActiveKeys()}
onChange={(key) => {
props?.onChange?.(key)
_formCollapse?.setActiveKeys?.(key)
}}
>
{panels.map(({ props, schema, name }, index) => (
<Collapse.Panel
key={index}
{...props}
header={badgedHeader(name, props)}
forceRender
>
<RecursionField schema={schema} name={name} />
</Collapse.Panel>
))}
</Collapse>
)
}
)
const CollapsePanel: React.FC<React.PropsWithChildren<CollapsePanelProps>> = ({
children,
}) => {
return <Fragment>{children}</Fragment>
}
FormCollapse.CollapsePanel = CollapsePanel
FormCollapse.createFormCollapse = createFormCollapse
export default FormCollapse
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-collapse/json-schema.vue:
--------------------------------------------------------------------------------
```vue
<template>
<FormProvider :form="form">
<SchemaField :schema="schema" />
<Submit @submit="log">提交</Submit>
</FormProvider>
</template>
<script>
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/vue'
import {
FormItem,
FormButtonGroup,
Submit,
Input,
ArrayCollapse,
} from '@formily/element'
import { Button } from 'element-ui'
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
ArrayCollapse,
},
})
export default {
components: {
FormProvider,
FormButtonGroup,
Button,
Submit,
...SchemaField,
},
data() {
const form = createForm()
const schema = {
type: 'object',
properties: {
string_array: {
type: 'array',
'x-component': 'ArrayCollapse',
maxItems: 3,
'x-decorator': 'FormItem',
items: {
type: 'object',
'x-component': 'ArrayCollapse.Item',
'x-component-props': {
title: '字符串数组',
},
properties: {
index: {
type: 'void',
'x-component': 'ArrayCollapse.Index',
},
input: {
type: 'string',
'x-decorator': 'FormItem',
title: 'Input',
required: true,
'x-component': 'Input',
},
remove: {
type: 'void',
'x-component': 'ArrayCollapse.Remove',
},
moveUp: {
type: 'void',
'x-component': 'ArrayCollapse.MoveUp',
},
moveDown: {
type: 'void',
'x-component': 'ArrayCollapse.MoveDown',
},
},
},
properties: {
addition: {
type: 'void',
title: '添加条目',
'x-component': 'ArrayCollapse.Addition',
},
},
},
array: {
type: 'array',
'x-component': 'ArrayCollapse',
maxItems: 3,
'x-decorator': 'FormItem',
items: {
type: 'object',
'x-component': 'ArrayCollapse.Item',
'x-component-props': {
title: '对象数组',
},
properties: {
index: {
type: 'void',
'x-component': 'ArrayCollapse.Index',
},
input: {
type: 'string',
'x-decorator': 'FormItem',
title: 'Input',
required: true,
'x-component': 'Input',
},
remove: {
type: 'void',
'x-component': 'ArrayCollapse.Remove',
},
moveUp: {
type: 'void',
'x-component': 'ArrayCollapse.MoveUp',
},
moveDown: {
type: 'void',
'x-component': 'ArrayCollapse.MoveDown',
},
},
},
properties: {
addition: {
type: 'void',
title: '添加条目',
'x-component': 'ArrayCollapse.Addition',
},
},
},
array_unshift: {
type: 'array',
'x-component': 'ArrayCollapse',
maxItems: 3,
'x-decorator': 'FormItem',
items: {
type: 'object',
'x-component': 'ArrayCollapse.Item',
'x-component-props': {
title: '对象数组',
},
properties: {
index: {
type: 'void',
'x-component': 'ArrayCollapse.Index',
},
input: {
type: 'string',
'x-decorator': 'FormItem',
title: 'Input',
required: true,
'x-component': 'Input',
},
remove: {
type: 'void',
'x-component': 'ArrayCollapse.Remove',
},
moveUp: {
type: 'void',
'x-component': 'ArrayCollapse.MoveUp',
},
moveDown: {
type: 'void',
'x-component': 'ArrayCollapse.MoveDown',
},
},
},
properties: {
addition: {
type: 'void',
title: '添加条目(unshift)',
'x-component': 'ArrayCollapse.Addition',
'x-component-props': {
method: 'unshift',
},
},
},
},
},
}
return {
form,
schema,
}
},
methods: {
log(values) {
console.log(values)
},
},
}
</script>
<style lang="scss" scoped></style>
```
--------------------------------------------------------------------------------
/packages/element/src/array-items/index.ts:
--------------------------------------------------------------------------------
```typescript
import { ArrayField } from '@formily/core'
import { ISchema } from '@formily/json-schema'
import { observer } from '@formily/reactive-vue'
import { h, RecursionField, useField, useFieldSchema } from '@formily/vue'
import { defineComponent } from 'vue-demi'
import { SlickItem, SlickList } from 'vue-slicksort'
import { ArrayBase } from '../array-base'
import { stylePrefix } from '../__builtins__/configs'
import { composeExport } from '../__builtins__/shared'
const isAdditionComponent = (schema: ISchema) => {
return schema['x-component']?.indexOf('Addition') > -1
}
export interface IArrayItemsItemProps {
type?: 'card' | 'divide'
}
const ArrayItemsInner = observer(
defineComponent({
name: 'FArrayItems',
setup() {
const fieldRef = useField<ArrayField>()
const schemaRef = useFieldSchema()
const prefixCls = `${stylePrefix}-array-items`
const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value)
return () => {
const field = fieldRef.value
const schema = schemaRef.value
const dataSource = Array.isArray(field.value) ? field.value.slice() : []
const renderItems = () => {
const items = dataSource?.map((item, index) => {
const items = Array.isArray(schema.items)
? schema.items[index] || schema.items[0]
: schema.items
const key = getKey(item, index)
return h(
ArrayBase.Item,
{
key,
props: {
index,
record: item,
},
},
{
default: () =>
h(
SlickItem,
{
class: [`${prefixCls}-item-inner`],
props: {
index,
},
key,
},
{
default: () =>
h(
RecursionField,
{
props: {
schema: items,
name: index,
},
},
{}
),
}
),
}
)
})
return h(
SlickList,
{
class: [`${prefixCls}-list`],
props: {
useDragHandle: true,
lockAxis: 'y',
helperClass: `${prefixCls}-sort-helper`,
value: [],
},
on: {
'sort-end': ({ oldIndex, newIndex }) => {
if (Array.isArray(keyMap)) {
keyMap.splice(newIndex, 0, keyMap.splice(oldIndex, 1)[0])
}
field.move(oldIndex, newIndex)
},
},
},
{ default: () => items }
)
}
const renderAddition = () => {
return schema.reduceProperties((addition, schema) => {
if (isAdditionComponent(schema)) {
return h(
RecursionField,
{
props: {
schema,
name: 'addition',
},
},
{}
)
}
return addition
}, null)
}
return h(
ArrayBase,
{
props: {
keyMap,
},
},
{
default: () =>
h(
'div',
{
class: [prefixCls],
on: {
change: () => {},
},
},
{
default: () => [renderItems(), renderAddition()],
}
),
}
)
}
},
})
)
const ArrayItemsItem = defineComponent<IArrayItemsItemProps>({
name: 'FArrayItemsItem',
props: ['type'],
setup(props, { attrs, slots }) {
const prefixCls = `${stylePrefix}-array-items`
return () =>
h(
'div',
{
class: [`${prefixCls}-${props.type || 'card'}`],
attrs: {
...attrs,
},
on: {
change: () => {},
},
},
slots
)
},
})
export const ArrayItems = composeExport(ArrayItemsInner, {
Item: ArrayItemsItem,
Index: ArrayBase.Index,
SortHandle: ArrayBase.SortHandle,
Addition: ArrayBase.Addition,
Remove: ArrayBase.Remove,
MoveDown: ArrayBase.MoveDown,
MoveUp: ArrayBase.MoveUp,
useArray: ArrayBase.useArray,
useIndex: ArrayBase.useIndex,
useRecord: ArrayBase.useRecord,
})
export default ArrayItems
```
--------------------------------------------------------------------------------
/packages/next/src/form-collapse/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { Fragment, useMemo } from 'react'
import { Collapse, Badge } from '@alifd/next'
import { model, markRaw } from '@formily/reactive'
import {
CollapseProps,
PanelProps as CollapsePanelProps,
} from '@alifd/next/lib/collapse'
import {
useField,
observer,
useFieldSchema,
RecursionField,
} from '@formily/react'
import { Schema, SchemaKey } from '@formily/json-schema'
import { toArr } from '@formily/shared'
import cls from 'classnames'
import { usePrefixCls } from '../__builtins__'
type ActiveKeys = string | number | Array<string | number>
type ActiveKey = string | number
export interface IFormCollapse {
activeKeys: ActiveKeys
hasActiveKey(key: ActiveKey): boolean
setActiveKeys(key: ActiveKeys): void
addActiveKey(key: ActiveKey): void
removeActiveKey(key: ActiveKey): void
toggleActiveKey(key: ActiveKey): void
}
export interface IFormCollapseProps extends CollapseProps {
formCollapse?: IFormCollapse
}
type ComposedFormCollapse = React.FC<
React.PropsWithChildren<IFormCollapseProps>
> & {
CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>>
createFormCollapse?: (
defaultActiveKeys?: CollapseProps['expandedKeys']
) => IFormCollapse
}
const usePanels = () => {
const collapseField = useField()
const schema = useFieldSchema()
const panels: { name: SchemaKey; props: any; schema: Schema }[] = []
schema.mapProperties((schema, name) => {
const field = collapseField.query(collapseField.address.concat(name)).take()
if (field?.display === 'none' || field?.display === 'hidden') return
if (schema['x-component']?.indexOf('CollapsePanel') > -1) {
panels.push({
name,
props: {
...schema?.['x-component-props'],
title: schema?.['x-component-props']?.title || schema?.title,
key: schema?.['x-component-props']?.key || name,
},
schema,
})
}
})
return panels
}
const createFormCollapse = (defaultActiveKeys?: ActiveKeys) => {
const formCollapse = model({
activeKeys: defaultActiveKeys,
setActiveKeys(keys: ActiveKeys) {
formCollapse.activeKeys = keys
},
hasActiveKey(key: ActiveKey) {
if (Array.isArray(formCollapse.activeKeys)) {
if (formCollapse.activeKeys.includes(key)) {
return true
}
} else if (formCollapse.activeKeys == key) {
return true
}
return false
},
addActiveKey(key: ActiveKey) {
if (formCollapse.hasActiveKey(key)) return
formCollapse.activeKeys = toArr(formCollapse.activeKeys).concat(key)
},
removeActiveKey(key: ActiveKey) {
if (Array.isArray(formCollapse.activeKeys)) {
formCollapse.activeKeys = formCollapse.activeKeys.filter(
(item) => item != key
)
} else {
formCollapse.activeKeys = []
}
},
toggleActiveKey(key: ActiveKey) {
if (formCollapse.hasActiveKey(key)) {
formCollapse.removeActiveKey(key)
} else {
formCollapse.addActiveKey(key)
}
},
})
return markRaw(formCollapse)
}
export const FormCollapse: ComposedFormCollapse = observer(
({ formCollapse, ...props }) => {
const field = useField()
const panels = usePanels()
const prefixCls = usePrefixCls('formily-collapse', props)
const _formCollapse = useMemo(() => {
return formCollapse
? formCollapse
: createFormCollapse(props.defaultExpandedKeys)
}, [])
const takeExpandedKeys = () => {
if (props.expandedKeys) return props.expandedKeys
if (_formCollapse?.activeKeys) return _formCollapse?.activeKeys
if (props.accordion) return panels[0]?.name
return panels.map((item) => item.name)
}
const badgedHeader = (key: SchemaKey, props: any) => {
const errors = field.form.queryFeedbacks({
type: 'error',
address: `${field.address.concat(key)}.*`,
})
if (errors.length) {
return (
<Badge className="errors-badge" count={errors.length}>
{props.title}
</Badge>
)
}
return props.title
}
return (
<Collapse
{...props}
className={cls(prefixCls, props.className)}
expandedKeys={takeExpandedKeys() as any}
onExpand={(keys) => {
props?.onExpand?.(keys)
_formCollapse?.setActiveKeys?.(keys)
}}
>
{panels.map(({ props, schema, name }, index) => (
<Collapse.Panel
key={index}
{...props}
title={badgedHeader(name, props)}
>
<RecursionField schema={schema} name={name} />
</Collapse.Panel>
))}
</Collapse>
)
}
)
const CollapsePanel: React.FC<React.PropsWithChildren<CollapsePanelProps>> = ({
children,
}) => {
return <Fragment>{children}</Fragment>
}
FormCollapse.CollapsePanel = CollapsePanel
FormCollapse.createFormCollapse = createFormCollapse
export default FormCollapse
```
--------------------------------------------------------------------------------
/packages/react/src/types.ts:
--------------------------------------------------------------------------------
```typescript
import React from 'react'
import {
Form,
Field as FieldType,
VoidField,
ObjectField,
GeneralField,
IFieldFactoryProps,
IVoidFieldFactoryProps,
FormPatternTypes,
FieldDisplayTypes,
FieldValidator,
} from '@formily/core'
import { ReactFC } from '@formily/reactive-react'
import { ISchema, Schema, SchemaKey } from '@formily/json-schema'
import { FormPathPattern } from '@formily/shared'
export type JSXComponent =
| keyof JSX.IntrinsicElements
| React.JSXElementConstructor<any>
export type IProviderProps = {
form: Form
}
export interface IFormSpyProps {
children?: (form: Form) => ReactChild
}
export type RenderPropsChildren<Payload> =
| ((field: Payload, form: Form) => React.ReactNode)
| React.ReactNode
export interface IFieldProps<
D extends JSXComponent,
C extends JSXComponent,
Field = FieldType
> extends IFieldFactoryProps<D, C> {
children?: RenderPropsChildren<Field>
decorator?: [] | [D] | [D, React.ComponentProps<D>] | any[]
component?: [] | [C] | [C, React.ComponentProps<C>] | any[]
}
export interface IVoidFieldProps<
D extends JSXComponent,
C extends JSXComponent,
Field = VoidField
> extends IVoidFieldFactoryProps<D, C> {
children?: RenderPropsChildren<Field>
decorator?: [] | [D] | [D, React.ComponentProps<D>] | any[]
component?: [] | [C] | [C, React.ComponentProps<C>] | any[]
}
export interface IComponentMapper<T extends JSXComponent> {
(target: T): JSXComponent
}
export type IStateMapper<Props> =
| {
[key in keyof FieldType]?: keyof Props | boolean
}
| ((props: Props, field: GeneralField) => Props)
export type SchemaReactComponents = Record<string, JSXComponent>
export interface ISchemaFieldReactFactoryOptions<
Components extends SchemaReactComponents = any
> {
components?: Components
scope?: any
}
export interface ISchemaFieldOptionContext {
components: SchemaReactComponents
}
export interface ISchemaFieldProps<
Decorator extends JSXComponent = any,
Component extends JSXComponent = any,
InnerField = ObjectField<Decorator, Component>
> extends Omit<IFieldFactoryProps<Decorator, Component, InnerField>, 'name'> {
schema?: ISchema
components?: {
[key: string]: JSXComponent
}
scope?: any
name?: SchemaKey
children?: React.ReactNode
}
export interface ISchemaMapper {
(schema: Schema, name: SchemaKey): Schema
}
export interface ISchemaFilter {
(schema: Schema, name: SchemaKey): boolean
}
export interface IRecursionFieldProps {
schema: ISchema
name?: SchemaKey
basePath?: FormPathPattern
propsRecursion?: boolean
onlyRenderProperties?: boolean
onlyRenderSelf?: boolean
mapProperties?: ISchemaMapper
filterProperties?: ISchemaFilter
}
export type ObjectKey = string | number | boolean | symbol
export type Path<T, Key extends keyof T = keyof T> = Key extends string
? T[Key] extends Record<string, any>
?
| `${Key}.${Path<T[Key], Exclude<keyof T[Key], keyof Array<any>>> &
string}`
| `${Key}.${Exclude<keyof T[Key], keyof Array<any>> & string}`
| Key
: Key
: never
export type PathValue<
T,
P extends Path<T>
> = P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? Rest extends Path<T[Key]>
? PathValue<T[Key], Rest>
: never
: never
: P extends keyof T
? T[P]
: never
export type KeyOfReactComponent<T> = Exclude<
keyof T,
'contextTypes' | 'displayName' | 'propTypes' | 'defaultProps'
>
export type ReactComponentPath<
T,
Key extends KeyOfReactComponent<T> = KeyOfReactComponent<T>
> = Key extends string
? T[Key] extends Record<string, any>
?
| `${Key}.${Exclude<KeyOfReactComponent<T[Key]>, keyof Array<any>> &
string}`
| Key
: Key
: never
export type ReactComponentPropsByPathValue<
T extends Record<string, any>,
P extends ReactComponentPath<T>
> = P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? Rest extends ReactComponentPath<T[Key]>
? ReactComponentPropsByPathValue<T[Key], Rest>
: never
: React.ComponentProps<T[P]>
: P extends keyof T
? React.ComponentProps<T[P]>
: never
export interface ISchemaMarkupFieldProps<
Components extends SchemaReactComponents,
Decorator extends ReactComponentPath<Components>,
Component extends ReactComponentPath<Components>
> extends ISchema<
Decorator,
Component,
ReactComponentPropsByPathValue<Components, Decorator>,
ReactComponentPropsByPathValue<Components, Component>,
FormPatternTypes,
FieldDisplayTypes,
FieldValidator,
React.ReactNode,
GeneralField
> {
children?: React.ReactNode
}
export type ISchemaTypeFieldProps<
Components extends SchemaReactComponents,
Decorator extends ReactComponentPath<Components>,
Component extends ReactComponentPath<Components>
> = ISchemaMarkupFieldProps<Components, Decorator, Component>
export interface IExpressionScopeProps {
value?: any
}
export interface IRecordScopeProps {
getIndex?(): number
getRecord(): any
}
export interface IRecordsScopeProps {
getRecords(): any[]
}
export type ReactChild = React.ReactElement | string | number
export { ReactFC }
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/form-grid/form.vue:
--------------------------------------------------------------------------------
```vue
<template>
<FormProvider :form="form">
<SchemaField>
<SchemaObjectField x-component="QueryForm">
<SchemaStringField
name="input1"
title="Input 1"
x-component="Input"
x-decorator="FormItem"
/>
<SchemaStringField
name="input2"
title="Input 2"
x-component="Input"
x-decorator="FormItem"
/>
<SchemaStringField
name="select1"
title="Select 1"
x-component="Select"
x-decorator="FormItem"
/>
<SchemaStringField
name="select2"
title="Select 2"
x-component="Select"
x-decorator="FormItem"
/>
<SchemaStringField
name="date"
title="DatePicker"
x-component="DatePicker"
x-decorator="FormItem"
/>
<SchemaStringField
name="dateRange"
title="DatePicker"
x-component="DatePicker"
x-decorator="FormItem"
:x-decorator-props="{
gridSpan: 2,
}"
:x-component-props="{
type: 'daterange',
}"
/>
<SchemaStringField
name="select3"
title="Select 3"
x-component="Select"
x-decorator="FormItem"
/>
</SchemaObjectField>
</SchemaField>
</FormProvider>
</template>
<script>
import { defineComponent, ref, onUnmounted } from 'vue-demi'
import { createForm } from '@formily/core'
import {
createSchemaField,
FormProvider,
FragmentComponent,
} from '@formily/vue'
import { autorun } from '@formily/reactive'
import { observer } from '@formily/reactive-vue'
import {
Form,
Input,
Select,
DatePicker,
FormItem,
FormGrid,
Submit,
Reset,
FormButtonGroup,
} from '@formily/element'
const useCollapseGrid = (maxRows) => {
const grid = FormGrid.createFormGrid({
maxColumns: 4,
maxWidth: 240,
maxRows: maxRows,
shouldVisible: (node, grid) => {
if (node.index === grid.childSize - 1) return true
if (grid.maxRows === Infinity) return true
return node.shadowRow < maxRows + 1
},
})
const expanded = ref(false)
const type = ref('')
const takeType = (realRows, computeRows) => {
if (realRows < maxRows + 1) return 'incomplete-wrap'
if (computeRows > maxRows) return 'collapsible'
return 'complete-wrap'
}
const dispose = autorun(() => {
expanded.value = grid.maxRows === Infinity
const realRows = grid.shadowRows
const computeRows = grid.fullnessLastColumn
? grid.shadowRows - 1
: grid.shadowRows
type.value = takeType(realRows, computeRows)
})
onUnmounted(dispose)
const toggle = () => {
if (grid.maxRows === Infinity) {
grid.maxRows = maxRows
} else {
grid.maxRows = Infinity
}
}
return {
grid,
expanded,
toggle,
type,
}
}
const QueryForm = observer(
defineComponent({
setup(props, { slots }) {
const { grid, expanded, toggle, type } = useCollapseGrid(1)
const renderActions = () => {
return (
<FragmentComponent>
<Submit onSubmit={console.log}>查询</Submit>
<Reset>重置</Reset>
</FragmentComponent>
)
}
const renderButtonGroup = () => {
if (type.value === 'incomplete-wrap') {
return (
<FormButtonGroup.FormItem>
<FormButtonGroup>{renderActions()}</FormButtonGroup>
</FormButtonGroup.FormItem>
)
}
if (type.value === 'collapsible') {
return (
<FragmentComponent>
<FormButtonGroup>
<a
href=""
onClick={(e) => {
e.preventDefault()
toggle()
}}
>
{expanded.value ? '收起' : '展开'}
</a>
</FormButtonGroup>
<FormButtonGroup align="right">{renderActions()}</FormButtonGroup>
</FragmentComponent>
)
}
return (
<FormButtonGroup
align="right"
style={{ display: 'flex', width: '100%' }}
>
{renderActions()}
</FormButtonGroup>
)
}
return () => {
return (
<Form {...props} layout="vertical" feedbackLayout="terse">
<FormGrid grid={grid}>
{slots.default()}
<FormGrid.GridColumn
gridSpan={-1}
style={{ display: 'flex', justifyContent: 'space-between' }}
>
{renderButtonGroup()}
</FormGrid.GridColumn>
</FormGrid>
</Form>
)
}
},
})
)
const form = createForm()
const fields = createSchemaField({
components: {
QueryForm,
Input,
Select,
DatePicker,
FormItem,
},
})
export default {
components: { FormProvider, ...fields, Submit },
data() {
return {
form,
}
},
methods: {
onSubmit(value) {
console.log(value)
},
},
}
</script>
```
--------------------------------------------------------------------------------
/packages/json-schema/src/shared.ts:
--------------------------------------------------------------------------------
```typescript
import { isFn, each, isPlainObj, isArr, toArr, FormPath } from '@formily/shared'
import { isObservable, untracked } from '@formily/reactive'
import { Schema } from './schema'
import { ISchema } from './types'
const REVA_ACTIONS_KEY = Symbol.for('__REVA_ACTIONS')
export const SchemaNestedMap = {
parent: true,
root: true,
properties: true,
patternProperties: true,
additionalProperties: true,
items: true,
additionalItems: true,
'x-linkages': true,
'x-reactions': true,
}
export const SchemaStateMap = {
title: 'title',
description: 'description',
default: 'initialValue',
enum: 'dataSource',
readOnly: 'readOnly',
writeOnly: 'editable',
'x-content': 'content',
'x-data': 'data',
'x-value': 'value',
'x-editable': 'editable',
'x-disabled': 'disabled',
'x-read-pretty': 'readPretty',
'x-read-only': 'readOnly',
'x-visible': 'visible',
'x-hidden': 'hidden',
'x-display': 'display',
'x-pattern': 'pattern',
'x-validator': 'validator',
'x-decorator': 'decoratorType',
'x-component': 'componentType',
'x-decorator-props': 'decoratorProps',
'x-component-props': 'componentProps',
}
export const SchemaValidatorMap = {
required: true,
format: true,
maxItems: true,
minItems: true,
maxLength: true,
minLength: true,
maximum: true,
minimum: true,
exclusiveMaximum: true,
exclusiveMinimum: true,
pattern: true,
const: true,
multipleOf: true,
maxProperties: true,
minProperties: true,
uniqueItems: true,
}
export const SchemaNormalKeys = Object.keys(SchemaStateMap)
export const SchemaValidatorKeys = Object.keys(SchemaValidatorMap)
export const hasOwnProperty = Object.prototype.hasOwnProperty
export const traverse = (
target: any,
visitor: (value: any, path: Array<string | number>) => void
) => {
const seenObjects = []
const root = target
const traverse = (target: any, path = []) => {
if (isPlainObj(target)) {
const seenIndex = seenObjects.indexOf(target)
if (seenIndex > -1) {
return
}
const addIndex = seenObjects.length
seenObjects.push(target)
if (isNoNeedCompileObject(target) && root !== target) {
visitor(target, path)
return
}
each(target, (value, key) => {
traverse(value, path.concat(key))
})
seenObjects.splice(addIndex, 1)
} else {
visitor(target, path)
}
}
traverse(target)
}
export const traverseSchema = (
schema: ISchema,
visitor: (value: any, path: any[], omitCompile?: boolean) => void
) => {
if (schema['x-validator'] !== undefined) {
visitor(
schema['x-validator'],
['x-validator'],
schema['x-compile-omitted']?.includes('x-validator')
)
}
const seenObjects = []
const root = schema
const traverse = (target: any, path = []) => {
if (
path[0] === 'x-compile-omitted' ||
path[0] === 'x-validator' ||
path[0] === 'version' ||
path[0] === '_isJSONSchemaObject'
)
return
if (String(path[0]).indexOf('x-') == -1 && isFn(target)) return
if (SchemaNestedMap[path[0]]) return
if (schema['x-compile-omitted']?.indexOf(path[0]) > -1) {
visitor(target, path, true)
return
}
if (isPlainObj(target)) {
if (path[0] === 'default' || path[0] === 'x-value') {
visitor(target, path)
return
}
const seenIndex = seenObjects.indexOf(target)
if (seenIndex > -1) {
return
}
const addIndex = seenObjects.length
seenObjects.push(target)
if (isNoNeedCompileObject(target) && root !== target) {
visitor(target, path)
return
}
each(target, (value, key) => {
traverse(value, path.concat(key))
})
seenObjects.splice(addIndex, 1)
} else {
visitor(target, path)
}
}
traverse(schema)
}
export const isNoNeedCompileObject = (source: any) => {
if ('$$typeof' in source && '_owner' in source) {
return true
}
if (source['_isAMomentObject']) {
return true
}
if (Schema.isSchemaInstance(source)) {
return true
}
if (source[REVA_ACTIONS_KEY]) {
return true
}
if (isFn(source['toJS'])) {
return true
}
if (isFn(source['toJSON'])) {
return true
}
if (isObservable(source)) {
return true
}
return false
}
export const createDataSource = (source: any[]) => {
return toArr(source).map((item) => {
if (typeof item === 'object') {
return item
} else {
return {
label: item,
value: item,
}
}
})
}
export const patchStateFormSchema = (
targetState: any,
pattern: any[],
compiled: any
) => {
untracked(() => {
const path = FormPath.parse(pattern)
const segments = path.segments
const key = segments[0]
const isEnum = key === 'enum' && isArr(compiled)
const schemaMapKey = SchemaStateMap[key]
if (schemaMapKey) {
FormPath.setIn(
targetState,
[schemaMapKey].concat(segments.slice(1)),
isEnum ? createDataSource(compiled) : compiled
)
} else {
const isValidatorKey = SchemaValidatorMap[key]
if (isValidatorKey) {
targetState['setValidatorRule']?.(key, compiled)
}
}
})
}
```
--------------------------------------------------------------------------------
/packages/next/docs/components/DatePicker.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# DatePicker
> 日期选择器
## Markup Schema 案例
```tsx
import React from 'react'
import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
DatePicker,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="date"
title="普通日期"
x-decorator="FormItem"
x-component="DatePicker"
/>
<SchemaField.String
name="week"
title="周选择"
x-decorator="FormItem"
x-component="DatePicker.WeekPicker"
/>
<SchemaField.String
name="month"
title="月选择"
x-decorator="FormItem"
x-component="DatePicker.MonthPicker"
/>
<SchemaField.String
name="year"
title="年选择"
x-decorator="FormItem"
x-component="DatePicker.YearPicker"
/>
<SchemaField.String
name="[startDate,endDate]"
title="日期范围"
x-decorator="FormItem"
x-component="DatePicker.RangePicker"
x-component-props={{
showTime: true,
}}
/>
<SchemaField.String
name="range_month"
title="月范围选择"
x-decorator="FormItem"
x-component="DatePicker.RangePicker"
x-component-props={{
type: 'month',
}}
/>
<SchemaField.String
name="range_year"
title="年范围选择"
x-decorator="FormItem"
x-component="DatePicker.RangePicker"
x-component-props={{
type: 'year',
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## JSON Schema 案例
```tsx
import React from 'react'
import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
DatePicker,
FormItem,
},
})
const form = createForm()
const schema = {
type: 'object',
properties: {
date: {
title: '普通日期',
'x-decorator': 'FormItem',
'x-component': 'DatePicker',
type: 'string',
},
week: {
title: '周选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.WeekPicker',
type: 'string',
},
month: {
title: '月选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.MonthPicker',
type: 'string',
},
year: {
title: '年选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.YearPicker',
type: 'string',
},
'[startDate,endDate]': {
title: '日期范围',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.RangePicker',
'x-component-props': {
showTime: true,
},
type: 'string',
},
range_month: {
title: '月范围选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.RangePicker',
'x-component-props': {
type: 'month',
},
type: 'string',
},
range_year: {
name: 'range_year',
title: '年范围选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.RangePicker',
'x-component-props': {
type: 'year',
},
type: 'string',
},
},
}
export default () => (
<FormProvider form={form}>
<SchemaField schema={schema} />
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## 纯 JSX 案例
```tsx
import React from 'react'
import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, Field } from '@formily/react'
const form = createForm()
export default () => (
<FormProvider form={form}>
<Field
name="date"
title="日期选择"
decorator={[FormItem]}
component={[DatePicker]}
/>
<Field
name="week"
title="周选择"
decorator={[FormItem]}
component={[DatePicker.WeekPicker]}
/>
<Field
name="quarter"
title="财年选择"
decorator={[FormItem]}
component={[DatePicker.MonthPicker]}
/>
<Field
name="year"
title="年选择"
decorator={[FormItem]}
component={[DatePicker.YearPicker]}
/>
<Field
name="[startDate,endDate]"
title="日期范围选择"
decorator={[FormItem]}
component={[DatePicker.RangePicker]}
/>
<Field
name="range_month"
title="月范围选择"
decorator={[FormItem]}
component={[
DatePicker.RangePicker,
{
type: 'month',
},
]}
/>
<Field
name="range_year"
title="年范围选择"
decorator={[FormItem]}
component={[
DatePicker.RangePicker,
{
type: 'year',
},
]}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## API
参考 https://fusion.design/pc/component/basic/date-picker
```