This is page 12 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/docs/components/DatePicker2.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# DatePicker2
> 日期选择器
## Markup Schema 案例
```tsx
import React from 'react'
import { DatePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
DatePicker2,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="date"
title="普通日期"
x-decorator="FormItem"
x-component="DatePicker2"
/>
<SchemaField.String
name="week"
title="周选择"
x-decorator="FormItem"
x-component="DatePicker2.WeekPicker"
/>
<SchemaField.String
name="month"
title="月选择"
x-decorator="FormItem"
x-component="DatePicker2.MonthPicker"
/>
<SchemaField.String
name="year"
title="年选择"
x-decorator="FormItem"
x-component="DatePicker2.YearPicker"
/>
<SchemaField.String
name="[startDate,endDate]"
title="日期范围"
x-decorator="FormItem"
x-component="DatePicker2.RangePicker"
x-component-props={{
showTime: true,
}}
/>
<SchemaField.String
name="range_month"
title="月范围选择"
x-decorator="FormItem"
x-component="DatePicker2.RangePicker"
x-component-props={{
mode: 'month',
}}
/>
<SchemaField.String
name="range_year"
title="年范围选择"
x-decorator="FormItem"
x-component="DatePicker2.RangePicker"
x-component-props={{
mode: 'year',
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## JSON Schema 案例
```tsx
import React from 'react'
import { DatePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
DatePicker2,
FormItem,
},
})
const form = createForm()
const schema = {
type: 'object',
properties: {
date: {
title: '普通日期',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2',
type: 'string',
},
week: {
title: '周选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.WeekPicker',
type: 'string',
},
month: {
title: '月选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.MonthPicker',
type: 'string',
},
year: {
title: '年选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.YearPicker',
type: 'string',
},
'[startDate,endDate]': {
title: '日期范围',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.RangePicker',
'x-component-props': {
showTime: true,
},
type: 'string',
},
range_month: {
title: '月范围选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.RangePicker',
'x-component-props': {
mode: 'month',
},
type: 'string',
},
range_year: {
name: 'range_year',
title: '年范围选择',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.RangePicker',
'x-component-props': {
mode: '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 { DatePicker2, 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={[DatePicker2]}
/>
<Field
name="week"
title="周选择"
decorator={[FormItem]}
component={[DatePicker2.WeekPicker]}
/>
<Field
name="quarter"
title="财年选择"
decorator={[FormItem]}
component={[DatePicker2.MonthPicker]}
/>
<Field
name="year"
title="年选择"
decorator={[FormItem]}
component={[DatePicker2.YearPicker]}
/>
<Field
name="[startDate,endDate]"
title="日期范围选择"
decorator={[FormItem]}
component={[DatePicker2.RangePicker]}
/>
<Field
name="range_month"
title="月范围选择"
decorator={[FormItem]}
component={[
DatePicker2.RangePicker,
{
mode: 'month',
},
]}
/>
<Field
name="range_year"
title="年范围选择"
decorator={[FormItem]}
component={[
DatePicker2.RangePicker,
{
mode: 'year',
},
]}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## API
参考 https://fusion.design/pc/component/basic/date-picker2
```
--------------------------------------------------------------------------------
/packages/next/src/upload/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { useEffect } from 'react'
import { Field } from '@formily/core'
import { useField } from '@formily/react'
import { reaction } from '@formily/reactive'
import { Upload as NextUpload, Button, Icon } from '@alifd/next'
import {
UploadProps as NextUploadProps,
CardProps,
} from '@alifd/next/lib/upload'
import { isArr, toArr } from '@formily/shared'
import { UPLOAD_PLACEHOLDER } from './placeholder'
type ExtendsUploadProps = NextUploadProps & {
textContent?: React.ReactNode
serviceErrorMessage?: string
}
type FileList = Parameters<ExtendsUploadProps['onChange']>[0]
type ComposedUpload = React.FC<React.PropsWithChildren<IUploadProps>> & {
Card?: React.FC<React.PropsWithChildren<ICardUploadProps>>
Dragger?: React.FC<React.PropsWithChildren<IUploadProps>>
}
type IExtendsUploadProps = {
value?: any[]
serviceErrorMessage?: string
onChange?: (...args: any) => void
formatter?: (...args: any) => any
}
export type IUploadProps = ExtendsUploadProps & { serviceErrorMessage?: string }
export type ICardUploadProps = CardProps & { serviceErrorMessage?: string }
const testOpts = (
ext: RegExp,
options: { exclude?: string[]; include?: string[] }
) => {
if (options && isArr(options.include)) {
return options.include.some((url) => ext.test(url))
}
if (options && isArr(options.exclude)) {
return !options.exclude.some((url) => ext.test(url))
}
return true
}
const getImageByUrl = (url: string, options: any) => {
for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i++) {
if (
UPLOAD_PLACEHOLDER[i].ext.test(url) &&
testOpts(UPLOAD_PLACEHOLDER[i].ext, options)
) {
return UPLOAD_PLACEHOLDER[i].icon || url
}
}
return url
}
const getURL = (target: any) => {
return target?.['url'] || target?.['downloadURL'] || target?.['imgURL']
}
const getThumbURL = (target: any) => {
return (
target?.['thumbUrl'] ||
target?.['url'] ||
target?.['downloadURL'] ||
target?.['imgURL']
)
}
const getSuccess = (target: any) => {
return (
target?.success ||
target?.status === 'done' ||
target?.status === 'success' ||
target?.state === 'done' ||
target?.state === 'success'
)
}
const getErrorMessage = (target: any) => {
return (
target?.errorMessage ||
target?.errMsg ||
target?.errorMsg ||
target?.message ||
(typeof target?.error === 'string' ? target.error : '')
)
}
const getState = (target: any) => {
if (target?.success === false) return 'error'
if (target?.failed === true) return 'error'
if (target?.error) return 'error'
return target?.state || target?.status
}
const normalizeFileList = (fileList: IUploadProps['value']) => {
if (fileList && fileList.length) {
return fileList.map(({ ...file }, index) => {
delete file['originFileObj']
return {
...file,
uid: file.uid || index,
state: getState(file?.response) || getState(file),
downloadURL: getURL(file) || getURL(file?.response),
imgURL: getImageByUrl(
getThumbURL(file) || getThumbURL(file?.response),
{
exclude: ['.png', '.jpg', '.jpeg', '.gif'],
}
),
}
})
}
return []
}
const useValidator = (validator: (value: any) => string) => {
const field = useField<Field>()
useEffect(() => {
const dispose = reaction(
() => field.value,
(value) => {
const message = validator(value)
field.setFeedback({
type: 'error',
code: 'UploadError',
messages: message ? [message] : [],
})
}
)
return () => {
dispose()
}
}, [])
}
const useUploadValidator = (serviceErrorMessage = 'Upload Service Error') => {
useValidator((value) => {
const list = toArr(value)
for (let i = 0; i < list.length; i++) {
if (list[i]?.state === 'error') {
return (
getErrorMessage(list[i]?.response) ||
getErrorMessage(list[i]) ||
serviceErrorMessage
)
}
}
})
}
function useUploadProps<T extends IExtendsUploadProps = IUploadProps>({
serviceErrorMessage,
...props
}: T) {
useUploadValidator(serviceErrorMessage)
const onChange = (fileList: FileList) => {
props.onChange?.(normalizeFileList([...fileList]))
}
const formatter = (res: any, file: any) => {
const response = props?.formatter?.(res, file) as any
return {
...res,
success: getSuccess(res),
...response,
}
}
return {
...props,
value: normalizeFileList(props.value),
onChange,
formatter,
}
}
const getPlaceholder = (props: IUploadProps) => {
if (props.shape !== 'card') {
return (
<Button>
<Icon type="upload" />
{props.textContent}
</Button>
)
}
return <Icon type="upload" style={{ fontSize: 20 }} />
}
export const Upload: ComposedUpload = (props) => {
return (
<NextUpload listType="text" {...useUploadProps(props)}>
{props.children || getPlaceholder(props)}
</NextUpload>
)
}
Upload.Dragger = (props) => {
return <NextUpload.Dragger listType="text" {...useUploadProps(props)} />
}
Upload.Card = (props) => {
return <NextUpload.Card listType="card" {...useUploadProps(props)} />
}
export default Upload
```
--------------------------------------------------------------------------------
/packages/next/docs/components/PreviewText.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# PreviewText
> 阅读态组件,主要用来实现类 Input,类 DatePicker 这些组件的阅读态
## 简单用例
```tsx
import React from 'react'
import { PreviewText, FormItem, FormLayout } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
FormItem,
PreviewText,
},
})
const form = createForm()
export default () => {
return (
<FormLayout labelCol={8} wrapperCol={16}>
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
x-decorator="FormItem"
title="文本预览"
x-component="PreviewText.Input"
default={'Hello world'}
/>
<SchemaField.String
x-decorator="FormItem"
title="选择项预览"
x-component="PreviewText.Select"
x-component-props={{
mode: 'multiple',
}}
default={['123', '222']}
enum={[
{ label: 'A111', value: '123' },
{ label: 'A222', value: '222' },
]}
/>
<SchemaField.String
x-decorator="FormItem"
title="日期预览"
x-component="PreviewText.DatePicker"
default={'2020-11-23 22:15:20'}
/>
<SchemaField.String
x-decorator="FormItem"
title="Cascader预览"
x-component="PreviewText.Cascader"
default={'yuhang'}
enum={[
{
label: '杭州',
value: 'hangzhou',
children: [
{
label: '余杭',
value: 'yuhang',
},
],
},
]}
/>
</SchemaField>
</FormProvider>
</FormLayout>
)
}
```
## 扩展阅读态
```tsx
import React from 'react'
import {
PreviewText,
FormItem,
FormButtonGroup,
FormLayout,
} from '@formily/next'
import { createForm } from '@formily/core'
import {
FormProvider,
mapReadPretty,
connect,
createSchemaField,
} from '@formily/react'
import { Button, Input as NextInput } from '@alifd/next'
const Input = connect(NextInput, mapReadPretty(PreviewText.Input))
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
PreviewText,
},
})
const form = createForm()
export default () => {
return (
<PreviewText.Placeholder value="暂无数据">
<FormLayout labelCol={8} wrapperCol={16}>
<FormProvider form={form}>
<SchemaField>
<SchemaField.Markup
type="string"
x-decorator="FormItem"
title="文本预览"
required
x-component="Input"
default={'Hello world'}
/>
<SchemaField.Markup
type="string"
x-decorator="FormItem"
title="选择项预览"
x-component="PreviewText.Select"
x-component-props={{
mode: 'multiple',
}}
default={['123']}
enum={[
{ label: 'A111', value: '123' },
{ label: 'A222', value: '222' },
]}
/>
<SchemaField.Markup
type="string"
x-decorator="FormItem"
title="日期预览"
x-component="PreviewText.DatePicker"
/>
<SchemaField.Markup
type="string"
x-decorator="FormItem"
title="Cascader预览"
x-component="PreviewText.Cascader"
default={'yuhang'}
enum={[
{
label: '杭州',
value: 'hangzhou',
children: [
{
label: '余杭',
value: 'yuhang',
},
],
},
]}
/>
</SchemaField>
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.setState((state) => {
state.editable = !state.editable
})
}}
>
切换阅读态
</Button>
</FormButtonGroup.FormItem>
</FormProvider>
</FormLayout>
</PreviewText.Placeholder>
)
}
```
## API
### PreviewText.Input
参考 https://fusion.design/pc/component/basic/input
### PreviewText.Select
参考 https://fusion.design/pc/component/basic/select
### PreviewText.TreeSelect
参考 https://fusion.design/pc/component/basic/tree-select
### PreviewText.Cascader
参考 https://fusion.design/pc/component/basic/cascader-select
### PreviewText.DatePicker
参考 https://fusion.design/pc/component/basic/date-picker
### PreviewText.DateRangePicker
参考 https://fusion.design/pc/component/basic/date-picker
### PreviewText.TimePicker
参考 https://fusion.design/pc/component/basic/time-picker
### PreviewText.NumberPicker
参考 https://fusion.design/pc/component/basic/number-picker
### PreviewText.Placeholder
| 属性名 | 类型 | 描述 | 默认值 |
| ------ | ------ | ---------- | ------ |
| value | stirng | 缺省占位符 | N/A |
### PreviewText.usePlaceholder
```ts pure
interface usePlaceholder {
(): string
}
```
```
--------------------------------------------------------------------------------
/packages/next/src/form-drawer/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { Fragment, useLayoutEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import {
createForm,
onFormSubmitSuccess,
IFormProps,
Form,
} from '@formily/core'
import { toJS } from '@formily/reactive'
import { FormProvider, observer, Observer, ReactFC } from '@formily/react'
import {
isNum,
isStr,
isBool,
isFn,
applyMiddleware,
IMiddleware,
} from '@formily/shared'
import { ConfigProvider, Drawer } from '@alifd/next'
import { DrawerProps } from '@alifd/next/lib/drawer'
import {
usePrefixCls,
loading,
createPortalProvider,
createPortalRoot,
} from '../__builtins__'
type FormDrawerRenderer =
| React.ReactElement
| ((form: Form) => React.ReactElement)
type DrawerTitle = string | number | React.ReactElement
const getContext: () => any = ConfigProvider['getContext']
const isDrawerTitle = (props: any): props is DrawerTitle => {
return (
isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props)
)
}
const getDrawerProps = (props: any): IDrawerProps => {
if (isDrawerTitle(props)) {
return {
title: props,
}
} else {
return props
}
}
export interface IDrawerProps extends DrawerProps {
onClose?: (reason: string, e: React.MouseEvent) => void | boolean
loadingText?: React.ReactNode
}
export interface IFormDrawer {
forOpen(middleware: IMiddleware<IFormProps>): IFormDrawer
open(props?: IFormProps): Promise<any>
close(): void
}
export function FormDrawer(
title: IDrawerProps,
id: string,
renderer: FormDrawerRenderer
): IFormDrawer
export function FormDrawer(
title: IDrawerProps,
renderer: FormDrawerRenderer
): IFormDrawer
export function FormDrawer(
title: DrawerTitle,
id: string,
renderer: FormDrawerRenderer
): IFormDrawer
export function FormDrawer(
title: DrawerTitle,
renderer: FormDrawerRenderer
): IFormDrawer
export function FormDrawer(title: any, id: any, renderer?: any): IFormDrawer {
if (isFn(id) || React.isValidElement(id)) {
renderer = id
id = 'form-drawer'
}
const env = {
host: document.createElement('div'),
form: null,
promise: null,
openMiddlewares: [],
}
const root = createPortalRoot(env.host, id)
const props = getDrawerProps(title)
const drawer = {
width: '40%',
...props,
onClose: (reason: string, e: any) => {
if (props?.onClose?.(reason, e) !== false) {
formDrawer.close()
}
},
afterClose() {
props?.afterClose?.()
root.unmount()
},
}
const DrawerContent = observer(() => {
return <Fragment>{isFn(renderer) ? renderer(env.form) : renderer}</Fragment>
})
const renderDrawer = (visible = true) => {
return (
<ConfigProvider {...getContext()}>
<Observer>
{() => (
<Drawer {...drawer} visible={visible}>
<FormProvider form={env.form}>
<DrawerContent />
</FormProvider>
</Drawer>
)}
</Observer>
</ConfigProvider>
)
}
document.body.appendChild(env.host)
const formDrawer = {
forOpen: (middleware: IMiddleware<IFormProps>) => {
if (isFn(middleware)) {
env.openMiddlewares.push(middleware)
}
return formDrawer
},
open: (props: IFormProps) => {
if (env.promise) return env.promise
env.promise = new Promise(async (resolve, reject) => {
try {
props = await loading(drawer.loadingText, () =>
applyMiddleware(props, env.openMiddlewares)
)
env.form =
env.form ||
createForm({
...props,
effects(form) {
onFormSubmitSuccess(() => {
resolve(toJS(form.values))
formDrawer.close()
})
props?.effects?.(form)
},
})
} catch (e) {
reject(e)
}
root.render(() => renderDrawer(false))
setTimeout(() => {
root.render(() => renderDrawer(true))
}, 16)
})
return env.promise
},
close: () => {
if (!env.host) return
root.render(() => renderDrawer(false))
},
}
return formDrawer
}
const DrawerFooter: ReactFC = (props) => {
const ref = useRef<HTMLDivElement>()
const [footer, setFooter] = useState<HTMLDivElement>()
const footerRef = useRef<HTMLDivElement>()
const prefixCls = usePrefixCls('drawer')
useLayoutEffect(() => {
const content = ref.current?.closest(`.${prefixCls}`)
if (content) {
if (!footerRef.current) {
footerRef.current = content.querySelector(`.${prefixCls}-footer`)
const body = content.querySelector(`.${prefixCls}-body`)
if (!footerRef.current && body) {
footerRef.current = document.createElement('div')
footerRef.current.classList.add(`${prefixCls}-footer`)
footerRef.current.style.padding = '20px'
footerRef.current.style.borderTop = '1px solid #eee'
body.after(footerRef.current)
}
}
setFooter(footerRef.current)
}
})
footerRef.current = footer
return (
<div ref={ref} style={{ display: 'none' }}>
{footer && createPortal(props.children, footer)}
</div>
)
}
FormDrawer.Footer = DrawerFooter
FormDrawer.Portal = createPortalProvider('form-drawer')
export default FormDrawer
```
--------------------------------------------------------------------------------
/packages/next/src/editable/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { useLayoutEffect, useRef, useState } from 'react'
import { isVoidField, Field } from '@formily/core'
import { useField, observer } from '@formily/react'
import { Balloon } from '@alifd/next'
import { BalloonProps as PopoverProps } from '@alifd/next/lib/balloon'
import { BaseItem, IFormItemProps } from '../form-item'
import {
useClickAway,
usePrefixCls,
EditOutlinedIcon,
CloseOutlinedIcon,
MessageOutlinedIcon,
} from '../__builtins__'
/**
* 默认Inline展示
*/
type IPopoverProps = PopoverProps
type ComposedEditable = React.FC<React.PropsWithChildren<IFormItemProps>> & {
Popover?: React.FC<
React.PropsWithChildren<IPopoverProps & { title?: React.ReactNode }>
>
}
const useParentPattern = () => {
const field = useField<Field>()
return field?.parent?.pattern || field?.form?.pattern
}
const useEditable = (): [boolean, (payload: boolean) => void] => {
const pattern = useParentPattern()
const field = useField<Field>()
useLayoutEffect(() => {
if (pattern === 'editable') {
field.setPattern('readPretty')
}
}, [pattern])
return [
field.pattern === 'editable',
(pyaload: boolean) => {
if (pattern !== 'editable') return
field.setPattern(pyaload ? 'editable' : 'readPretty')
},
]
}
const useFormItemProps = (): IFormItemProps => {
const field = useField()
if (isVoidField(field)) return {}
if (!field) return {}
const takeMessage = () => {
if (field.selfErrors.length) return field.selfErrors
if (field.selfWarnings.length) return field.selfWarnings
if (field.selfSuccesses.length) return field.selfSuccesses
}
return {
feedbackStatus:
field.validateStatus === 'validating' ? 'pending' : field.validateStatus,
feedbackText: takeMessage(),
extra: field.description,
}
}
export const Editable: ComposedEditable = observer((props) => {
const [editable, setEditable] = useEditable()
const pattern = useParentPattern()
const itemProps = useFormItemProps()
const field = useField<Field>()
const basePrefixCls = usePrefixCls()
const prefixCls = usePrefixCls('formily-editable')
const ref = useRef<boolean>()
const innerRef = useRef<HTMLDivElement>()
const recover = () => {
if (ref.current && !field?.errors?.length) {
setEditable(false)
}
}
const renderEditHelper = () => {
if (editable) return
return (
<BaseItem {...props} {...itemProps}>
{pattern === 'editable' && (
<EditOutlinedIcon className={`${prefixCls}-edit-btn`} />
)}
{pattern !== 'editable' && (
<MessageOutlinedIcon className={`${prefixCls}-edit-btn`} />
)}
</BaseItem>
)
}
const renderCloseHelper = () => {
if (!editable) return
return (
<BaseItem {...props}>
<CloseOutlinedIcon className={`${prefixCls}-close-btn`} />
</BaseItem>
)
}
useClickAway((e) => {
const target = e.target as HTMLElement
if (target?.closest(`.${basePrefixCls}-overlay-wrapper`)) return
recover()
}, innerRef)
const onClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const target = e.target as HTMLElement
const close = innerRef.current.querySelector(`.${prefixCls}-close-btn`)
if (target?.contains(close) || close?.contains(target)) {
recover()
} else if (!ref.current) {
setTimeout(() => {
setEditable(true)
setTimeout(() => {
innerRef.current.querySelector('input')?.focus()
})
})
}
}
ref.current = editable
return (
<div className={prefixCls} ref={innerRef} onClick={onClick}>
<div className={`${prefixCls}-content`}>
<BaseItem {...props} {...itemProps}>
{props.children}
</BaseItem>
{renderEditHelper()}
{renderCloseHelper()}
</div>
</div>
)
})
Editable.Popover = observer((props) => {
const field = useField<Field>()
const pattern = useParentPattern()
const [visible, setVisible] = useState(false)
const prefixCls = usePrefixCls('formily-editable')
const closePopover = async () => {
try {
await field.form.validate(`${field.address}.*`)
} finally {
const errors = field.form.queryFeedbacks({
type: 'error',
address: `${field.address}.*`,
})
if (errors?.length) return
setVisible(false)
}
}
const openPopover = () => {
setVisible(true)
}
return (
<Balloon
{...props}
title={field.title}
visible={visible}
className={props.className}
onVisibleChange={(visible) => {
if (visible) {
openPopover()
} else {
closePopover()
}
}}
align="t"
triggerType="click"
closable={false}
trigger={
<div style={{ display: 'inline-flex' }}>
<BaseItem className={`${prefixCls}-trigger`}>
<div className={`${prefixCls}-content`}>
<span className={`${prefixCls}-preview`}>
{props.title || field.title}
</span>
{pattern === 'editable' && (
<EditOutlinedIcon className={`${prefixCls}-edit-btn`} />
)}
{pattern !== 'editable' && (
<MessageOutlinedIcon className={`${prefixCls}-edit-btn`} />
)}
</div>
</BaseItem>
</div>
}
>
{props.children}
</Balloon>
)
})
export default Editable
```
--------------------------------------------------------------------------------
/packages/next/docs/components/DatePicker.md:
--------------------------------------------------------------------------------
```markdown
# DatePicker
> Date Picker
## Markup Schema example
```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="normal date"
x-decorator="FormItem"
x-component="DatePicker"
/>
<SchemaField.String
name="week"
title="Week Selection"
x-decorator="FormItem"
x-component="DatePicker.WeekPicker"
/>
<SchemaField.String
name="month"
title="Month Selection"
x-decorator="FormItem"
x-component="DatePicker.MonthPicker"
/>
<SchemaField.String
name="year"
title="Year selection"
x-decorator="FormItem"
x-component="DatePicker.YearPicker"
/>
<SchemaField.String
name="[startDate,endDate]"
title="Date Range"
x-decorator="FormItem"
x-component="DatePicker.RangePicker"
x-component-props={{
showTime: true,
}}
/>
<SchemaField.String
name="range_month"
title="Month Range Selection"
x-decorator="FormItem"
x-component="DatePicker.RangePicker"
x-component-props={{
type: 'month',
}}
/>
<SchemaField.String
name="range_year"
title="Year range selection"
x-decorator="FormItem"
x-component="DatePicker.RangePicker"
x-component-props={{
type: 'year',
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## JSON Schema case
```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: 'Normal date',
'x-decorator': 'FormItem',
'x-component': 'DatePicker',
type: 'string',
},
week: {
title: 'Week Selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.WeekPicker',
type: 'string',
},
month: {
title: 'Month Selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.MonthPicker',
type: 'string',
},
year: {
title: 'Year selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.YearPicker',
type: 'string',
},
'[startDate,endDate]': {
title: 'Date range',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.RangePicker',
'x-component-props': {
showTime: true,
},
type: 'string',
},
range_month: {
title: 'Month Range Selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker.RangePicker',
'x-component-props': {
type: 'month',
},
type: 'string',
},
range_year: {
name: 'range_year',
title: 'Year range selection',
'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</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## Pure JSX case
```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="date selection"
decorator={[FormItem]}
component={[DatePicker]}
/>
<Field
name="week"
title="Week Selection"
decorator={[FormItem]}
component={[DatePicker.WeekPicker]}
/>
<Field
name="quarter"
title="Financial Year Selection"
decorator={[FormItem]}
component={[DatePicker.MonthPicker]}
/>
<Field
name="year"
title="Year selection"
decorator={[FormItem]}
component={[DatePicker.YearPicker]}
/>
<Field
name="[startDate,endDate]"
title="Date range selection"
decorator={[FormItem]}
component={[DatePicker.RangePicker]}
/>
<Field
name="range_month"
title="Month Range Selection"
decorator={[FormItem]}
component={[
DatePicker.RangePicker,
{
type: 'month',
},
]}
/>
<Field
name="range_year"
title="Year range selection"
decorator={[FormItem]}
component={[
DatePicker.RangePicker,
{
type: 'year',
},
]}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## API
Reference https://fusion.design/pc/component/basic/date-picker
```
--------------------------------------------------------------------------------
/packages/antd/src/editable/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { useLayoutEffect, useRef, useState } from 'react'
import { isVoidField, Field } from '@formily/core'
import { useField, observer } from '@formily/react'
import { Popover } from 'antd'
import { EditOutlined, CloseOutlined, MessageOutlined } from '@ant-design/icons'
import { BaseItem, IFormItemProps } from '../form-item'
import { PopoverProps } from 'antd/lib/popover'
import { useClickAway, usePrefixCls } from '../__builtins__'
import cls from 'classnames'
/**
* 默认Inline展示
*/
type IPopoverProps = PopoverProps
type ComposedEditable = React.FC<React.PropsWithChildren<IFormItemProps>> & {
Popover?: React.FC<
React.PropsWithChildren<IPopoverProps & { title?: React.ReactNode }>
>
}
const useParentPattern = () => {
const field = useField<Field>()
return field?.parent?.pattern || field?.form?.pattern
}
const useEditable = (): [boolean, (payload: boolean) => void] => {
const pattern = useParentPattern()
const field = useField<Field>()
useLayoutEffect(() => {
if (pattern === 'editable') {
return field.setPattern('readPretty')
}
}, [pattern])
return [
field.pattern === 'editable',
(payload: boolean) => {
if (pattern !== 'editable') return
field.setPattern(payload ? 'editable' : 'readPretty')
},
]
}
const useFormItemProps = (): IFormItemProps => {
const field = useField()
if (isVoidField(field)) return {}
if (!field) return {}
const takeMessage = () => {
if (field.selfErrors.length) return field.selfErrors
if (field.selfWarnings.length) return field.selfWarnings
if (field.selfSuccesses.length) return field.selfSuccesses
}
return {
feedbackStatus:
field.validateStatus === 'validating' ? 'pending' : field.validateStatus,
feedbackText: takeMessage(),
extra: field.description,
}
}
export const Editable: ComposedEditable = observer((props) => {
const [editable, setEditable] = useEditable()
const pattern = useParentPattern()
const itemProps = useFormItemProps()
const field = useField<Field>()
const basePrefixCls = usePrefixCls()
const prefixCls = usePrefixCls('formily-editable')
const ref = useRef<boolean>()
const innerRef = useRef<HTMLDivElement>()
const recover = () => {
if (ref.current && !field?.errors?.length) {
setEditable(false)
}
}
const renderEditHelper = () => {
if (editable) return
return (
<BaseItem {...props} {...itemProps}>
{pattern === 'editable' && (
<EditOutlined className={`${prefixCls}-edit-btn`} />
)}
{pattern !== 'editable' && (
<MessageOutlined className={`${prefixCls}-edit-btn`} />
)}
</BaseItem>
)
}
const renderCloseHelper = () => {
if (!editable) return
return (
<BaseItem {...props}>
<CloseOutlined className={`${prefixCls}-close-btn`} />
</BaseItem>
)
}
useClickAway((e) => {
const target = e.target as HTMLElement
if (target?.closest(`.${basePrefixCls}-select-dropdown`)) return
if (target?.closest(`.${basePrefixCls}-picker-dropdown`)) return
if (target?.closest(`.${basePrefixCls}-cascader-menus`)) return
recover()
}, innerRef)
const onClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const target = e.target as HTMLElement
const close = innerRef.current.querySelector(`.${prefixCls}-close-btn`)
if (target?.contains(close) || close?.contains(target)) {
recover()
} else if (!ref.current) {
setTimeout(() => {
setEditable(true)
setTimeout(() => {
innerRef.current.querySelector('input')?.focus()
})
})
}
}
ref.current = editable
return (
<div className={prefixCls} ref={innerRef} onClick={onClick}>
<div className={`${prefixCls}-content`}>
<BaseItem {...props} {...itemProps}>
{props.children}
</BaseItem>
{renderEditHelper()}
{renderCloseHelper()}
</div>
</div>
)
})
Editable.Popover = observer((props) => {
const field = useField<Field>()
const pattern = useParentPattern()
const [visible, setVisible] = useState(false)
const prefixCls = usePrefixCls('formily-editable')
const closePopover = async () => {
try {
await field.form.validate(`${field.address}.*`)
} finally {
const errors = field.form.queryFeedbacks({
type: 'error',
address: `${field.address}.*`,
})
if (errors?.length) return
setVisible(false)
}
}
const openPopover = () => {
setVisible(true)
}
return (
<Popover
{...props}
title={props.title || field.title}
visible={visible}
className={cls(prefixCls, props.className)}
content={props.children}
trigger="click"
destroyTooltipOnHide
onVisibleChange={(visible) => {
if (visible) {
openPopover()
} else {
closePopover()
}
}}
>
<div>
<BaseItem className={`${prefixCls}-trigger`}>
<div className={`${prefixCls}-content`}>
<span className={`${prefixCls}-preview`}>
{props.title || field.title}
</span>
{pattern === 'editable' && (
<EditOutlined className={`${prefixCls}-edit-btn`} />
)}
{pattern !== 'editable' && (
<MessageOutlined className={`${prefixCls}-edit-btn`} />
)}
</div>
</BaseItem>
</div>
</Popover>
)
})
export default Editable
```
--------------------------------------------------------------------------------
/packages/antd/src/upload/index.tsx:
--------------------------------------------------------------------------------
```typescript
import React, { useEffect } from 'react'
import { Field } from '@formily/core'
import { connect, mapProps, useField } from '@formily/react'
import { Upload as AntdUpload, Button } from 'antd'
import {
UploadChangeParam,
UploadProps as AntdUploadProps,
DraggerProps as AntdDraggerProps,
} from 'antd/lib/upload'
import { InboxOutlined, UploadOutlined } from '@ant-design/icons'
import { reaction } from '@formily/reactive'
import { UploadFile } from 'antd/lib/upload/interface'
import { isArr, toArr } from '@formily/shared'
import { UPLOAD_PLACEHOLDER } from './placeholder'
import { usePrefixCls } from '../__builtins__'
export type IUploadProps = Omit<AntdUploadProps, 'onChange'> & {
textContent?: React.ReactNode
onChange?: (fileList: UploadFile[]) => void
serviceErrorMessage?: string
}
export type IDraggerUploadProps = Omit<AntdDraggerProps, 'onChange'> & {
textContent?: React.ReactNode
onChange?: (fileList: UploadFile[]) => void
serviceErrorMessage?: string
}
type ComposedUpload = React.FC<React.PropsWithChildren<IUploadProps>> & {
Dragger?: React.FC<React.PropsWithChildren<IDraggerUploadProps>>
}
type IExtendsUploadProps = {
fileList?: any[]
serviceErrorMessage?: string
onChange?: (...args: any) => void
}
const testOpts = (
ext: RegExp,
options: { exclude?: string[]; include?: string[] }
) => {
if (options && isArr(options.include)) {
return options.include.some((url) => ext.test(url))
}
if (options && isArr(options.exclude)) {
return !options.exclude.some((url) => ext.test(url))
}
return true
}
const getImageByUrl = (url: string, options: any) => {
for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i++) {
if (
UPLOAD_PLACEHOLDER[i].ext.test(url) &&
testOpts(UPLOAD_PLACEHOLDER[i].ext, options)
) {
return UPLOAD_PLACEHOLDER[i].icon || url
}
}
return url
}
const getURL = (target: any) => {
return target?.['url'] || target?.['downloadURL'] || target?.['imgURL']
}
const getThumbURL = (target: any) => {
return (
target?.['thumbUrl'] ||
target?.['url'] ||
target?.['downloadURL'] ||
target?.['imgURL']
)
}
const getErrorMessage = (target: any) => {
return (
target?.errorMessage ||
target?.errMsg ||
target?.errorMsg ||
target?.message ||
(typeof target?.error === 'string' ? target.error : '')
)
}
const getState = (target: any) => {
if (target?.success === false) return 'error'
if (target?.failed === true) return 'error'
if (target?.error) return 'error'
return target?.state || target?.status
}
const normalizeFileList = (fileList: UploadFile[]) => {
if (fileList && fileList.length) {
return fileList.map((file, index) => {
return {
...file,
uid: file.uid || `${index}`,
status: getState(file.response) || getState(file),
url: getURL(file) || getURL(file?.response),
thumbUrl: getImageByUrl(
getThumbURL(file) || getThumbURL(file?.response),
{
exclude: ['.png', '.jpg', '.jpeg', '.gif'],
}
),
}
})
}
return []
}
const useValidator = (validator: (value: any) => string) => {
const field = useField<Field>()
useEffect(() => {
const dispose = reaction(
() => field.value,
(value) => {
const message = validator(value)
field.setFeedback({
type: 'error',
code: 'UploadError',
messages: message ? [message] : [],
})
}
)
return () => {
dispose()
}
}, [])
}
const useUploadValidator = (serviceErrorMessage = 'Upload Service Error') => {
useValidator((value) => {
const list = toArr(value)
for (let i = 0; i < list.length; i++) {
if (list[i]?.status === 'error') {
return (
getErrorMessage(list[i]?.response) ||
getErrorMessage(list[i]) ||
serviceErrorMessage
)
}
}
})
}
function useUploadProps<T extends IExtendsUploadProps = IUploadProps>({
serviceErrorMessage,
...props
}: T) {
useUploadValidator(serviceErrorMessage)
const onChange = (param: UploadChangeParam<UploadFile>) => {
props.onChange?.(normalizeFileList([...param.fileList]))
}
return {
...props,
fileList: normalizeFileList(props.fileList),
onChange,
}
}
const getPlaceholder = (props: IUploadProps) => {
if (props.listType !== 'picture-card') {
return (
<Button>
<UploadOutlined />
{props.textContent}
</Button>
)
}
return <UploadOutlined style={{ fontSize: 20 }} />
}
export const Upload: ComposedUpload = connect(
(props: React.PropsWithChildren<IUploadProps>) => {
return (
<AntdUpload {...useUploadProps(props)}>
{props.children || getPlaceholder(props)}
</AntdUpload>
)
},
mapProps({
value: 'fileList',
})
)
const Dragger = connect(
(props: React.PropsWithChildren<IDraggerUploadProps>) => {
return (
<div className={usePrefixCls('upload-dragger')}>
<AntdUpload.Dragger {...useUploadProps(props)}>
{props.children || (
<React.Fragment>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
{props.textContent && (
<p className="ant-upload-text">{props.textContent}</p>
)}
</React.Fragment>
)}
</AntdUpload.Dragger>
</div>
)
},
mapProps({
value: 'fileList',
})
)
Upload.Dragger = Dragger
export default Upload
```
--------------------------------------------------------------------------------
/packages/next/docs/components/DatePicker2.md:
--------------------------------------------------------------------------------
```markdown
# DatePicker2
> Date Picker
## Markup Schema example
```tsx
import React from 'react'
import { DatePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
DatePicker2,
FormItem,
},
})
const form = createForm()
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="date"
title="normal date"
x-decorator="FormItem"
x-component="DatePicker2"
/>
<SchemaField.String
name="week"
title="Week Selection"
x-decorator="FormItem"
x-component="DatePicker2.WeekPicker"
/>
<SchemaField.String
name="month"
title="Month Selection"
x-decorator="FormItem"
x-component="DatePicker2.MonthPicker"
/>
<SchemaField.String
name="year"
title="Year selection"
x-decorator="FormItem"
x-component="DatePicker2.YearPicker"
/>
<SchemaField.String
name="[startDate,endDate]"
title="Date Range"
x-decorator="FormItem"
x-component="DatePicker2.RangePicker"
x-component-props={{
showTime: true,
}}
/>
<SchemaField.String
name="range_month"
title="Month Range Selection"
x-decorator="FormItem"
x-component="DatePicker2.RangePicker"
x-component-props={{
mode: 'month',
}}
/>
<SchemaField.String
name="range_year"
title="Year range selection"
x-decorator="FormItem"
x-component="DatePicker2.RangePicker"
x-component-props={{
mode: 'year',
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## JSON Schema case
```tsx
import React from 'react'
import { DatePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
DatePicker2,
FormItem,
},
})
const form = createForm()
const schema = {
type: 'object',
properties: {
date: {
title: 'Normal date',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2',
type: 'string',
},
week: {
title: 'Week Selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.WeekPicker',
type: 'string',
},
month: {
title: 'Month Selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.MonthPicker',
type: 'string',
},
year: {
title: 'Year selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.YearPicker',
type: 'string',
},
'[startDate,endDate]': {
title: 'Date range',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.RangePicker',
'x-component-props': {
showTime: true,
},
type: 'string',
},
range_month: {
title: 'Month Range Selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.RangePicker',
'x-component-props': {
mode: 'month',
},
type: 'string',
},
range_year: {
name: 'range_year',
title: 'Year range selection',
'x-decorator': 'FormItem',
'x-component': 'DatePicker2.RangePicker',
'x-component-props': {
mode: 'year',
},
type: 'string',
},
},
}
export default () => (
<FormProvider form={form}>
<SchemaField schema={schema} />
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## Pure JSX case
```tsx
import React from 'react'
import { DatePicker2, 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="date selection"
decorator={[FormItem]}
component={[DatePicker2]}
/>
<Field
name="week"
title="Week Selection"
decorator={[FormItem]}
component={[DatePicker2.WeekPicker]}
/>
<Field
name="quarter"
title="Financial Year Selection"
decorator={[FormItem]}
component={[DatePicker2.MonthPicker]}
/>
<Field
name="year"
title="Year selection"
decorator={[FormItem]}
component={[DatePicker2.YearPicker]}
/>
<Field
name="[startDate,endDate]"
title="Date range selection"
decorator={[FormItem]}
component={[DatePicker2.RangePicker]}
/>
<Field
name="range_month"
title="Month Range Selection"
decorator={[FormItem]}
component={[
DatePicker2.RangePicker,
{
mode: 'month',
},
]}
/>
<Field
name="range_year"
title="Year range selection"
decorator={[FormItem]}
component={[
DatePicker2.RangePicker,
{
mode: 'year',
},
]}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## API
Reference https://fusion.design/pc/component/basic/date-picker2
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/FormTab.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# FormTab
> 选项卡表单
>
> 注意:该组件只适用于 Schema 场景
## Markup Schema 案例
```tsx
import React from 'react'
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button } from 'antd'
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
})
const form = createForm()
const formTab = FormTab.createFormTab()
export default () => {
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Void
type="void"
x-component="FormTab"
x-component-props={{ formTab }}
>
<SchemaField.Void
type="void"
name="tab1"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A1' }}
>
<SchemaField.String
name="aaa"
x-decorator="FormItem"
title="AAA"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab2"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A2' }}
>
<SchemaField.String
name="bbb"
x-decorator="FormItem"
title="BBB"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab3"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A3' }}
>
<SchemaField.String
name="ccc"
x-decorator="FormItem"
title="CCC"
required
x-component="Input"
/>
</SchemaField.Void>
</SchemaField.Void>
</SchemaField>
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible
})
}}
>
显示/隐藏最后一个Tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2')
}}
>
切换第二个Tab
</Button>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup.FormItem>
</FormProvider>
)
}
```
## JSON Schema 案例
```tsx
import React from 'react'
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button } from 'antd'
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
})
const form = createForm()
const formTab = FormTab.createFormTab()
const schema = {
type: 'object',
properties: {
collapse: {
type: 'void',
'x-component': 'FormTab',
'x-component-props': {
formTab: '{{formTab}}',
},
properties: {
tab1: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A1',
},
properties: {
aaa: {
type: 'string',
title: 'AAA',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab2: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A2',
},
properties: {
bbb: {
type: 'string',
title: 'BBB',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab3: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A3',
},
properties: {
ccc: {
type: 'string',
title: 'CCC',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
},
},
},
}
export default () => {
return (
<FormProvider form={form}>
<SchemaField schema={schema} scope={{ formTab }} />
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible
})
}}
>
显示/隐藏最后一个Tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2')
}}
>
切换第二个Tab
</Button>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup.FormItem>
</FormProvider>
)
}
```
## API
### FormTab
| 属性名 | 类型 | 描述 | 默认值 |
| ------- | -------- | ------------------------------------------------ | ------ |
| formTab | IFormTab | 传入通过 createFormTab/useFormTab 创建出来的模型 | |
其余参考 https://ant.design/components/tabs-cn/
### FormTab.TabPane
参考 https://ant.design/components/tabs-cn/
### FormTab.createFormTab
```ts pure
type ActiveKey = string | number
interface createFormTab {
(defaultActiveKey?: ActiveKey): IFormTab
}
interface IFormTab {
//激活主键
activeKey: ActiveKey
//设置激活主键
setActiveKey(key: ActiveKey): void
}
```
```
--------------------------------------------------------------------------------
/packages/next/docs/components/FormTab.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# FormTab
> 选项卡表单
>
> 注意:该组件只适用于 Schema 场景
## Markup Schema 案例
```tsx
import React from 'react'
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button } from '@alifd/next'
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
})
const form = createForm()
const formTab = FormTab.createFormTab()
export default () => {
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Void
type="void"
x-component="FormTab"
x-component-props={{ formTab }}
>
<SchemaField.Void
type="void"
name="tab1"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A1' }}
>
<SchemaField.String
name="aaa"
x-decorator="FormItem"
title="AAA"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab2"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A2' }}
>
<SchemaField.String
name="bbb"
x-decorator="FormItem"
title="BBB"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab3"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A3' }}
>
<SchemaField.String
name="ccc"
x-decorator="FormItem"
title="CCC"
required
x-component="Input"
/>
</SchemaField.Void>
</SchemaField.Void>
</SchemaField>
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible
})
}}
>
显示/隐藏最后一个Tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2')
}}
>
切换第二个Tab
</Button>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup.FormItem>
</FormProvider>
)
}
```
## JSON Schema 案例
```tsx
import React from 'react'
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button } from '@alifd/next'
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
})
const form = createForm()
const formTab = FormTab.createFormTab()
const schema = {
type: 'object',
properties: {
collapse: {
type: 'void',
'x-component': 'FormTab',
'x-component-props': {
formTab: '{{formTab}}',
},
properties: {
tab1: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A1',
},
properties: {
aaa: {
type: 'string',
title: 'AAA',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab2: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A2',
},
properties: {
bbb: {
type: 'string',
title: 'BBB',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab3: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A3',
},
properties: {
ccc: {
type: 'string',
title: 'CCC',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
},
},
},
}
export default () => {
return (
<FormProvider form={form}>
<SchemaField schema={schema} scope={{ formTab }} />
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible
})
}}
>
显示/隐藏最后一个Tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2')
}}
>
切换第二个Tab
</Button>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup.FormItem>
</FormProvider>
)
}
```
## API
### FormTab
| 属性名 | 类型 | 描述 | 默认值 |
| ------- | -------- | ------------------------------------------------ | ------ |
| formTab | IFormTab | 传入通过 createFormTab/useFormTab 创建出来的模型 | |
其余参考 https://fusion.design/pc/component/basic/tab
### FormTab.TabPane
参考 https://fusion.design/pc/component/basic/tab 的 Item 属性
### FormTab.createFormTab
```ts pure
type ActiveKey = string | number
interface createFormTab {
(defaultActiveKey?: ActiveKey): IFormTab
}
interface IFormTab {
//激活主键
activeKey: ActiveKey
//设置激活主键
setActiveKey(key: ActiveKey): void
}
```
```
--------------------------------------------------------------------------------
/packages/next/docs/components/Cascader.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# Cascader
> 联级选择器
## Markup Schema 案例
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm, onFieldReact, FormPathPattern } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { action } from '@formily/reactive'
const SchemaField = createSchemaField({
components: {
Cascader,
FormItem,
},
})
const useAddress = (pattern: FormPathPattern) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
onFieldReact(pattern, (field) => {
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
})
}
const form = createForm({
effects: () => {
useAddress('address')
},
})
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="address"
title="地址选择"
x-decorator="FormItem"
x-component="Cascader"
x-component-props={{
style: {
width: 240,
},
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## JSON Schema 案例
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { action } from '@formily/reactive'
const SchemaField = createSchemaField({
components: {
Cascader,
FormItem,
},
})
const transformAddress = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transformAddress(cities)
const _districts = transformAddress(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
const useAsyncDataSource =
(url: string, transform: (data: any) => any) => (field) => {
field.loading = true
fetch(url)
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
}
const form = createForm()
const schema = {
type: 'object',
properties: {
address: {
type: 'string',
title: '地址选择',
'x-decorator': 'FormItem',
'x-component': 'Cascader',
'x-component-props': {
style: {
width: 240,
},
},
'x-reactions': [
'{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}',
],
},
},
}
export default () => (
<FormProvider form={form}>
<SchemaField
schema={schema}
scope={{ useAsyncDataSource, transformAddress }}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## 纯 JSX 案例
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm, onFieldReact, FormPathPattern } from '@formily/core'
import { FormProvider, Field } from '@formily/react'
import { action } from '@formily/reactive'
const useAddress = (pattern: FormPathPattern) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
onFieldReact(pattern, (field) => {
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
})
}
const form = createForm({
effects: () => {
useAddress('address')
},
})
export default () => (
<FormProvider form={form}>
<Field
name="address"
title="地址选择"
decorator={[FormItem]}
component={[
Cascader,
{
style: {
width: 240,
},
},
]}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## API
参考 https://fusion.design/pc/component/basic/cascader-select
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/Cascader.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
# Cascader
> 联级选择器
## Markup Schema 案例
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd'
import { createForm, onFieldReact, FormPathPattern } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { action } from '@formily/reactive'
const SchemaField = createSchemaField({
components: {
Cascader,
FormItem,
},
})
const useAddress = (pattern: FormPathPattern) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
onFieldReact(pattern, (field) => {
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
})
}
const form = createForm({
effects: () => {
useAddress('address')
},
})
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="address"
title="地址选择"
required
x-decorator="FormItem"
x-component="Cascader"
x-component-props={{
style: {
width: 240,
},
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## JSON Schema 案例
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { action } from '@formily/reactive'
const SchemaField = createSchemaField({
components: {
Cascader,
FormItem,
},
})
const transformAddress = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transformAddress(cities)
const _districts = transformAddress(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
const useAsyncDataSource =
(url: string, transform: (data: any) => any) => (field) => {
field.loading = true
fetch(url)
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
}
const form = createForm()
const schema = {
type: 'object',
properties: {
address: {
type: 'string',
title: '地址选择',
'x-decorator': 'FormItem',
'x-component': 'Cascader',
'x-component-props': {
style: {
width: 240,
},
},
'x-reactions': [
'{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}',
],
},
},
}
export default () => (
<FormProvider form={form}>
<SchemaField
schema={schema}
scope={{ useAsyncDataSource, transformAddress }}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## 纯 JSX 案例
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd'
import { createForm, onFieldReact, FormPathPattern } from '@formily/core'
import { FormProvider, Field } from '@formily/react'
import { action } from '@formily/reactive'
const useAddress = (pattern: FormPathPattern) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
onFieldReact(pattern, (field) => {
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
})
}
const form = createForm({
effects: () => {
useAddress('address')
},
})
export default () => (
<FormProvider form={form}>
<Field
name="address"
title="地址选择"
decorator={[FormItem]}
component={[
Cascader,
{
style: {
width: 240,
},
},
]}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>提交</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## API
参考 https://ant.design/components/cascader-cn/
```
--------------------------------------------------------------------------------
/packages/next/docs/components/Cascader.md:
--------------------------------------------------------------------------------
```markdown
# Cascader
> Cascade selector
## Markup Schema example
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm, onFieldReact, FormPathPattern } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { action } from '@formily/reactive'
const SchemaField = createSchemaField({
components: {
Cascader,
FormItem,
},
})
const useAddress = (pattern: FormPathPattern) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
onFieldReact(pattern, (field) => {
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
})
}
const form = createForm({
effects: () => {
useAddress('address')
},
})
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="address"
title="Address Selection"
x-decorator="FormItem"
x-component="Cascader"
x-component-props={{
style: {
width: 240,
},
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## JSON Schema case
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { action } from '@formily/reactive'
const SchemaField = createSchemaField({
components: {
Cascader,
FormItem,
},
})
const transformAddress = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transformAddress(cities)
const _districts = transformAddress(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
const useAsyncDataSource =
(url: string, transform: (data: any) => any) => (field) => {
field.loading = true
fetch(url)
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
}
const form = createForm()
const schema = {
type: 'object',
properties: {
address: {
type: 'string',
title: 'Address Selection',
'x-decorator': 'FormItem',
'x-component': 'Cascader',
'x-component-props': {
style: {
width: 240,
},
},
'x-reactions': [
'{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}',
],
},
},
}
export default () => (
<FormProvider form={form}>
<SchemaField
schema={schema}
scope={{ useAsyncDataSource, transformAddress }}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## Pure JSX case
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next'
import { createForm, onFieldReact, FormPathPattern } from '@formily/core'
import { FormProvider, Field } from '@formily/react'
import { action } from '@formily/reactive'
const useAddress = (pattern: FormPathPattern) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
onFieldReact(pattern, (field) => {
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
})
}
const form = createForm({
effects: () => {
useAddress('address')
},
})
export default () => (
<FormProvider form={form}>
<Field
name="address"
title="Address Selection"
decorator={[FormItem]}
component={[
Cascader,
{
style: {
width: 240,
},
},
]}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## API
Reference https://fusion.design/pc/component/basic/cascader-select
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/Cascader.md:
--------------------------------------------------------------------------------
```markdown
# Cascader
> Cascade selector
## Markup Schema example
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd'
import { createForm, onFieldReact, FormPathPattern } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { action } from '@formily/reactive'
const SchemaField = createSchemaField({
components: {
Cascader,
FormItem,
},
})
const useAddress = (pattern: FormPathPattern) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
onFieldReact(pattern, (field) => {
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
})
}
const form = createForm({
effects: () => {
useAddress('address')
},
})
export default () => (
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
name="address"
title="Address Selection"
required
x-decorator="FormItem"
x-component="Cascader"
x-component-props={{
style: {
width: 240,
},
}}
/>
</SchemaField>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## JSON Schema case
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { action } from '@formily/reactive'
const SchemaField = createSchemaField({
components: {
Cascader,
FormItem,
},
})
const transformAddress = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transformAddress(cities)
const _districts = transformAddress(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
const useAsyncDataSource =
(url: string, transform: (data: any) => any) => (field) => {
field.loading = true
fetch(url)
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
}
const form = createForm()
const schema = {
type: 'object',
properties: {
address: {
type: 'string',
title: 'Address Selection',
'x-decorator': 'FormItem',
'x-component': 'Cascader',
'x-component-props': {
style: {
width: 240,
},
},
'x-reactions': [
'{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}',
],
},
},
}
export default () => (
<FormProvider form={form}>
<SchemaField
schema={schema}
scope={{ useAsyncDataSource, transformAddress }}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## Pure JSX case
```tsx
import React from 'react'
import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd'
import { createForm, onFieldReact, FormPathPattern } from '@formily/core'
import { FormProvider, Field } from '@formily/react'
import { action } from '@formily/reactive'
const useAddress = (pattern: FormPathPattern) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
onFieldReact(pattern, (field) => {
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
})
}
const form = createForm({
effects: () => {
useAddress('address')
},
})
export default () => (
<FormProvider form={form}>
<Field
name="address"
title="Address Selection"
decorator={[FormItem]}
component={[
Cascader,
{
style: {
width: 240,
},
},
]}
/>
<FormButtonGroup>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup>
</FormProvider>
)
```
## API
Reference https://ant.design/components/cascader-cn/
```
--------------------------------------------------------------------------------
/packages/next/docs/components/PreviewText.md:
--------------------------------------------------------------------------------
```markdown
# PreviewText
> Reading state components, mainly used to implement the reading state of these components of class Input and DatePicker
## Simple use case
```tsx
import React from 'react'
import { PreviewText, FormItem, FormLayout } from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
const SchemaField = createSchemaField({
components: {
FormItem,
PreviewText,
},
})
const form = createForm()
export default () => {
return (
<FormLayout labelCol={8} wrapperCol={16}>
<FormProvider form={form}>
<SchemaField>
<SchemaField.String
x-decorator="FormItem"
title="text preview"
x-component="PreviewText.Input"
default={'Hello world'}
/>
<SchemaField.String
x-decorator="FormItem"
title="Select item preview"
x-component="PreviewText.Select"
x-component-props={{
mode: 'multiple',
}}
default={['123', '222']}
enum={[
{ label: 'A111', value: '123' },
{ label: 'A222', value: '222' },
]}
/>
<SchemaField.String
x-decorator="FormItem"
title="date preview"
x-component="PreviewText.DatePicker"
default={'2020-11-23 22:15:20'}
/>
<SchemaField.String
x-decorator="FormItem"
title="Cascader Preview"
x-component="PreviewText.Cascader"
default={'yuhang'}
enum={[
{
label: 'Hangzhou',
value: 'hangzhou',
children: [
{
label: 'Yuhang',
value: 'yuhang',
},
],
},
]}
/>
</SchemaField>
</FormProvider>
</FormLayout>
)
}
```
## Extended reading mode
```tsx
import React from 'react'
import {
PreviewText,
FormItem,
FormButtonGroup,
FormLayout,
} from '@formily/next'
import { createForm } from '@formily/core'
import {
FormProvider,
mapReadPretty,
connect,
createSchemaField,
} from '@formily/react'
import { Button, Input as NextInput } from '@alifd/next'
const Input = connect(NextInput, mapReadPretty(PreviewText.Input))
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
PreviewText,
},
})
const form = createForm()
export default () => {
return (
<PreviewText.Placeholder value="No data currently available">
<FormLayout labelCol={8} wrapperCol={16}>
<FormProvider form={form}>
<SchemaField>
<SchemaField.Markup
type="string"
x-decorator="FormItem"
title="text preview"
required
x-component="Input"
default={'Hello world'}
/>
<SchemaField.Markup
type="string"
x-decorator="FormItem"
title="Select item preview"
x-component="PreviewText.Select"
x-component-props={{
mode: 'multiple',
}}
default={['123']}
enum={[
{ label: 'A111', value: '123' },
{ label: 'A222', value: '222' },
]}
/>
<SchemaField.Markup
type="string"
x-decorator="FormItem"
title="date preview"
x-component="PreviewText.DatePicker"
/>
<SchemaField.Markup
type="string"
x-decorator="FormItem"
title="Cascader Preview"
x-component="PreviewText.Cascader"
default={'yuhang'}
enum={[
{
label: 'Hangzhou',
value: 'hangzhou',
children: [
{
label: 'Yuhang',
value: 'yuhang',
},
],
},
]}
/>
</SchemaField>
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.setState((state) => {
state.editable = !state.editable
})
}}
>
Switch reading mode
</Button>
</FormButtonGroup.FormItem>
</FormProvider>
</FormLayout>
</PreviewText.Placeholder>
)
}
```
## API
### PreviewText.Input
Reference https://fusion.design/pc/component/basic/input
### PreviewText.Select
Reference https://fusion.design/pc/component/basic/select
### PreviewText.TreeSelect
Reference https://fusion.design/pc/component/basic/tree-select
### PreviewText.Cascader
Reference https://fusion.design/pc/component/basic/cascader-select
### PreviewText.DatePicker
Reference https://fusion.design/pc/component/basic/date-picker
### PreviewText.DateRangePicker
Reference https://fusion.design/pc/component/basic/date-picker
### PreviewText.TimePicker
Reference https://fusion.design/pc/component/basic/time-picker
### PreviewText.NumberPicker
Reference https://fusion.design/pc/component/basic/number-picker
### PreviewText.Placeholder
| Property name | Type | Description | Default value |
| ------------- | ------ | ------------------- | ------------- |
| value | stirng | Default placeholder | N/A |
### PreviewText.usePlaceholder
```ts pure
interface usePlaceholder {
(): string
}
```
```
--------------------------------------------------------------------------------
/packages/json-schema/src/types.ts:
--------------------------------------------------------------------------------
```typescript
import {
IGeneralFieldState,
GeneralField,
FormPathPattern,
} from '@formily/core'
export type SchemaEnum<Message> = Array<
| string
| number
| boolean
| { label?: Message; value?: any; [key: string]: any }
| { key?: any; title?: Message; [key: string]: any }
>
export type SchemaTypes =
| 'string'
| 'object'
| 'array'
| 'number'
| 'boolean'
| 'void'
| 'date'
| 'datetime'
| (string & {})
export type SchemaProperties<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
> = Record<
string,
ISchema<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>
>
export type SchemaPatch = (schema: ISchema) => ISchema
export type SchemaKey = string | number
export type SchemaEffectTypes =
| 'onFieldInit'
| 'onFieldMount'
| 'onFieldUnmount'
| 'onFieldValueChange'
| 'onFieldInputValueChange'
| 'onFieldInitialValueChange'
| 'onFieldValidateStart'
| 'onFieldValidateEnd'
| 'onFieldValidateFailed'
| 'onFieldValidateSuccess'
export type SchemaReaction<Field = any> =
| {
dependencies?:
| Array<
| string
| {
name?: string
type?: string
source?: string
property?: string
}
>
| Record<string, string>
when?: string | boolean
target?: string
effects?: (SchemaEffectTypes | (string & {}))[]
fulfill?: {
state?: Stringify<IGeneralFieldState>
schema?: ISchema
run?: string
}
otherwise?: {
state?: Stringify<IGeneralFieldState>
schema?: ISchema
run?: string
}
[key: string]: any
}
| ((field: Field, scope: IScopeContext) => void)
export type SchemaReactions<Field = any> =
| SchemaReaction<Field>
| SchemaReaction<Field>[]
export type SchemaItems<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
> =
| ISchema<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>
| ISchema<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>[]
export type SchemaComponents = Record<string, any>
export interface ISchemaFieldUpdateRequest {
state?: Stringify<IGeneralFieldState>
schema?: ISchema
run?: string
}
export interface IScopeContext {
[key: string]: any
}
export interface IFieldStateSetterOptions {
field: GeneralField
target?: FormPathPattern
request: ISchemaFieldUpdateRequest
runner?: string
scope?: IScopeContext
}
export interface ISchemaTransformerOptions {
scope?: IScopeContext
}
export type Slot = {
target: string
isRenderProp?: boolean
}
export type Stringify<P extends { [key: string]: any }> = {
/**
* Use `string & {}` instead of string to keep Literal Type for ISchema#component and ISchema#decorator
*/
[key in keyof P]?: P[key] | (string & {})
}
export type ISchema<
Decorator = any,
Component = any,
DecoratorProps = any,
ComponentProps = any,
Pattern = any,
Display = any,
Validator = any,
Message = any,
ReactionField = any
> = Stringify<{
version?: string
name?: SchemaKey
title?: Message
description?: Message
default?: any
readOnly?: boolean
writeOnly?: boolean
type?: SchemaTypes
enum?: SchemaEnum<Message>
const?: any
multipleOf?: number
maximum?: number
exclusiveMaximum?: number
minimum?: number
exclusiveMinimum?: number
maxLength?: number
minLength?: number
pattern?: string | RegExp
maxItems?: number
minItems?: number
uniqueItems?: boolean
maxProperties?: number
minProperties?: number
required?: string[] | boolean | string
format?: string
$ref?: string
$namespace?: string
/** nested json schema spec **/
definitions?: SchemaProperties<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>
properties?: SchemaProperties<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>
items?: SchemaItems<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>
additionalItems?: ISchema<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>
patternProperties?: SchemaProperties<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>
additionalProperties?: ISchema<
Decorator,
Component,
DecoratorProps,
ComponentProps,
Pattern,
Display,
Validator,
Message
>
['x-value']?: any
//顺序描述
['x-index']?: number
//交互模式
['x-pattern']?: Pattern
//展示状态
['x-display']?: Display
//校验器
['x-validator']?: Validator
//装饰器
['x-decorator']?: Decorator | (string & {}) | ((...args: any[]) => any)
//装饰器属性
['x-decorator-props']?: DecoratorProps
//组件
['x-component']?: Component | (string & {}) | ((...args: any[]) => any)
//组件属性
['x-component-props']?: ComponentProps
//组件响应器
['x-reactions']?: SchemaReactions<ReactionField>
//内容
['x-content']?: any
['x-data']?: any
['x-visible']?: boolean
['x-hidden']?: boolean
['x-disabled']?: boolean
['x-editable']?: boolean
['x-read-only']?: boolean
['x-read-pretty']?: boolean
['x-compile-omitted']?: string[]
[key: `x-${string | number}` | symbol]: any
}>
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-items/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 {
FormButtonGroup,
Submit,
FormItem,
Space,
Input,
Select,
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()
const schema = {
type: 'object',
properties: {
string_array: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: '字符串数组',
items: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
properties: {
add: {
type: 'void',
title: '添加条目',
'x-component': 'ArrayItems.Addition',
},
},
},
array: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: '对象数组',
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
date: {
type: 'string',
title: '日期',
'x-decorator': 'FormItem',
'x-component': 'DatePicker',
'x-component-props': {
type: 'daterange',
style: {
width: '250px',
},
},
},
input: {
type: 'string',
title: '输入框',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
select: {
type: 'string',
title: '下拉框',
enum: [
{ label: '选项1', value: 1 },
{ label: '选项2', value: 2 },
],
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
style: {
width: '250px',
},
},
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: '添加条目',
'x-component': 'ArrayItems.Addition',
},
},
},
array2: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
'x-component-props': { style: { width: '600px' } },
title: '对象数组',
items: {
type: 'object',
'x-decorator': 'ArrayItems.Item',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
date: {
type: 'string',
title: '日期',
'x-decorator': 'FormItem',
'x-component': 'DatePicker',
'x-component-props': {
type: 'daterange',
style: {
width: '250px',
},
},
},
input: {
type: 'string',
title: '输入框',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: '添加条目',
'x-component': 'ArrayItems.Addition',
},
},
},
},
}
return {
form,
schema,
}
},
methods: {
log(values) {
console.log(values)
},
},
}
</script>
<style lang="scss" scoped></style>
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/FormTab.md:
--------------------------------------------------------------------------------
```markdown
# FormTab
> Tab form
>
> Note: This component is only applicable to Schema scenarios
## Markup Schema example
```tsx
import React from 'react'
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button } from 'antd'
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
})
const form = createForm()
const formTab = FormTab.createFormTab()
export default () => {
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Void
type="void"
x-component="FormTab"
x-component-props={{ formTab }}
>
<SchemaField.Void
type="void"
name="tab1"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A1' }}
>
<SchemaField.String
name="aaa"
x-decorator="FormItem"
title="AAA"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab2"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A2' }}
>
<SchemaField.String
name="bbb"
x-decorator="FormItem"
title="BBB"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab3"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A3' }}
>
<SchemaField.String
name="ccc"
x-decorator="FormItem"
title="CCC"
required
x-component="Input"
/>
</SchemaField.Void>
</SchemaField.Void>
</SchemaField>
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible
})
}}
>
Show/hide the last tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2')
}}
>
Switch to the second Tab
</Button>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup.FormItem>
</FormProvider>
)
}
```
## JSON Schema case
```tsx
import React from 'react'
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/antd'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button } from 'antd'
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
})
const form = createForm()
const formTab = FormTab.createFormTab()
const schema = {
type: 'object',
properties: {
collapse: {
type: 'void',
'x-component': 'FormTab',
'x-component-props': {
formTab: '{{formTab}}',
},
properties: {
tab1: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A1',
},
properties: {
aaa: {
type: 'string',
title: 'AAA',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab2: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A2',
},
properties: {
bbb: {
type: 'string',
title: 'BBB',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab3: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A3',
},
properties: {
ccc: {
type: 'string',
title: 'CCC',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
},
},
},
}
export default () => {
return (
<FormProvider form={form}>
<SchemaField schema={schema} scope={{ formTab }} />
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible
})
}}
>
Show/hide the last tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2')
}}
>
Switch to the second Tab
</Button>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup.FormItem>
</FormProvider>
)
}
```
## API
### FormTab
| Property name | Type | Description | Default value |
| ------------- | -------- | ----------------------------------------------------- | ------------- |
| formTab | IFormTab | Pass in the model created by createFormTab/useFormTab | |
Other references https://ant.design/components/tabs-cn/
### FormTab.TabPane
Reference https://ant.design/components/tabs-cn/
### FormTab.createFormTab
```ts pure
type ActiveKey = string | number
interface createFormTab {
(defaultActiveKey?: ActiveKey): IFormTab
}
interface IFormTab {
//Activate the primary key
activeKey: ActiveKey
//Set the activation key
setActiveKey(key: ActiveKey): void
}
```
```
--------------------------------------------------------------------------------
/packages/path/src/matcher.ts:
--------------------------------------------------------------------------------
```typescript
import {
Segments,
Node,
isIdentifier,
isExpandOperator,
isWildcardOperator,
isGroupExpression,
isRangeExpression,
isIgnoreExpression,
isDotOperator,
isDestructorExpression,
IdentifierNode,
IgnoreExpressionNode,
DestructorExpressionNode,
ExpandOperatorNode,
WildcardOperatorNode,
GroupExpressionNode,
RangeExpressionNode,
} from './types'
import { isEqual, toArr, isSegmentEqual } from './shared'
export interface IRecord {
score: number
}
export class Matcher {
private tree: Node
private stack: Node[]
private record: IRecord
private excluding: boolean
private wildcards: WildcardOperatorNode[]
private path: Segments
constructor(tree: Node, record?: any) {
this.tree = tree
this.stack = []
this.excluding = false
this.wildcards = []
this.record = record
}
next(node: Node, pos: number) {
// const isOverToken = pos > this.path.length
if (node.after) {
// if (isOverToken) {
// return false
// }
return this.matchNode(node.after, pos)
}
if (isWildcardOperator(node) && !node.filter) {
if (this.excluding) {
return false
} else {
if (pos === 0 || node.optional) return true
return !!this.take(pos)
}
}
const isLastToken = pos === this.path.length - 1
if (isLastToken) {
return !!this.take(pos)
} else {
const wildcard = this.wildcards.pop()
if (wildcard && wildcard.after) {
return this.next(wildcard, pos)
}
}
return false
}
shot() {
if (this.record?.score >= 0) {
this.record.score++
}
}
take(pos: number) {
return String(this.path[pos] ?? '')
}
matchExcludeIdentifier(matched: boolean, node: Node, pos: number) {
const isLastToken = pos === this.path.length - 1
const isContainToken = pos < this.path.length
if (!node.after) {
this.excluding = false
}
if (matched) {
if (node.after) {
return this.next(node, pos)
}
if (isLastToken) {
return false
}
}
if (isLastToken) {
return true
}
return isContainToken
}
matchIdentifier(node: IdentifierNode, pos: number) {
const current = this.take(pos)
let matched = false
if (isExpandOperator(node.after)) {
if (current.indexOf(node.value) === 0) {
this.shot()
matched = true
}
if (this.excluding) {
return this.matchExcludeIdentifier(matched, node.after, pos)
} else {
return matched && this.next(node.after, pos)
}
} else if (current === node.value) {
this.shot()
matched = true
}
if (this.excluding) {
return this.matchExcludeIdentifier(matched, node, pos)
} else {
return matched && this.next(node, pos)
}
}
matchIgnoreExpression(node: IgnoreExpressionNode, pos: number) {
return isEqual(node.value, this.take(pos)) && this.next(node, pos)
}
matchDestructorExpression(node: DestructorExpressionNode, pos: number) {
return isEqual(node.source, this.take(pos)) && this.next(node, pos)
}
matchExpandOperator(node: ExpandOperatorNode, pos: number) {
return this.next(node, pos)
}
matchWildcardOperator(node: WildcardOperatorNode, pos: number) {
let matched = false
if (node.filter) {
this.stack.push(node)
matched = this.matchNode(node.filter, pos)
this.stack.pop()
} else {
matched = this.next(node, pos)
}
return matched
}
matchGroupExpression(node: GroupExpressionNode, pos: number) {
let excluding = false
if (node.isExclude) {
excluding = !this.excluding
}
return toArr(node.value)[excluding ? 'every' : 'some']((item) => {
this.wildcards = this.stack.slice() as WildcardOperatorNode[]
this.excluding = excluding
return this.matchNode(item, pos)
})
}
matchRangeExpression(node: RangeExpressionNode, pos: number) {
const current = Number(this.take(pos))
if (node.start) {
if (node.end) {
return (
current >= Number(node.start.value) &&
current <= Number(node.end.value)
)
} else {
return current >= Number(node.start.value)
}
} else {
if (node.end) {
return current <= Number(node.end.value)
} else {
this.wildcards = this.stack.slice() as WildcardOperatorNode[]
return this.next(node, pos)
}
}
}
matchNode(node: Node, pos = 0) {
if (isDotOperator(node)) {
return this.next(node, pos + 1)
} else if (isIdentifier(node)) {
return this.matchIdentifier(node, pos)
} else if (isIgnoreExpression(node)) {
return this.matchIgnoreExpression(node, pos)
} else if (isDestructorExpression(node)) {
return this.matchDestructorExpression(node, pos)
} else if (isExpandOperator(node)) {
return this.matchExpandOperator(node, pos)
} else if (isWildcardOperator(node)) {
return this.matchWildcardOperator(node, pos)
} else if (isGroupExpression(node)) {
return this.matchGroupExpression(node, pos)
} else if (isRangeExpression(node)) {
return this.matchRangeExpression(node, pos)
}
return false
}
match(path: Segments) {
this.path = path
return { matched: this.matchNode(this.tree), record: this.record }
}
static matchSegments(source: Segments, target: Segments, record?: any) {
if (source.length !== target.length) return { matched: false, record }
const match = (pos = 0) => {
const current = isSegmentEqual(source[pos], target[pos])
if (record?.score >= 0) {
record.score++
}
return current && (pos < source.length - 1 ? match(pos + 1) : true)
}
return { matched: match(), record }
}
}
```
--------------------------------------------------------------------------------
/packages/vue/src/components/RecursionField.ts:
--------------------------------------------------------------------------------
```typescript
import { inject, provide, watch, shallowRef, computed, markRaw } from 'vue-demi'
import { GeneralField } from '@formily/core'
import { isFn, isValid, lazyMerge } from '@formily/shared'
import { Schema } from '@formily/json-schema'
import {
SchemaSymbol,
SchemaOptionsSymbol,
SchemaExpressionScopeSymbol,
} from '../shared'
import { useField } from '../hooks'
import ObjectField from './ObjectField'
import ArrayField from './ArrayField'
import Field from './Field'
import VoidField from './VoidField'
import { h } from '../shared/h'
import type { IRecursionFieldProps, DefineComponent } from '../types'
const resolveEmptySlot = (slots: Record<any, (...args: any[]) => any[]>) => {
return Object.keys(slots).length
? h('div', { style: 'display:contents;' }, slots)
: undefined
}
const RecursionField = {
name: 'RecursionField',
inheritAttrs: false,
props: {
schema: {
required: true,
},
name: [String, Number],
basePath: {},
onlyRenderProperties: {
type: Boolean,
default: undefined,
},
onlyRenderSelf: {
type: Boolean,
default: undefined,
},
mapProperties: {},
filterProperties: {},
},
setup(props: IRecursionFieldProps) {
const parentRef = useField()
const optionsRef = inject(SchemaOptionsSymbol)
const scopeRef = inject(SchemaExpressionScopeSymbol)
const createSchema = (schemaProp: IRecursionFieldProps['schema']) =>
markRaw(new Schema(schemaProp))
const fieldSchemaRef = computed(() => createSchema(props.schema))
const getPropsFromSchema = (schema: Schema) =>
schema?.toFieldProps?.({
...optionsRef.value,
get scope() {
return lazyMerge(optionsRef.value.scope, scopeRef.value)
},
})
const fieldPropsRef = shallowRef(getPropsFromSchema(fieldSchemaRef.value))
watch([fieldSchemaRef, optionsRef], () => {
fieldPropsRef.value = getPropsFromSchema(fieldSchemaRef.value)
})
const getBasePath = () => {
if (props.onlyRenderProperties) {
return props.basePath ?? parentRef?.value?.address.concat(props.name)
}
return props.basePath ?? parentRef?.value?.address
}
provide(SchemaSymbol, fieldSchemaRef)
return () => {
const basePath = getBasePath()
const fieldProps = fieldPropsRef.value
const generateSlotsByProperties = (scoped = false) => {
if (props.onlyRenderSelf) return {}
const properties = Schema.getOrderProperties(fieldSchemaRef.value)
if (!properties.length) return {}
const renderMap: Record<string, ((field?: GeneralField) => unknown)[]> =
{}
const setRender = (
key: string,
value: (field?: GeneralField) => unknown
) => {
if (!renderMap[key]) {
renderMap[key] = []
}
renderMap[key].push(value)
}
properties.forEach(({ schema: item, key: name }, index) => {
let schema: Schema = item
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
}
}
setRender(schema['x-slot'] ?? 'default', (field?: GeneralField) =>
h(
RecursionField,
{
key: `${index}-${name}`,
attrs: {
schema,
name,
basePath: field?.address ?? basePath,
},
slot: schema['x-slot'],
},
{}
)
)
})
const slots = {}
Object.keys(renderMap).forEach((key) => {
const renderFns = renderMap[key]
slots[key] = scoped
? ({ field }) => renderFns.map((fn) => fn(field))
: () => renderFns.map((fn) => fn())
})
return slots
}
const render = () => {
if (!isValid(props.name))
return resolveEmptySlot(generateSlotsByProperties())
if (fieldSchemaRef.value.type === 'object') {
if (props.onlyRenderProperties)
return resolveEmptySlot(generateSlotsByProperties())
return h(
ObjectField,
{
attrs: {
...fieldProps,
name: props.name,
basePath: basePath,
},
},
generateSlotsByProperties(true)
)
} else if (fieldSchemaRef.value.type === 'array') {
return h(
ArrayField,
{
attrs: {
...fieldProps,
name: props.name,
basePath: basePath,
},
},
{}
)
} else if (fieldSchemaRef.value.type === 'void') {
if (props.onlyRenderProperties)
return resolveEmptySlot(generateSlotsByProperties())
const slots = generateSlotsByProperties(true)
return h(
VoidField,
{
attrs: {
...fieldProps,
name: props.name,
basePath: basePath,
},
},
slots
)
}
return h(
Field,
{
attrs: {
...fieldProps,
name: props.name,
basePath: basePath,
},
},
{}
)
}
if (!fieldSchemaRef.value) return
return render()
}
},
} as unknown as DefineComponent<IRecursionFieldProps>
export default RecursionField
```
--------------------------------------------------------------------------------
/packages/next/docs/components/FormTab.md:
--------------------------------------------------------------------------------
```markdown
# FormTab
> Tab form
>
> Note: This component is only applicable to Schema scenarios
## Markup Schema example
```tsx
import React from 'react'
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button } from '@alifd/next'
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
})
const form = createForm()
const formTab = FormTab.createFormTab()
export default () => {
return (
<FormProvider form={form}>
<SchemaField>
<SchemaField.Void
type="void"
x-component="FormTab"
x-component-props={{ formTab }}
>
<SchemaField.Void
type="void"
name="tab1"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A1' }}
>
<SchemaField.String
name="aaa"
x-decorator="FormItem"
title="AAA"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab2"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A2' }}
>
<SchemaField.String
name="bbb"
x-decorator="FormItem"
title="BBB"
required
x-component="Input"
/>
</SchemaField.Void>
<SchemaField.Void
name="tab3"
x-component="FormTab.TabPane"
x-component-props={{ tab: 'A3' }}
>
<SchemaField.String
name="ccc"
x-decorator="FormItem"
title="CCC"
required
x-component="Input"
/>
</SchemaField.Void>
</SchemaField.Void>
</SchemaField>
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible
})
}}
>
Show/hide the last tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2')
}}
>
Switch to the second Tab
</Button>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup.FormItem>
</FormProvider>
)
}
```
## JSON Schema case
```tsx
import React from 'react'
import {
FormTab,
FormItem,
Input,
FormButtonGroup,
Submit,
} from '@formily/next'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Button } from '@alifd/next'
const SchemaField = createSchemaField({
components: {
FormItem,
FormTab,
Input,
},
})
const form = createForm()
const formTab = FormTab.createFormTab()
const schema = {
type: 'object',
properties: {
collapse: {
type: 'void',
'x-component': 'FormTab',
'x-component-props': {
formTab: '{{formTab}}',
},
properties: {
tab1: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A1',
},
properties: {
aaa: {
type: 'string',
title: 'AAA',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab2: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A2',
},
properties: {
bbb: {
type: 'string',
title: 'BBB',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab3: {
type: 'void',
'x-component': 'FormTab.TabPane',
'x-component-props': {
tab: 'A3',
},
properties: {
ccc: {
type: 'string',
title: 'CCC',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
},
},
},
}
export default () => {
return (
<FormProvider form={form}>
<SchemaField schema={schema} scope={{ formTab }} />
<FormButtonGroup.FormItem>
<Button
onClick={() => {
form.query('tab3').take((field) => {
field.visible = !field.visible
})
}}
>
Show/hide the last tab
</Button>
<Button
onClick={() => {
formTab.setActiveKey('tab2')
}}
>
Switch to the second Tab
</Button>
<Submit onSubmit={console.log}>Submit</Submit>
</FormButtonGroup.FormItem>
</FormProvider>
)
}
```
## API
### FormTab
| Property name | Type | Description | Default value |
| ------------- | -------- | ----------------------------------------------------- | ------------- |
| formTab | IFormTab | Pass in the model created by createFormTab/useFormTab | |
Other references https://fusion.design/pc/component/basic/tab
### FormTab.TabPane
Refer to the Item property of https://fusion.design/pc/component/basic/tab
### FormTab.createFormTab
```ts pure
type ActiveKey = string | number
interface createFormTab {
(defaultActiveKey?: ActiveKey): IFormTab
}
interface IFormTab {
//Activate the primary key
activeKey: ActiveKey
//Set the activation key
setActiveKey(key: ActiveKey): void
}
```
```