This is page 12 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .all-contributorsrc
├── .codecov.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE
│ │ └── config.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows
│ ├── check-pr-title.yml
│ ├── ci.yml
│ ├── commitlint.yml
│ ├── issue-open-check.yml
│ ├── package-size.yml
│ └── pr-welcome.yml
├── .gitignore
├── .prettierrc.js
├── .umirc.js
├── .vscode
│ └── cspell.json
├── .yarnrc
├── CHANGELOG.md
├── commitlint.config.js
├── devtools
│ ├── .eslintrc
│ └── chrome-extension
│ ├── .npmignore
│ ├── assets
│ │ └── img
│ │ ├── loading.svg
│ │ └── logo
│ │ ├── 128x128.png
│ │ ├── 16x16.png
│ │ ├── 38x38.png
│ │ ├── 48x48.png
│ │ ├── error.png
│ │ ├── gray.png
│ │ └── scalable.png
│ ├── config
│ │ ├── webpack.base.ts
│ │ ├── webpack.dev.ts
│ │ └── webpack.prod.ts
│ ├── LICENSE.md
│ ├── package.json
│ ├── src
│ │ ├── app
│ │ │ ├── components
│ │ │ │ ├── FieldTree.tsx
│ │ │ │ ├── filter.ts
│ │ │ │ ├── LeftPanel.tsx
│ │ │ │ ├── RightPanel.tsx
│ │ │ │ ├── SearchBox.tsx
│ │ │ │ └── Tabs.tsx
│ │ │ ├── demo.tsx
│ │ │ └── index.tsx
│ │ └── extension
│ │ ├── backend.ts
│ │ ├── background.ts
│ │ ├── content.ts
│ │ ├── devpanel.tsx
│ │ ├── devtools.tsx
│ │ ├── inject.ts
│ │ ├── manifest.json
│ │ ├── popup.tsx
│ │ └── views
│ │ ├── devpanel.ejs
│ │ ├── devtools.ejs
│ │ └── popup.ejs
│ ├── tsconfig.build.json
│ └── tsconfig.json
├── docs
│ ├── functions
│ │ ├── contributors.ts
│ │ └── npm-search.ts
│ ├── guide
│ │ ├── advanced
│ │ │ ├── async.md
│ │ │ ├── async.zh-CN.md
│ │ │ ├── build.md
│ │ │ ├── build.zh-CN.md
│ │ │ ├── business-logic.md
│ │ │ ├── business-logic.zh-CN.md
│ │ │ ├── calculator.md
│ │ │ ├── calculator.zh-CN.md
│ │ │ ├── controlled.md
│ │ │ ├── controlled.zh-CN.md
│ │ │ ├── custom.md
│ │ │ ├── custom.zh-CN.md
│ │ │ ├── destructor.md
│ │ │ ├── destructor.zh-CN.md
│ │ │ ├── input.less
│ │ │ ├── layout.md
│ │ │ ├── layout.zh-CN.md
│ │ │ ├── linkages.md
│ │ │ ├── linkages.zh-CN.md
│ │ │ ├── validate.md
│ │ │ └── validate.zh-CN.md
│ │ ├── contribution.md
│ │ ├── contribution.zh-CN.md
│ │ ├── form-builder.md
│ │ ├── form-builder.zh-CN.md
│ │ ├── index.md
│ │ ├── index.zh-CN.md
│ │ ├── issue-helper.md
│ │ ├── issue-helper.zh-CN.md
│ │ ├── learn-formily.md
│ │ ├── learn-formily.zh-CN.md
│ │ ├── quick-start.md
│ │ ├── quick-start.zh-CN.md
│ │ ├── scenes
│ │ │ ├── dialog-drawer.md
│ │ │ ├── dialog-drawer.zh-CN.md
│ │ │ ├── edit-detail.md
│ │ │ ├── edit-detail.zh-CN.md
│ │ │ ├── index.less
│ │ │ ├── login-register.md
│ │ │ ├── login-register.zh-CN.md
│ │ │ ├── more.md
│ │ │ ├── more.zh-CN.md
│ │ │ ├── query-list.md
│ │ │ ├── query-list.zh-CN.md
│ │ │ ├── step-form.md
│ │ │ ├── step-form.zh-CN.md
│ │ │ ├── tab-form.md
│ │ │ ├── tab-form.zh-CN.md
│ │ │ └── VerifyCode.tsx
│ │ ├── upgrade.md
│ │ └── upgrade.zh-CN.md
│ ├── index.md
│ ├── index.zh-CN.md
│ └── site
│ ├── Contributors.less
│ ├── Contributors.tsx
│ ├── QrCode.less
│ ├── QrCode.tsx
│ ├── Section.less
│ ├── Section.tsx
│ └── styles.less
├── global.config.ts
├── jest.config.js
├── lerna.json
├── LICENSE.md
├── package.json
├── packages
│ ├── .eslintrc
│ ├── antd
│ │ ├── __tests__
│ │ │ ├── moment.spec.ts
│ │ │ └── sideEffects.spec.ts
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── build-style.ts
│ │ ├── create-style.ts
│ │ ├── docs
│ │ │ ├── components
│ │ │ │ ├── ArrayCards.md
│ │ │ │ ├── ArrayCards.zh-CN.md
│ │ │ │ ├── ArrayCollapse.md
│ │ │ │ ├── ArrayCollapse.zh-CN.md
│ │ │ │ ├── ArrayItems.md
│ │ │ │ ├── ArrayItems.zh-CN.md
│ │ │ │ ├── ArrayTable.md
│ │ │ │ ├── ArrayTable.zh-CN.md
│ │ │ │ ├── ArrayTabs.md
│ │ │ │ ├── ArrayTabs.zh-CN.md
│ │ │ │ ├── Cascader.md
│ │ │ │ ├── Cascader.zh-CN.md
│ │ │ │ ├── Checkbox.md
│ │ │ │ ├── Checkbox.zh-CN.md
│ │ │ │ ├── DatePicker.md
│ │ │ │ ├── DatePicker.zh-CN.md
│ │ │ │ ├── Editable.md
│ │ │ │ ├── Editable.zh-CN.md
│ │ │ │ ├── Form.md
│ │ │ │ ├── Form.zh-CN.md
│ │ │ │ ├── FormButtonGroup.md
│ │ │ │ ├── FormButtonGroup.zh-CN.md
│ │ │ │ ├── FormCollapse.md
│ │ │ │ ├── FormCollapse.zh-CN.md
│ │ │ │ ├── FormDialog.md
│ │ │ │ ├── FormDialog.zh-CN.md
│ │ │ │ ├── FormDrawer.md
│ │ │ │ ├── FormDrawer.zh-CN.md
│ │ │ │ ├── FormGrid.md
│ │ │ │ ├── FormGrid.zh-CN.md
│ │ │ │ ├── FormItem.md
│ │ │ │ ├── FormItem.zh-CN.md
│ │ │ │ ├── FormLayout.md
│ │ │ │ ├── FormLayout.zh-CN.md
│ │ │ │ ├── FormStep.md
│ │ │ │ ├── FormStep.zh-CN.md
│ │ │ │ ├── FormTab.md
│ │ │ │ ├── FormTab.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ ├── Input.md
│ │ │ │ ├── Input.zh-CN.md
│ │ │ │ ├── NumberPicker.md
│ │ │ │ ├── NumberPicker.zh-CN.md
│ │ │ │ ├── Password.md
│ │ │ │ ├── Password.zh-CN.md
│ │ │ │ ├── PreviewText.md
│ │ │ │ ├── PreviewText.zh-CN.md
│ │ │ │ ├── Radio.md
│ │ │ │ ├── Radio.zh-CN.md
│ │ │ │ ├── Reset.md
│ │ │ │ ├── Reset.zh-CN.md
│ │ │ │ ├── Select.md
│ │ │ │ ├── Select.zh-CN.md
│ │ │ │ ├── SelectTable.md
│ │ │ │ ├── SelectTable.zh-CN.md
│ │ │ │ ├── Space.md
│ │ │ │ ├── Space.zh-CN.md
│ │ │ │ ├── Submit.md
│ │ │ │ ├── Submit.zh-CN.md
│ │ │ │ ├── Switch.md
│ │ │ │ ├── Switch.zh-CN.md
│ │ │ │ ├── TimePicker.md
│ │ │ │ ├── TimePicker.zh-CN.md
│ │ │ │ ├── Transfer.md
│ │ │ │ ├── Transfer.zh-CN.md
│ │ │ │ ├── TreeSelect.md
│ │ │ │ ├── TreeSelect.zh-CN.md
│ │ │ │ ├── Upload.md
│ │ │ │ └── Upload.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __builtins__
│ │ │ │ ├── hooks
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── useClickAway.ts
│ │ │ │ │ └── usePrefixCls.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── loading.ts
│ │ │ │ ├── moment.ts
│ │ │ │ ├── pickDataProps.ts
│ │ │ │ ├── portal.tsx
│ │ │ │ ├── render.ts
│ │ │ │ └── sort.tsx
│ │ │ ├── array-base
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-cards
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-collapse
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-items
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-table
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── array-tabs
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── cascader
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── checkbox
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── date-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── editable
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form-button-group
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form-collapse
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-dialog
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-drawer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-grid
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form-item
│ │ │ │ ├── animation.less
│ │ │ │ ├── grid.less
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── form-layout
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ ├── style.ts
│ │ │ │ └── useResponsiveFormLayout.ts
│ │ │ ├── form-step
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-tab
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── index.ts
│ │ │ ├── input
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── number-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── password
│ │ │ │ ├── index.tsx
│ │ │ │ ├── PasswordStrength.tsx
│ │ │ │ └── style.ts
│ │ │ ├── preview-text
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── radio
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ └── style.ts
│ │ │ ├── reset
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── select-table
│ │ │ │ ├── index.tsx
│ │ │ │ ├── style.less
│ │ │ │ ├── style.ts
│ │ │ │ ├── useCheckSlackly.tsx
│ │ │ │ ├── useFilterOptions.tsx
│ │ │ │ ├── useFlatOptions.tsx
│ │ │ │ ├── useSize.tsx
│ │ │ │ ├── useTitleAddon.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── space
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── style.less
│ │ │ ├── style.ts
│ │ │ ├── submit
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── switch
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── time-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── transfer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── tree-select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── upload
│ │ │ ├── index.tsx
│ │ │ ├── placeholder.ts
│ │ │ └── style.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── benchmark
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.tsx
│ │ ├── template.ejs
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ ├── webpack.base.ts
│ │ ├── webpack.dev.ts
│ │ └── webpack.prod.ts
│ ├── core
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── docs
│ │ │ ├── api
│ │ │ │ ├── entry
│ │ │ │ │ ├── ActionResponse.less
│ │ │ │ │ ├── ActionResponse.tsx
│ │ │ │ │ ├── createForm.md
│ │ │ │ │ ├── createForm.zh-CN.md
│ │ │ │ │ ├── FieldEffectHooks.md
│ │ │ │ │ ├── FieldEffectHooks.zh-CN.md
│ │ │ │ │ ├── FormChecker.md
│ │ │ │ │ ├── FormChecker.zh-CN.md
│ │ │ │ │ ├── FormEffectHooks.md
│ │ │ │ │ ├── FormEffectHooks.zh-CN.md
│ │ │ │ │ ├── FormHooksAPI.md
│ │ │ │ │ ├── FormHooksAPI.zh-CN.md
│ │ │ │ │ ├── FormPath.md
│ │ │ │ │ ├── FormPath.zh-CN.md
│ │ │ │ │ ├── FormValidatorRegistry.md
│ │ │ │ │ └── FormValidatorRegistry.zh-CN.md
│ │ │ │ └── models
│ │ │ │ ├── ArrayField.md
│ │ │ │ ├── ArrayField.zh-CN.md
│ │ │ │ ├── Field.md
│ │ │ │ ├── Field.zh-CN.md
│ │ │ │ ├── Form.md
│ │ │ │ ├── Form.zh-CN.md
│ │ │ │ ├── ObjectField.md
│ │ │ │ ├── ObjectField.zh-CN.md
│ │ │ │ ├── Query.md
│ │ │ │ ├── Query.zh-CN.md
│ │ │ │ ├── VoidField.md
│ │ │ │ └── VoidField.zh-CN.md
│ │ │ ├── guide
│ │ │ │ ├── architecture.md
│ │ │ │ ├── architecture.zh-CN.md
│ │ │ │ ├── field.md
│ │ │ │ ├── field.zh-CN.md
│ │ │ │ ├── form.md
│ │ │ │ ├── form.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ ├── mvvm.md
│ │ │ │ ├── mvvm.zh-CN.md
│ │ │ │ ├── values.md
│ │ │ │ └── values.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── array.spec.ts
│ │ │ │ ├── effects.spec.ts
│ │ │ │ ├── externals.spec.ts
│ │ │ │ ├── field.spec.ts
│ │ │ │ ├── form.spec.ts
│ │ │ │ ├── graph.spec.ts
│ │ │ │ ├── heart.spec.ts
│ │ │ │ ├── internals.spec.ts
│ │ │ │ ├── lifecycle.spec.ts
│ │ │ │ ├── object.spec.ts
│ │ │ │ ├── shared.ts
│ │ │ │ └── void.spec.ts
│ │ │ ├── effects
│ │ │ │ ├── index.ts
│ │ │ │ ├── onFieldEffects.ts
│ │ │ │ └── onFormEffects.ts
│ │ │ ├── global.d.ts
│ │ │ ├── index.ts
│ │ │ ├── models
│ │ │ │ ├── ArrayField.ts
│ │ │ │ ├── BaseField.ts
│ │ │ │ ├── Field.ts
│ │ │ │ ├── Form.ts
│ │ │ │ ├── Graph.ts
│ │ │ │ ├── Heart.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── LifeCycle.ts
│ │ │ │ ├── ObjectField.ts
│ │ │ │ ├── Query.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── VoidField.ts
│ │ │ ├── shared
│ │ │ │ ├── checkers.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── effective.ts
│ │ │ │ ├── externals.ts
│ │ │ │ └── internals.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── element
│ │ ├── .npmignore
│ │ ├── build-style.ts
│ │ ├── create-style.ts
│ │ ├── docs
│ │ │ ├── .vuepress
│ │ │ │ ├── components
│ │ │ │ │ ├── createCodeSandBox.js
│ │ │ │ │ ├── dumi-previewer.vue
│ │ │ │ │ └── highlight.js
│ │ │ │ ├── config.js
│ │ │ │ ├── enhanceApp.js
│ │ │ │ ├── styles
│ │ │ │ │ └── index.styl
│ │ │ │ └── util.js
│ │ │ ├── demos
│ │ │ │ ├── guide
│ │ │ │ │ ├── array-cards
│ │ │ │ │ │ ├── effects-json-schema.vue
│ │ │ │ │ │ ├── effects-markup-schema.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── array-collapse
│ │ │ │ │ │ ├── effects-json-schema.vue
│ │ │ │ │ │ ├── effects-markup-schema.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── array-items
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── array-table
│ │ │ │ │ │ ├── effects-json-schema.vue
│ │ │ │ │ │ ├── effects-markup-schema.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── array-tabs
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── cascader
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── checkbox
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── date-picker
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── editable
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-button-group.vue
│ │ │ │ │ ├── form-collapse
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── form-dialog
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-drawer
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-grid
│ │ │ │ │ │ ├── form.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── native.vue
│ │ │ │ │ ├── form-item
│ │ │ │ │ │ ├── bordered-none.vue
│ │ │ │ │ │ ├── common.vue
│ │ │ │ │ │ ├── feedback.vue
│ │ │ │ │ │ ├── inset.vue
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ ├── size.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-layout
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── form-step
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── form-tab
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ └── markup-schema.vue
│ │ │ │ │ ├── form.vue
│ │ │ │ │ ├── input
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── input-number
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── password
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── preview-text
│ │ │ │ │ │ ├── base.vue
│ │ │ │ │ │ └── extend.vue
│ │ │ │ │ ├── radio
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── reset
│ │ │ │ │ │ ├── base.vue
│ │ │ │ │ │ ├── force.vue
│ │ │ │ │ │ └── validate.vue
│ │ │ │ │ ├── select
│ │ │ │ │ │ ├── json-schema-async.vue
│ │ │ │ │ │ ├── json-schema-sync.vue
│ │ │ │ │ │ ├── markup-schema-async-search.vue
│ │ │ │ │ │ ├── markup-schema-async.vue
│ │ │ │ │ │ ├── markup-schema-sync.vue
│ │ │ │ │ │ ├── template-async.vue
│ │ │ │ │ │ └── template-sync.vue
│ │ │ │ │ ├── space
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── submit
│ │ │ │ │ │ ├── base.vue
│ │ │ │ │ │ └── loading.vue
│ │ │ │ │ ├── switch
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── time-picker
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ ├── transfer
│ │ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ │ └── template.vue
│ │ │ │ │ └── upload
│ │ │ │ │ ├── json-schema.vue
│ │ │ │ │ ├── markup-schema.vue
│ │ │ │ │ └── template.vue
│ │ │ │ └── index.vue
│ │ │ ├── guide
│ │ │ │ ├── array-cards.md
│ │ │ │ ├── array-collapse.md
│ │ │ │ ├── array-items.md
│ │ │ │ ├── array-table.md
│ │ │ │ ├── array-tabs.md
│ │ │ │ ├── cascader.md
│ │ │ │ ├── checkbox.md
│ │ │ │ ├── date-picker.md
│ │ │ │ ├── editable.md
│ │ │ │ ├── form-button-group.md
│ │ │ │ ├── form-collapse.md
│ │ │ │ ├── form-dialog.md
│ │ │ │ ├── form-drawer.md
│ │ │ │ ├── form-grid.md
│ │ │ │ ├── form-item.md
│ │ │ │ ├── form-layout.md
│ │ │ │ ├── form-step.md
│ │ │ │ ├── form-tab.md
│ │ │ │ ├── form.md
│ │ │ │ ├── index.md
│ │ │ │ ├── input-number.md
│ │ │ │ ├── input.md
│ │ │ │ ├── password.md
│ │ │ │ ├── preview-text.md
│ │ │ │ ├── radio.md
│ │ │ │ ├── reset.md
│ │ │ │ ├── select.md
│ │ │ │ ├── space.md
│ │ │ │ ├── submit.md
│ │ │ │ ├── switch.md
│ │ │ │ ├── time-picker.md
│ │ │ │ ├── transfer.md
│ │ │ │ └── upload.md
│ │ │ └── README.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __builtins__
│ │ │ │ ├── configs
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── shared
│ │ │ │ │ ├── create-context.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── loading.ts
│ │ │ │ │ ├── portal.ts
│ │ │ │ │ ├── resolve-component.ts
│ │ │ │ │ ├── transform-component.ts
│ │ │ │ │ ├── types.ts
│ │ │ │ │ └── utils.ts
│ │ │ │ └── styles
│ │ │ │ └── common.scss
│ │ │ ├── array-base
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-cards
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-collapse
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-items
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-table
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-tabs
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── cascader
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── checkbox
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── date-picker
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── editable
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── el-form
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── el-form-item
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── form
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-button-group
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-collapse
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-dialog
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── form-drawer
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-grid
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-item
│ │ │ │ ├── animation.scss
│ │ │ │ ├── grid.scss
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ ├── style.ts
│ │ │ │ └── var.scss
│ │ │ ├── form-layout
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ ├── style.ts
│ │ │ │ └── useResponsiveFormLayout.ts
│ │ │ ├── form-step
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── form-tab
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── index.ts
│ │ │ ├── input
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── input-number
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── password
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── preview-text
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── radio
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── reset
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── select
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── space
│ │ │ │ ├── index.ts
│ │ │ │ ├── style.scss
│ │ │ │ └── style.ts
│ │ │ ├── style.ts
│ │ │ ├── submit
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── switch
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── time-picker
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ ├── transfer
│ │ │ │ ├── index.ts
│ │ │ │ └── style.ts
│ │ │ └── upload
│ │ │ ├── index.ts
│ │ │ └── style.ts
│ │ ├── transformer.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── grid
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── index.ts
│ │ │ └── observer.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── json-schema
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── schema.spec.ts.snap
│ │ │ │ ├── compiler.spec.ts
│ │ │ │ ├── patches.spec.ts
│ │ │ │ ├── schema.spec.ts
│ │ │ │ ├── server-validate.spec.ts
│ │ │ │ ├── shared.spec.ts
│ │ │ │ ├── transformer.spec.ts
│ │ │ │ └── traverse.spec.ts
│ │ │ ├── compiler.ts
│ │ │ ├── global.d.ts
│ │ │ ├── index.ts
│ │ │ ├── patches.ts
│ │ │ ├── polyfills
│ │ │ │ ├── index.ts
│ │ │ │ └── SPECIFICATION_1_0.ts
│ │ │ ├── schema.ts
│ │ │ ├── shared.ts
│ │ │ ├── transformer.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── next
│ │ ├── __tests__
│ │ │ ├── moment.spec.ts
│ │ │ └── sideEffects.spec.ts
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── build-style.ts
│ │ ├── create-style.ts
│ │ ├── docs
│ │ │ ├── components
│ │ │ │ ├── ArrayCards.md
│ │ │ │ ├── ArrayCards.zh-CN.md
│ │ │ │ ├── ArrayCollapse.md
│ │ │ │ ├── ArrayCollapse.zh-CN.md
│ │ │ │ ├── ArrayItems.md
│ │ │ │ ├── ArrayItems.zh-CN.md
│ │ │ │ ├── ArrayTable.md
│ │ │ │ ├── ArrayTable.zh-CN.md
│ │ │ │ ├── Cascader.md
│ │ │ │ ├── Cascader.zh-CN.md
│ │ │ │ ├── Checkbox.md
│ │ │ │ ├── Checkbox.zh-CN.md
│ │ │ │ ├── DatePicker.md
│ │ │ │ ├── DatePicker.zh-CN.md
│ │ │ │ ├── DatePicker2.md
│ │ │ │ ├── DatePicker2.zh-CN.md
│ │ │ │ ├── Editable.md
│ │ │ │ ├── Editable.zh-CN.md
│ │ │ │ ├── Form.md
│ │ │ │ ├── Form.zh-CN.md
│ │ │ │ ├── FormButtonGroup.md
│ │ │ │ ├── FormButtonGroup.zh-CN.md
│ │ │ │ ├── FormCollapse.md
│ │ │ │ ├── FormCollapse.zh-CN.md
│ │ │ │ ├── FormDialog.md
│ │ │ │ ├── FormDialog.zh-CN.md
│ │ │ │ ├── FormDrawer.md
│ │ │ │ ├── FormDrawer.zh-CN.md
│ │ │ │ ├── FormGrid.md
│ │ │ │ ├── FormGrid.zh-CN.md
│ │ │ │ ├── FormItem.md
│ │ │ │ ├── FormItem.zh-CN.md
│ │ │ │ ├── FormLayout.md
│ │ │ │ ├── FormLayout.zh-CN.md
│ │ │ │ ├── FormStep.md
│ │ │ │ ├── FormStep.zh-CN.md
│ │ │ │ ├── FormTab.md
│ │ │ │ ├── FormTab.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ ├── index.zh-CN.md
│ │ │ │ ├── Input.md
│ │ │ │ ├── Input.zh-CN.md
│ │ │ │ ├── NumberPicker.md
│ │ │ │ ├── NumberPicker.zh-CN.md
│ │ │ │ ├── Password.md
│ │ │ │ ├── Password.zh-CN.md
│ │ │ │ ├── PreviewText.md
│ │ │ │ ├── PreviewText.zh-CN.md
│ │ │ │ ├── Radio.md
│ │ │ │ ├── Radio.zh-CN.md
│ │ │ │ ├── Reset.md
│ │ │ │ ├── Reset.zh-CN.md
│ │ │ │ ├── Select.md
│ │ │ │ ├── Select.zh-CN.md
│ │ │ │ ├── SelectTable.md
│ │ │ │ ├── SelectTable.zh-CN.md
│ │ │ │ ├── Space.md
│ │ │ │ ├── Space.zh-CN.md
│ │ │ │ ├── Submit.md
│ │ │ │ ├── Submit.zh-CN.md
│ │ │ │ ├── Switch.md
│ │ │ │ ├── Switch.zh-CN.md
│ │ │ │ ├── TimePicker.md
│ │ │ │ ├── TimePicker.zh-CN.md
│ │ │ │ ├── TimePicker2.md
│ │ │ │ ├── TimePicker2.zh-CN.md
│ │ │ │ ├── Transfer.md
│ │ │ │ ├── Transfer.zh-CN.md
│ │ │ │ ├── TreeSelect.md
│ │ │ │ ├── TreeSelect.zh-CN.md
│ │ │ │ ├── Upload.md
│ │ │ │ └── Upload.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LESENCE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __builtins__
│ │ │ │ ├── empty.tsx
│ │ │ │ ├── hooks
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── useClickAway.ts
│ │ │ │ │ └── usePrefixCls.ts
│ │ │ │ ├── icons.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── loading.ts
│ │ │ │ ├── mapSize.ts
│ │ │ │ ├── mapStatus.ts
│ │ │ │ ├── moment.ts
│ │ │ │ ├── pickDataProps.ts
│ │ │ │ ├── portal.tsx
│ │ │ │ ├── render.ts
│ │ │ │ └── toArray.ts
│ │ │ ├── array-base
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-cards
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-collapse
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-items
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── array-table
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── cascader
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── checkbox
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── date-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── date-picker2
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── editable
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── form
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-button-group
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-collapse
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-dialog
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-drawer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-grid
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-item
│ │ │ │ ├── animation.scss
│ │ │ │ ├── grid.scss
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ ├── scss
│ │ │ │ │ └── variable.scss
│ │ │ │ └── style.ts
│ │ │ ├── form-layout
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ ├── style.ts
│ │ │ │ └── useResponsiveFormLayout.ts
│ │ │ ├── form-step
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── form-tab
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── index.ts
│ │ │ ├── input
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── main.scss
│ │ │ ├── number-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── password
│ │ │ │ ├── index.tsx
│ │ │ │ ├── PasswordStrength.tsx
│ │ │ │ └── style.ts
│ │ │ ├── preview-text
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── radio
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── reset
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── select-table
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ ├── style.ts
│ │ │ │ ├── useCheckSlackly.tsx
│ │ │ │ ├── useFilterOptions.tsx
│ │ │ │ ├── useFlatOptions.tsx
│ │ │ │ ├── useSize.tsx
│ │ │ │ ├── useTitleAddon.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── space
│ │ │ │ ├── index.tsx
│ │ │ │ ├── main.scss
│ │ │ │ └── style.ts
│ │ │ ├── style.ts
│ │ │ ├── submit
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── switch
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── time-picker
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── time-picker2
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── transfer
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── tree-select
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── upload
│ │ │ ├── index.tsx
│ │ │ ├── main.scss
│ │ │ ├── placeholder.ts
│ │ │ └── style.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── path
│ │ ├── .npmignore
│ │ ├── benchmark.ts
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── accessor.spec.ts
│ │ │ │ ├── basic.spec.ts
│ │ │ │ ├── match.spec.ts
│ │ │ │ ├── parser.spec.ts
│ │ │ │ └── share.spec.ts
│ │ │ ├── contexts.ts
│ │ │ ├── destructor.ts
│ │ │ ├── index.ts
│ │ │ ├── matcher.ts
│ │ │ ├── parser.ts
│ │ │ ├── shared.ts
│ │ │ ├── tokenizer.ts
│ │ │ ├── tokens.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── react
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── docs
│ │ │ ├── api
│ │ │ │ ├── components
│ │ │ │ │ ├── ArrayField.md
│ │ │ │ │ ├── ArrayField.zh-CN.md
│ │ │ │ │ ├── ExpressionScope.md
│ │ │ │ │ ├── ExpressionScope.zh-CN.md
│ │ │ │ │ ├── Field.md
│ │ │ │ │ ├── Field.zh-CN.md
│ │ │ │ │ ├── FormConsumer.md
│ │ │ │ │ ├── FormConsumer.zh-CN.md
│ │ │ │ │ ├── FormProvider.md
│ │ │ │ │ ├── FormProvider.zh-CN.md
│ │ │ │ │ ├── ObjectField.md
│ │ │ │ │ ├── ObjectField.zh-CN.md
│ │ │ │ │ ├── RecordScope.md
│ │ │ │ │ ├── RecordScope.zh-CN.md
│ │ │ │ │ ├── RecordsScope.md
│ │ │ │ │ ├── RecordsScope.zh-CN.md
│ │ │ │ │ ├── RecursionField.md
│ │ │ │ │ ├── RecursionField.zh-CN.md
│ │ │ │ │ ├── SchemaField.md
│ │ │ │ │ ├── SchemaField.zh-CN.md
│ │ │ │ │ ├── VoidField.md
│ │ │ │ │ └── VoidField.zh-CN.md
│ │ │ │ ├── hooks
│ │ │ │ │ ├── useExpressionScope.md
│ │ │ │ │ ├── useExpressionScope.zh-CN.md
│ │ │ │ │ ├── useField.md
│ │ │ │ │ ├── useField.zh-CN.md
│ │ │ │ │ ├── useFieldSchema.md
│ │ │ │ │ ├── useFieldSchema.zh-CN.md
│ │ │ │ │ ├── useForm.md
│ │ │ │ │ ├── useForm.zh-CN.md
│ │ │ │ │ ├── useFormEffects.md
│ │ │ │ │ ├── useFormEffects.zh-CN.md
│ │ │ │ │ ├── useParentForm.md
│ │ │ │ │ └── useParentForm.zh-CN.md
│ │ │ │ └── shared
│ │ │ │ ├── connect.md
│ │ │ │ ├── connect.zh-CN.md
│ │ │ │ ├── context.md
│ │ │ │ ├── context.zh-CN.md
│ │ │ │ ├── mapProps.md
│ │ │ │ ├── mapProps.zh-CN.md
│ │ │ │ ├── mapReadPretty.md
│ │ │ │ ├── mapReadPretty.zh-CN.md
│ │ │ │ ├── observer.md
│ │ │ │ ├── observer.zh-CN.md
│ │ │ │ ├── Schema.md
│ │ │ │ └── Schema.zh-CN.md
│ │ │ ├── guide
│ │ │ │ ├── architecture.md
│ │ │ │ ├── architecture.zh-CN.md
│ │ │ │ ├── concept.md
│ │ │ │ ├── concept.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── expression.spec.tsx
│ │ │ │ ├── field.spec.tsx
│ │ │ │ ├── form.spec.tsx
│ │ │ │ ├── schema.json.spec.tsx
│ │ │ │ ├── schema.markup.spec.tsx
│ │ │ │ └── shared.tsx
│ │ │ ├── components
│ │ │ │ ├── ArrayField.tsx
│ │ │ │ ├── ExpressionScope.tsx
│ │ │ │ ├── Field.tsx
│ │ │ │ ├── FormConsumer.tsx
│ │ │ │ ├── FormProvider.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── ObjectField.tsx
│ │ │ │ ├── ReactiveField.tsx
│ │ │ │ ├── RecordScope.tsx
│ │ │ │ ├── RecordsScope.tsx
│ │ │ │ ├── RecursionField.tsx
│ │ │ │ ├── SchemaField.tsx
│ │ │ │ └── VoidField.tsx
│ │ │ ├── global.d.ts
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ ├── useAttach.ts
│ │ │ │ ├── useExpressionScope.ts
│ │ │ │ ├── useField.ts
│ │ │ │ ├── useFieldSchema.ts
│ │ │ │ ├── useForm.ts
│ │ │ │ ├── useFormEffects.ts
│ │ │ │ └── useParentForm.ts
│ │ │ ├── index.ts
│ │ │ ├── shared
│ │ │ │ ├── connect.ts
│ │ │ │ ├── context.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── render.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── reactive
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── benchmark.ts
│ │ ├── docs
│ │ │ ├── api
│ │ │ │ ├── action.md
│ │ │ │ ├── action.zh-CN.md
│ │ │ │ ├── autorun.md
│ │ │ │ ├── autorun.zh-CN.md
│ │ │ │ ├── batch.md
│ │ │ │ ├── batch.zh-CN.md
│ │ │ │ ├── define.md
│ │ │ │ ├── define.zh-CN.md
│ │ │ │ ├── hasCollected.md
│ │ │ │ ├── hasCollected.zh-CN.md
│ │ │ │ ├── markObservable.md
│ │ │ │ ├── markObservable.zh-CN.md
│ │ │ │ ├── markRaw.md
│ │ │ │ ├── markRaw.zh-CN.md
│ │ │ │ ├── model.md
│ │ │ │ ├── model.zh-CN.md
│ │ │ │ ├── observable.md
│ │ │ │ ├── observable.zh-CN.md
│ │ │ │ ├── observe.md
│ │ │ │ ├── observe.zh-CN.md
│ │ │ │ ├── raw.md
│ │ │ │ ├── raw.zh-CN.md
│ │ │ │ ├── react
│ │ │ │ │ ├── observer.md
│ │ │ │ │ └── observer.zh-CN.md
│ │ │ │ ├── reaction.md
│ │ │ │ ├── reaction.zh-CN.md
│ │ │ │ ├── toJS.md
│ │ │ │ ├── toJS.zh-CN.md
│ │ │ │ ├── tracker.md
│ │ │ │ ├── tracker.zh-CN.md
│ │ │ │ ├── typeChecker.md
│ │ │ │ ├── typeChecker.zh-CN.md
│ │ │ │ ├── untracked.md
│ │ │ │ ├── untracked.zh-CN.md
│ │ │ │ └── vue
│ │ │ │ ├── observer.md
│ │ │ │ └── observer.zh-CN.md
│ │ │ ├── guide
│ │ │ │ ├── best-practice.md
│ │ │ │ ├── best-practice.zh-CN.md
│ │ │ │ ├── concept.md
│ │ │ │ ├── concept.zh-CN.md
│ │ │ │ ├── index.md
│ │ │ │ └── index.zh-CN.md
│ │ │ ├── index.md
│ │ │ └── index.zh-CN.md
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── action.spec.ts
│ │ │ │ ├── annotations.spec.ts
│ │ │ │ ├── array.spec.ts
│ │ │ │ ├── autorun.spec.ts
│ │ │ │ ├── batch.spec.ts
│ │ │ │ ├── collections-map.spec.ts
│ │ │ │ ├── collections-set.spec.ts
│ │ │ │ ├── collections-weakmap.spec.ts
│ │ │ │ ├── collections-weakset.spec.ts
│ │ │ │ ├── define.spec.ts
│ │ │ │ ├── externals.spec.ts
│ │ │ │ ├── hasCollected.spec.ts
│ │ │ │ ├── observable.spec.ts
│ │ │ │ ├── observe.spec.ts
│ │ │ │ ├── tracker.spec.ts
│ │ │ │ └── untracked.spec.ts
│ │ │ ├── action.ts
│ │ │ ├── annotations
│ │ │ │ ├── box.ts
│ │ │ │ ├── computed.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── observable.ts
│ │ │ │ ├── ref.ts
│ │ │ │ └── shallow.ts
│ │ │ ├── array.ts
│ │ │ ├── autorun.ts
│ │ │ ├── batch.ts
│ │ │ ├── checkers.ts
│ │ │ ├── environment.ts
│ │ │ ├── externals.ts
│ │ │ ├── global.d.ts
│ │ │ ├── handlers.ts
│ │ │ ├── index.ts
│ │ │ ├── internals.ts
│ │ │ ├── model.ts
│ │ │ ├── observable.ts
│ │ │ ├── observe.ts
│ │ │ ├── reaction.ts
│ │ │ ├── tracker.ts
│ │ │ ├── tree.ts
│ │ │ ├── types.ts
│ │ │ └── untracked.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── reactive-react
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ ├── useCompatEffect.ts
│ │ │ │ ├── useCompatFactory.ts
│ │ │ │ ├── useDidUpdate.ts
│ │ │ │ ├── useForceUpdate.ts
│ │ │ │ ├── useLayoutEffect.ts
│ │ │ │ └── useObserver.ts
│ │ │ ├── index.ts
│ │ │ ├── observer.ts
│ │ │ ├── shared
│ │ │ │ ├── gc.ts
│ │ │ │ ├── global.ts
│ │ │ │ ├── immediate.ts
│ │ │ │ └── index.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── reactive-test-cases-for-react18
│ │ ├── .npmignore
│ │ ├── .umirc.js
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── index.js
│ │ │ └── MySlowList.js
│ │ ├── template.ejs
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ ├── webpack.base.ts
│ │ ├── webpack.dev.ts
│ │ └── webpack.prod.ts
│ ├── reactive-vue
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ └── observer.spec.ts
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ └── useObserver.ts
│ │ │ ├── index.ts
│ │ │ ├── observer
│ │ │ │ ├── collectData.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── observerInVue2.ts
│ │ │ │ └── observerInVue3.ts
│ │ │ └── types.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── shared
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ └── index.spec.ts
│ │ │ ├── array.ts
│ │ │ ├── case.ts
│ │ │ ├── checkers.ts
│ │ │ ├── clone.ts
│ │ │ ├── compare.ts
│ │ │ ├── defaults.ts
│ │ │ ├── deprecate.ts
│ │ │ ├── global.ts
│ │ │ ├── index.ts
│ │ │ ├── instanceof.ts
│ │ │ ├── isEmpty.ts
│ │ │ ├── merge.ts
│ │ │ ├── middleware.ts
│ │ │ ├── path.ts
│ │ │ ├── string.ts
│ │ │ ├── subscribable.ts
│ │ │ └── uid.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ ├── validator
│ │ ├── .npmignore
│ │ ├── LICENSE.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── rollup.config.js
│ │ ├── src
│ │ │ ├── __tests__
│ │ │ │ ├── parser.spec.ts
│ │ │ │ ├── registry.spec.ts
│ │ │ │ └── validator.spec.ts
│ │ │ ├── formats.ts
│ │ │ ├── index.ts
│ │ │ ├── locale.ts
│ │ │ ├── parser.ts
│ │ │ ├── registry.ts
│ │ │ ├── rules.ts
│ │ │ ├── template.ts
│ │ │ ├── types.ts
│ │ │ └── validator.ts
│ │ ├── tsconfig.build.json
│ │ └── tsconfig.json
│ └── vue
│ ├── .npmignore
│ ├── bin
│ │ ├── formily-vue-fix.js
│ │ └── formily-vue-switch.js
│ ├── docs
│ │ ├── .vuepress
│ │ │ ├── components
│ │ │ │ ├── createCodeSandBox.js
│ │ │ │ ├── dumi-previewer.vue
│ │ │ │ └── highlight.js
│ │ │ ├── config.js
│ │ │ ├── enhanceApp.js
│ │ │ └── styles
│ │ │ └── index.styl
│ │ ├── api
│ │ │ ├── components
│ │ │ │ ├── array-field.md
│ │ │ │ ├── expression-scope.md
│ │ │ │ ├── field.md
│ │ │ │ ├── form-consumer.md
│ │ │ │ ├── form-provider.md
│ │ │ │ ├── object-field.md
│ │ │ │ ├── recursion-field-with-component.md
│ │ │ │ ├── recursion-field.md
│ │ │ │ ├── schema-field-with-schema.md
│ │ │ │ ├── schema-field.md
│ │ │ │ └── void-field.md
│ │ │ ├── hooks
│ │ │ │ ├── use-field-schema.md
│ │ │ │ ├── use-field.md
│ │ │ │ ├── use-form-effects.md
│ │ │ │ ├── use-form.md
│ │ │ │ └── use-parent-form.md
│ │ │ └── shared
│ │ │ ├── connect.md
│ │ │ ├── injections.md
│ │ │ ├── map-props.md
│ │ │ ├── map-read-pretty.md
│ │ │ ├── observer.md
│ │ │ └── schema.md
│ │ ├── demos
│ │ │ ├── api
│ │ │ │ ├── components
│ │ │ │ │ ├── array-field.vue
│ │ │ │ │ ├── expression-scope.vue
│ │ │ │ │ ├── field.vue
│ │ │ │ │ ├── form-consumer.vue
│ │ │ │ │ ├── form-provider.vue
│ │ │ │ │ ├── object-field.vue
│ │ │ │ │ ├── recursion-field-with-component.vue
│ │ │ │ │ ├── recursion-field.vue
│ │ │ │ │ ├── schema-field-with-schema.vue
│ │ │ │ │ ├── schema-field.vue
│ │ │ │ │ └── void-field.vue
│ │ │ │ ├── hooks
│ │ │ │ │ ├── use-field-schema.vue
│ │ │ │ │ ├── use-field.vue
│ │ │ │ │ ├── use-form-effects.vue
│ │ │ │ │ ├── use-form.vue
│ │ │ │ │ └── use-parent-form.vue
│ │ │ │ └── shared
│ │ │ │ ├── connect.vue
│ │ │ │ ├── map-props.vue
│ │ │ │ ├── map-read-pretty.vue
│ │ │ │ └── observer.vue
│ │ │ ├── index.vue
│ │ │ └── questions
│ │ │ ├── default-slot.vue
│ │ │ ├── events.vue
│ │ │ ├── named-slot.vue
│ │ │ └── scoped-slot.vue
│ │ ├── guide
│ │ │ ├── architecture.md
│ │ │ ├── concept.md
│ │ │ └── README.md
│ │ ├── questions
│ │ │ └── README.md
│ │ └── README.md
│ ├── package.json
│ ├── README.md
│ ├── rollup.config.js
│ ├── scripts
│ │ ├── postinstall.js
│ │ ├── switch-cli.js
│ │ └── utils.js
│ ├── src
│ │ ├── __tests__
│ │ │ ├── expression.scope.spec.ts
│ │ │ ├── field.spec.ts
│ │ │ ├── form.spec.ts
│ │ │ ├── schema.json.spec.ts
│ │ │ ├── schema.markup.spec.ts
│ │ │ ├── shared.spec.ts
│ │ │ └── utils.spec.ts
│ │ ├── components
│ │ │ ├── ArrayField.ts
│ │ │ ├── ExpressionScope.ts
│ │ │ ├── Field.ts
│ │ │ ├── FormConsumer.ts
│ │ │ ├── FormProvider.ts
│ │ │ ├── index.ts
│ │ │ ├── ObjectField.ts
│ │ │ ├── ReactiveField.ts
│ │ │ ├── RecursionField.ts
│ │ │ ├── SchemaField.ts
│ │ │ └── VoidField.ts
│ │ ├── global.d.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useAttach.ts
│ │ │ ├── useField.ts
│ │ │ ├── useFieldSchema.ts
│ │ │ ├── useForm.ts
│ │ │ ├── useFormEffects.ts
│ │ │ ├── useInjectionCleaner.ts
│ │ │ └── useParentForm.ts
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── connect.ts
│ │ │ ├── context.ts
│ │ │ ├── createForm.ts
│ │ │ ├── fragment.ts
│ │ │ ├── h.ts
│ │ │ └── index.ts
│ │ ├── types
│ │ │ └── index.ts
│ │ ├── utils
│ │ │ ├── formatVNodeData.ts
│ │ │ ├── getFieldProps.ts
│ │ │ ├── getRawComponent.ts
│ │ │ └── resolveSchemaProps.ts
│ │ └── vue2-components.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ └── tsconfig.types.json
├── README.md
├── README.zh-cn.md
├── scripts
│ ├── build-style
│ │ ├── buildAllStyles.ts
│ │ ├── copy.ts
│ │ ├── helper.ts
│ │ └── index.ts
│ └── rollup.base.js
├── tsconfig.build.json
├── tsconfig.jest.json
├── tsconfig.json
└── yarn.lock
```
# Files
--------------------------------------------------------------------------------
/packages/vue/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
```javascript
1 | const path = require('path')
2 |
3 | module.exports = {
4 | title: 'Formily Vue',
5 | dest: './doc-site',
6 | theme: '@vuepress-dumi/dumi',
7 | head: [
8 | [
9 | 'link',
10 | {
11 | rel: 'icon',
12 | href: '//img.alicdn.com/imgextra/i3/O1CN01XtT3Tv1Wd1b5hNVKy_!!6000000002810-55-tps-360-360.svg',
13 | },
14 | ],
15 | ],
16 | themeConfig: {
17 | logo: '//img.alicdn.com/imgextra/i2/O1CN01Kq3OHU1fph6LGqjIz_!!6000000004056-55-tps-1141-150.svg',
18 | nav: [
19 | {
20 | text: '指南',
21 | link: '/guide/',
22 | },
23 | {
24 | text: 'API',
25 | link: '/api/components/field',
26 | },
27 | {
28 | text: 'Q&A',
29 | link: '/questions/',
30 | },
31 | {
32 | text: '主站',
33 | link: 'https://formilyjs.org',
34 | },
35 | {
36 | text: 'GITHUB',
37 | link: 'https://github.com/alibaba/formily',
38 | },
39 | ],
40 | sidebar: {
41 | '/guide/': ['', 'architecture', 'concept'],
42 | '/api/': [
43 | {
44 | title: 'Components',
45 | children: [
46 | '/api/components/field',
47 | '/api/components/array-field',
48 | '/api/components/object-field',
49 | '/api/components/void-field',
50 | '/api/components/schema-field',
51 | '/api/components/schema-field-with-schema',
52 | '/api/components/recursion-field',
53 | '/api/components/recursion-field-with-component',
54 | '/api/components/form-provider',
55 | '/api/components/form-consumer',
56 | '/api/components/expression-scope',
57 | ],
58 | },
59 | {
60 | title: 'Hooks',
61 | children: [
62 | '/api/hooks/use-field',
63 | '/api/hooks/use-field-schema',
64 | '/api/hooks/use-form',
65 | '/api/hooks/use-form-effects',
66 | '/api/hooks/use-parent-form',
67 | ],
68 | },
69 | {
70 | title: 'Shared',
71 | children: [
72 | '/api/shared/connect',
73 | '/api/shared/injections',
74 | '/api/shared/map-props',
75 | '/api/shared/map-read-pretty',
76 | '/api/shared/observer',
77 | '/api/shared/schema',
78 | ],
79 | },
80 | ],
81 | },
82 | lastUpdated: 'Last Updated',
83 | smoothScroll: true,
84 | },
85 | plugins: [
86 | 'vuepress-plugin-typescript',
87 | '@vuepress/back-to-top',
88 | '@vuepress/last-updated',
89 | '@vuepress-dumi/dumi-previewer',
90 | [
91 | '@vuepress/medium-zoom',
92 | {
93 | selector: '.content__default :not(a) > img',
94 | },
95 | ],
96 | ],
97 | configureWebpack: (config, isServer) => {
98 | return {
99 | resolve: {
100 | alias: {
101 | '@formily/vue': path.resolve(__dirname, '../../src'),
102 | '@formily/json-schema': path.resolve(
103 | __dirname,
104 | '../../../json-schema/src'
105 | ),
106 | '@formily/path': path.resolve(__dirname, '../../../path/src'),
107 | '@formily/reactive-vue': path.resolve(
108 | __dirname,
109 | '../../../reactive-vue/src'
110 | ),
111 | '@formily/element': path.resolve(__dirname, '../../../element/src'),
112 | vue: path.resolve(
113 | __dirname,
114 | '../../../../node_modules/vue/dist/vue.runtime.esm.js'
115 | ),
116 | },
117 | },
118 | }
119 | },
120 | }
121 |
```
--------------------------------------------------------------------------------
/packages/json-schema/src/compiler.ts:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | isArr,
3 | isFn,
4 | isPlainObj,
5 | isStr,
6 | reduce,
7 | FormPath,
8 | } from '@formily/shared'
9 | import { IGeneralFieldState } from '@formily/core'
10 | import { untracked, hasCollected } from '@formily/reactive'
11 | import {
12 | traverse,
13 | traverseSchema,
14 | isNoNeedCompileObject,
15 | hasOwnProperty,
16 | patchStateFormSchema,
17 | } from './shared'
18 | import { ISchema } from './types'
19 |
20 | const ExpRE = /^\s*\{\{([\s\S]*)\}\}\s*$/
21 | const Registry = {
22 | silent: false,
23 | compile(expression: string, scope = {}) {
24 | if (Registry.silent) {
25 | try {
26 | return new Function('$root', `with($root) { return (${expression}); }`)(
27 | scope
28 | )
29 | } catch {}
30 | } else {
31 | return new Function('$root', `with($root) { return (${expression}); }`)(
32 | scope
33 | )
34 | }
35 | },
36 | }
37 |
38 | export const silent = (value = true) => {
39 | Registry.silent = !!value
40 | }
41 |
42 | export const registerCompiler = (
43 | compiler: (expression: string, scope: any) => any
44 | ) => {
45 | if (isFn(compiler)) {
46 | Registry.compile = compiler
47 | }
48 | }
49 |
50 | export const shallowCompile = <Source = any, Scope = any>(
51 | source: Source,
52 | scope?: Scope
53 | ) => {
54 | if (isStr(source)) {
55 | const matched = source.match(ExpRE)
56 | if (!matched) return source
57 | return Registry.compile(matched[1], scope)
58 | }
59 | return source
60 | }
61 |
62 | export const compile = <Source = any, Scope = any>(
63 | source: Source,
64 | scope?: Scope
65 | ): any => {
66 | const seenObjects = []
67 | const compile = (source: any) => {
68 | if (isStr(source)) {
69 | return shallowCompile(source, scope)
70 | } else if (isArr(source)) {
71 | return source.map((value: any) => compile(value))
72 | } else if (isPlainObj(source)) {
73 | if (isNoNeedCompileObject(source)) return source
74 | const seenIndex = seenObjects.indexOf(source)
75 | if (seenIndex > -1) {
76 | return source
77 | }
78 | const addIndex = seenObjects.length
79 | seenObjects.push(source)
80 | const results = reduce(
81 | source,
82 | (buf, value, key) => {
83 | buf[key] = compile(value)
84 | return buf
85 | },
86 | {}
87 | )
88 | seenObjects.splice(addIndex, 1)
89 | return results
90 | }
91 | return source
92 | }
93 | return compile(source)
94 | }
95 |
96 | export const patchCompile = (
97 | targetState: IGeneralFieldState,
98 | sourceState: any,
99 | scope: any
100 | ) => {
101 | traverse(sourceState, (value, pattern) => {
102 | const compiled = compile(value, scope)
103 | if (compiled === undefined) return
104 | const path = FormPath.parse(pattern)
105 | const key = path.segments[0]
106 | if (hasOwnProperty.call(targetState, key)) {
107 | untracked(() => FormPath.setIn(targetState, path, compiled))
108 | }
109 | })
110 | }
111 |
112 | export const patchSchemaCompile = (
113 | targetState: IGeneralFieldState,
114 | sourceSchema: ISchema,
115 | scope: any,
116 | demand = false
117 | ) => {
118 | traverseSchema(sourceSchema, (value, path, omitCompile) => {
119 | let compiled = value
120 | let collected = hasCollected(() => {
121 | if (!omitCompile) {
122 | compiled = compile(value, scope)
123 | }
124 | })
125 | if (compiled === undefined) return
126 | if (demand) {
127 | if (collected || !targetState.initialized) {
128 | patchStateFormSchema(targetState, path, compiled)
129 | }
130 | } else {
131 | patchStateFormSchema(targetState, path, compiled)
132 | }
133 | })
134 | }
135 |
```
--------------------------------------------------------------------------------
/packages/reactive/src/__tests__/externals.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | isObservable,
3 | isSupportObservable,
4 | markObservable,
5 | markRaw,
6 | observable,
7 | toJS,
8 | } from '..'
9 |
10 | test('is support observable', () => {
11 | const obs = observable<any>({ aa: 111 })
12 | class Class {}
13 |
14 | expect(isSupportObservable(obs)).toBe(true)
15 | expect(isSupportObservable(new Class())).toBe(true)
16 | expect(isSupportObservable(null)).toBe(false)
17 | expect(isSupportObservable([])).toBe(true)
18 | expect(isSupportObservable({})).toBe(true)
19 | expect(isSupportObservable({ $$typeof: {}, _owner: {} })).toBe(false)
20 | expect(isSupportObservable({ _isAMomentObject: {} })).toBe(false)
21 | expect(isSupportObservable({ _isJSONSchemaObject: {} })).toBe(false)
22 | expect(isSupportObservable({ toJS: () => {} })).toBe(false)
23 | expect(isSupportObservable({ toJSON: () => {} })).toBe(false)
24 | expect(isSupportObservable(new Map())).toBe(true)
25 | expect(isSupportObservable(new WeakMap())).toBe(true)
26 | expect(isSupportObservable(new Set())).toBe(true)
27 | expect(isSupportObservable(new WeakSet())).toBe(true)
28 | })
29 |
30 | describe('mark operation', () => {
31 | test('plain object should be observable', () => {
32 | const obs = observable<any>({ aa: 111 })
33 | expect(isObservable(obs)).toBe(true)
34 | })
35 |
36 | test('class instance should be observable', () => {
37 | class Class {}
38 | const obs = observable<any>(new Class())
39 | const obs2 = observable<any>(new Class())
40 | expect(isObservable(obs)).toBe(true)
41 | expect(isObservable(obs2)).toBe(true)
42 | })
43 |
44 | test('object with toJS function should NOT be observable', () => {
45 | const obs = observable<any>({ aa: 111, toJS: () => {} })
46 | expect(isObservable(obs)).toBe(false)
47 | })
48 |
49 | test('plain object marked as raw should NOT be observable', () => {
50 | const obs = observable<any>(markRaw({ aa: 111 }))
51 | expect(isObservable(obs)).toBe(false)
52 | })
53 |
54 | test('class marked as raw instance should NOT be observable', () => {
55 | class Class {}
56 | markRaw(Class)
57 | const obs = observable<any>(new Class())
58 | const obs2 = observable<any>(new Class())
59 | expect(isObservable(obs)).toBe(false)
60 | expect(isObservable(obs2)).toBe(false)
61 | })
62 |
63 | test('object with toJS function marked as observable should be observable', () => {
64 | const obs = observable<any>(markObservable({ aa: 111, toJS: () => {} }))
65 | expect(isObservable(obs)).toBe(true)
66 | })
67 |
68 | test('plain object marked as raw and observable should NOT be observable', () => {
69 | const obs = observable<any>(markRaw(markObservable({ aa: 111 })))
70 | expect(isObservable(obs)).toBe(false)
71 | })
72 |
73 | test('plain object marked as observable and raw should NOT be observable', () => {
74 | const obs = observable<any>(markObservable(markRaw({ aa: 111 })))
75 | expect(isObservable(obs)).toBe(false)
76 | })
77 |
78 | test('function marked as observable should NOT be observable', () => {
79 | const obs = observable<any>(markObservable(() => {}))
80 | expect(isObservable(obs)).toBe(false)
81 | })
82 | })
83 |
84 | test('recursive references tojs', () => {
85 | const obj: any = { aa: 111 }
86 | obj.obj = obj
87 | const obs = observable<any>(obj)
88 | obs.obs = obs
89 | expect(toJS(obs)).toBeTruthy()
90 |
91 | const arrObs = observable([{ aa: 1 }, { bb: 2 }, { cc: 3 }])
92 | expect(toJS(arrObs)).toEqual([{ aa: 1 }, { bb: 2 }, { cc: 3 }])
93 | })
94 |
```
--------------------------------------------------------------------------------
/packages/element/src/upload/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Field } from '@formily/core'
2 | import { connect, Fragment, h, mapProps, useField } from '@formily/vue'
3 | import { defineComponent } from 'vue-demi'
4 |
5 | import type {
6 | ElUpload as ElUploadProps,
7 | ElUploadInternalFileDetail,
8 | } from 'element-ui/types/upload'
9 |
10 | import { Button as ElButton, Upload as ElUpload } from 'element-ui'
11 |
12 | export type UploadProps = ElUploadProps & {
13 | textContent?: String
14 | errorAdaptor?: (error?: ErrorEvent) => String
15 | }
16 |
17 | const UploadWrapper = defineComponent<UploadProps>({
18 | name: 'FUpload',
19 | props: {
20 | textContent: {
21 | type: String,
22 | default: '',
23 | },
24 | errorAdaptor: {
25 | type: Function,
26 | default(error?: ErrorEvent) {
27 | return error?.message || ''
28 | },
29 | },
30 | },
31 | setup(curProps: UploadProps, { slots, attrs, listeners, emit }) {
32 | return () => {
33 | const fieldRef = useField<Field>()
34 | const setFeedBack = (error?: ErrorEvent) => {
35 | const message = curProps.errorAdaptor(error)
36 |
37 | fieldRef.value.setFeedback({
38 | type: 'error',
39 | code: 'UploadError',
40 | messages: message ? [message] : [],
41 | })
42 | }
43 |
44 | const props = {
45 | ...attrs,
46 | onChange(
47 | file: ElUploadInternalFileDetail,
48 | fileList: ElUploadInternalFileDetail[]
49 | ) {
50 | ;(attrs.onChange as Function)?.(file, fileList)
51 | setFeedBack()
52 | emit('change', fileList)
53 | },
54 |
55 | onRemove(
56 | file: ElUploadInternalFileDetail,
57 | fileList: ElUploadInternalFileDetail[]
58 | ) {
59 | ;(attrs.onRemove as Function)?.(file, fileList)
60 | setFeedBack()
61 | emit('change', fileList)
62 | },
63 |
64 | onError(
65 | error: ErrorEvent,
66 | file: ElUploadInternalFileDetail,
67 | fileList: ElUploadInternalFileDetail[]
68 | ) {
69 | ;(attrs.onError as Function)?.(error, file, fileList)
70 |
71 | setTimeout(() => {
72 | setFeedBack(error)
73 | }, 0)
74 | },
75 | }
76 | const children = {
77 | ...slots,
78 | }
79 | if (!slots.default) {
80 | children.default = () => {
81 | const listType = attrs.listType
82 | const drag = attrs.drag
83 |
84 | if (drag) {
85 | return h(
86 | Fragment,
87 | {},
88 | {
89 | default: () => [
90 | h('i', { staticClass: 'el-icon-upload' }, {}),
91 | h(
92 | 'div',
93 | { staticClass: 'el-upload__text' },
94 | { default: () => [curProps.textContent] }
95 | ),
96 | ],
97 | }
98 | )
99 | }
100 |
101 | if (listType === 'picture-card') {
102 | return h(
103 | 'i',
104 | {
105 | staticClass: 'el-icon-plus',
106 | },
107 | {}
108 | )
109 | }
110 |
111 | return h(
112 | ElButton,
113 | { props: { icon: 'el-icon-upload2' } },
114 | { default: () => [curProps.textContent] }
115 | )
116 | }
117 | }
118 | return h(ElUpload, { attrs: props, on: listeners }, children)
119 | }
120 | },
121 | })
122 |
123 | export const Upload = connect(
124 | UploadWrapper,
125 | mapProps({ readOnly: 'readonly', value: 'fileList' })
126 | )
127 |
128 | export default Upload
129 |
```
--------------------------------------------------------------------------------
/packages/reactive/src/__tests__/observe.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { observable, observe } from '../'
2 |
3 | test('deep observe', () => {
4 | const obs = observable<any>({
5 | aa: {
6 | bb: {
7 | cc: [11, 22, 33],
8 | },
9 | },
10 | ee: observable([]),
11 | })
12 | const handler = jest.fn()
13 | observe(obs, handler)
14 | obs.dd = 123
15 | obs.aa.bb.cc.push(44)
16 | expect(obs.aa.bb.cc).toEqual([11, 22, 33, 44])
17 | expect(handler).toHaveBeenCalledTimes(2)
18 | delete obs.aa
19 | expect(handler).toHaveBeenCalledTimes(3)
20 |
21 | // Are these expected behaviors?
22 | obs.ee.push(11)
23 | expect(handler).toHaveBeenCalledTimes(3)
24 | obs.ee = []
25 | expect(handler).toHaveBeenCalledTimes(4)
26 | obs.ee.push(11)
27 | expect(handler).toHaveBeenCalledTimes(5)
28 | })
29 |
30 | test('shallow observe', () => {
31 | const obs = observable<any>({
32 | aa: {
33 | bb: {
34 | cc: [11, 22, 33],
35 | },
36 | },
37 | })
38 | const handler = jest.fn()
39 | observe(obs, handler, false)
40 | obs.dd = 123
41 | obs.aa.bb.cc.push(44)
42 | expect(obs.aa.bb.cc).toEqual([11, 22, 33, 44])
43 | expect(handler).toHaveBeenCalledTimes(1)
44 | delete obs.aa
45 | expect(handler).toHaveBeenCalledTimes(2)
46 | })
47 |
48 | test('root replace observe', () => {
49 | const obs = observable<any>({
50 | aa: {
51 | bb: {
52 | cc: [11, 22, 33],
53 | },
54 | },
55 | })
56 | const handler1 = jest.fn()
57 | const handler = jest.fn()
58 | observe(obs, handler1)
59 | observe(obs.aa, handler)
60 | obs.aa = {
61 | mm: 123,
62 | }
63 | expect(handler1).toBeCalledTimes(1)
64 | expect(handler).toBeCalledTimes(1)
65 | obs.aa = {
66 | bb: {
67 | cc: [11, 22, 33],
68 | },
69 | }
70 | obs.aa.bb.cc.push(44)
71 | expect(handler1).toBeCalledTimes(3)
72 | expect(handler).toBeCalledTimes(3)
73 | })
74 |
75 | test('dispose observe', () => {
76 | const obs = observable<any>({
77 | aa: {
78 | bb: {
79 | cc: [11, 22, 33],
80 | },
81 | },
82 | })
83 | const handler = jest.fn()
84 | const dispose = observe(obs, handler)
85 | obs.kk = 123
86 | expect(handler).toBeCalledTimes(1)
87 | dispose()
88 | obs.aa = 123
89 | expect(handler).toBeCalledTimes(1)
90 | })
91 |
92 | test('dispose observe', () => {
93 | const obs = observable<any>({
94 | aa: {
95 | bb: {
96 | cc: [11, 22, 33],
97 | },
98 | },
99 | })
100 | const handler = jest.fn()
101 | const dispose = observe(obs.aa, handler)
102 | obs.kk = 111
103 | expect(handler).toBeCalledTimes(0)
104 | obs.aa = { mm: 222 }
105 | expect(handler).toBeCalledTimes(1)
106 | obs.aa = { mm: 222 }
107 | expect(handler).toBeCalledTimes(2)
108 | obs.aa = { mm: '111' }
109 | expect(handler).toBeCalledTimes(3)
110 | obs.aa = { mm: 333 }
111 | expect(handler).toBeCalledTimes(4)
112 | dispose()
113 | obs.aa = { mm: 444 }
114 | expect(handler).toBeCalledTimes(4)
115 | })
116 |
117 | test('array delete', () => {
118 | const array = observable([{ value: 1 }, { value: 2 }])
119 |
120 | const fn = jest.fn()
121 |
122 | const dispose = observe(array, (change) => {
123 | if (change.type === 'set' && change.key === 'value') {
124 | fn(change.path?.join('.'))
125 | }
126 | })
127 |
128 | array[0].value = 3
129 | expect(fn.mock.calls[0][0]).toBe('0.value')
130 |
131 | array.splice(0, 1)
132 |
133 | array[0].value = 3
134 | expect(fn.mock.calls[1][0]).toBe('0.value')
135 |
136 | dispose()
137 | })
138 |
139 | test('observe dynamic tree', () => {
140 | const handler = jest.fn()
141 | const tree = observable<any>({})
142 | const childTree = observable({})
143 | tree.children = childTree
144 | observe(tree, handler)
145 | tree.children.aa = 123
146 | expect(handler).toBeCalledTimes(1)
147 | })
148 |
149 | test('invalid target', () => {
150 | expect(() => observe(function () {})).toThrowError()
151 | })
152 |
```
--------------------------------------------------------------------------------
/packages/validator/src/formats.ts:
--------------------------------------------------------------------------------
```typescript
1 | export default {
2 | url: new RegExp(
3 | // protocol identifier
4 | '^(?:(?:(?:https?|ftp|rtmp):)?//)' +
5 | // user:pass authentication
6 | '(?:\\S+(?::\\S*)?@)?' +
7 | '(?:' +
8 | // IP address exclusion - private & local networks
9 | // Reference: https://www.arin.net/knowledge/address_filters.html
10 |
11 | // filter 10.*.*.* and 127.*.*.* addresses
12 | '(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
13 | // filter 169.254.*.* and 192.168.*.*
14 | '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
15 | // filter 172.16.0.0 - 172.31.255.255
16 | // TODO: add test to validate that it invalidates address in 16-31 range
17 | '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
18 | // IP address dotted notation octets
19 | // excludes loopback network 0.0.0.0
20 | // excludes reserved space >= 224.0.0.0
21 | // excludes network & broadcast addresses
22 | // (first & last IP address of each class)
23 |
24 | // filter 1. part for 1-223
25 | '(?:22[0-3]|2[01]\\d|[1-9]\\d?|1\\d\\d)' +
26 | // filter 2. and 3. part for 0-255
27 | '(?:\\.(?:25[0-5]|2[0-4]\\d|1?\\d{1,2})){2}' +
28 | // filter 4. part for 1-254
29 | '(?:\\.(?:25[0-4]|2[0-4]\\d|1\\d\\d|[1-9]\\d?))' +
30 | '|' +
31 | // host name
32 | '(?:(?:[a-z\\u00a1-\\uffff0-9_]-*)*[a-z\\u00a1-\\uffff0-9_]+)' +
33 | // domain name
34 | '(?:\\.(?:[a-z\\u00a1-\\uffff0-9_]-*)*[a-z\\u00a1-\\uffff0-9_]+)*' +
35 | // TLD identifier
36 | '(?:\\.(?:[a-z\\u00a1-\\uffff_]{2,}))' +
37 | ')' +
38 | // port number
39 | '(?::\\d{2,5})?' +
40 | // resource path
41 | '(?:/?\\S*)?$'
42 | ),
43 | email: /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
44 |
45 | ipv6: /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,
46 |
47 | ipv4: /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/,
48 |
49 | number: /^[+-]?\d+(\.\d+)?$/,
50 |
51 | integer: /^[+-]?\d+$/,
52 |
53 | qq: /^(\+?[1-9]\d*|0)$/,
54 |
55 | phone: /^\d{3}-\d{8}$|^\d{4}-\d{7}$|^\d{11}$/,
56 |
57 | idcard: /^\d{15}$|^\d{17}(\d|x|X)$/,
58 |
59 | money:
60 | /^([\u0024\u00A2\u00A3\u00A4\u20AC\u00A5\u20B1\u20B9\uFFE5]\s*)(\d+,?)+(\.\d+)?\s*$/,
61 |
62 | zh: /^[\u4e00-\u9fa5]+$/,
63 |
64 | date: /^[0-9]+[./-][0-9]+[./-][0-9]+\s*(?:[0-9]+\s*:\s*[0-9]+\s*:\s*[0-9]+)?$/,
65 |
66 | zip: /^[0-9]{6}$/,
67 | }
68 |
```
--------------------------------------------------------------------------------
/packages/core/src/models/VoidField.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { toArr, FormPathPattern } from '@formily/shared'
2 | import { define, observable, batch, action } from '@formily/reactive'
3 | import {
4 | createReactions,
5 | createStateSetter,
6 | createStateGetter,
7 | initializeStart,
8 | initializeEnd,
9 | } from '../shared/internals'
10 | import {
11 | IModelSetter,
12 | IModelGetter,
13 | IVoidFieldProps,
14 | IVoidFieldState,
15 | } from '../types'
16 | import { Form } from './Form'
17 | import { BaseField } from './BaseField'
18 |
19 | export class VoidField<
20 | Decorator = any,
21 | Component = any,
22 | TextType = any
23 | > extends BaseField<Decorator, Component, TextType> {
24 | displayName: 'VoidField' = 'VoidField'
25 | props: IVoidFieldProps<Decorator, Component>
26 |
27 | constructor(
28 | address: FormPathPattern,
29 | props: IVoidFieldProps<Decorator, Component>,
30 | form: Form,
31 | designable: boolean
32 | ) {
33 | super()
34 | this.form = form
35 | this.props = props
36 | this.designable = designable
37 | initializeStart()
38 | this.locate(address)
39 | this.initialize()
40 | this.makeObservable()
41 | this.makeReactive()
42 | this.onInit()
43 | initializeEnd()
44 | }
45 |
46 | protected initialize() {
47 | this.mounted = false
48 | this.unmounted = false
49 | this.initialized = false
50 | this.title = this.props.title
51 | this.description = this.props.description
52 | this.pattern = this.props.pattern
53 | this.display = this.props.display
54 | this.hidden = this.props.hidden
55 | this.editable = this.props.editable
56 | this.disabled = this.props.disabled
57 | this.readOnly = this.props.readOnly
58 | this.readPretty = this.props.readPretty
59 | this.visible = this.props.visible
60 | this.content = this.props.content
61 | this.data = this.props.data
62 | this.decorator = toArr(this.props.decorator)
63 | this.component = toArr(this.props.component)
64 | }
65 |
66 | protected makeObservable() {
67 | if (this.designable) return
68 | define(this, {
69 | path: observable.ref,
70 | title: observable.ref,
71 | description: observable.ref,
72 | selfDisplay: observable.ref,
73 | selfPattern: observable.ref,
74 | initialized: observable.ref,
75 | mounted: observable.ref,
76 | unmounted: observable.ref,
77 | decoratorType: observable.ref,
78 | componentType: observable.ref,
79 | content: observable.ref,
80 | data: observable.shallow,
81 | decoratorProps: observable,
82 | componentProps: observable,
83 | display: observable.computed,
84 | pattern: observable.computed,
85 | hidden: observable.computed,
86 | visible: observable.computed,
87 | disabled: observable.computed,
88 | readOnly: observable.computed,
89 | readPretty: observable.computed,
90 | editable: observable.computed,
91 | component: observable.computed,
92 | decorator: observable.computed,
93 | indexes: observable.computed,
94 | setTitle: action,
95 | setDescription: action,
96 | setDisplay: action,
97 | setPattern: action,
98 | setComponent: action,
99 | setComponentProps: action,
100 | setDecorator: action,
101 | setDecoratorProps: action,
102 | setData: action,
103 | setContent: action,
104 | onInit: batch,
105 | onMount: batch,
106 | onUnmount: batch,
107 | })
108 | }
109 |
110 | protected makeReactive() {
111 | if (this.designable) return
112 | createReactions(this)
113 | }
114 |
115 | setState: IModelSetter<IVoidFieldState> = createStateSetter(this)
116 |
117 | getState: IModelGetter<IVoidFieldState> = createStateGetter(this)
118 | }
119 |
```
--------------------------------------------------------------------------------
/packages/react/src/components/ReactiveField.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { Fragment, useContext } from 'react'
2 | import { toJS } from '@formily/reactive'
3 | import { observer } from '@formily/reactive-react'
4 | import { FormPath, isFn } from '@formily/shared'
5 | import { isVoidField, GeneralField, Form } from '@formily/core'
6 | import { SchemaComponentsContext } from '../shared'
7 | import { RenderPropsChildren } from '../types'
8 | interface IReactiveFieldProps {
9 | field: GeneralField
10 | children?: RenderPropsChildren<GeneralField>
11 | }
12 |
13 | const mergeChildren = (
14 | children: RenderPropsChildren<GeneralField>,
15 | content: React.ReactNode
16 | ) => {
17 | if (!children && !content) return
18 | if (isFn(children)) return
19 | return (
20 | <Fragment>
21 | {children}
22 | {content}
23 | </Fragment>
24 | )
25 | }
26 |
27 | const isValidComponent = (target: any) =>
28 | target && (typeof target === 'object' || typeof target === 'function')
29 |
30 | const renderChildren = (
31 | children: RenderPropsChildren<GeneralField>,
32 | field?: GeneralField,
33 | form?: Form
34 | ) => (isFn(children) ? children(field, form) : children)
35 |
36 | const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => {
37 | const components = useContext(SchemaComponentsContext)
38 | if (!props.field) {
39 | return <Fragment>{renderChildren(props.children)}</Fragment>
40 | }
41 | const field = props.field
42 | const content = mergeChildren(
43 | renderChildren(props.children, field, field.form),
44 | field.content ?? field.componentProps.children
45 | )
46 | if (field.display !== 'visible') return null
47 |
48 | const getComponent = (target: any) => {
49 | return isValidComponent(target)
50 | ? target
51 | : FormPath.getIn(components, target) ?? target
52 | }
53 |
54 | const renderDecorator = (children: React.ReactNode) => {
55 | if (!field.decoratorType) {
56 | return <Fragment>{children}</Fragment>
57 | }
58 |
59 | return React.createElement(
60 | getComponent(field.decoratorType),
61 | toJS(field.decoratorProps),
62 | children
63 | )
64 | }
65 |
66 | const renderComponent = () => {
67 | if (!field.componentType) return content
68 | const value = !isVoidField(field) ? field.value : undefined
69 | const onChange = !isVoidField(field)
70 | ? (...args: any[]) => {
71 | field.onInput(...args)
72 | field.componentProps?.onChange?.(...args)
73 | }
74 | : field.componentProps?.onChange
75 | const onFocus = !isVoidField(field)
76 | ? (...args: any[]) => {
77 | field.onFocus(...args)
78 | field.componentProps?.onFocus?.(...args)
79 | }
80 | : field.componentProps?.onFocus
81 | const onBlur = !isVoidField(field)
82 | ? (...args: any[]) => {
83 | field.onBlur(...args)
84 | field.componentProps?.onBlur?.(...args)
85 | }
86 | : field.componentProps?.onBlur
87 | const disabled = !isVoidField(field)
88 | ? field.pattern === 'disabled' || field.pattern === 'readPretty'
89 | : undefined
90 | const readOnly = !isVoidField(field)
91 | ? field.pattern === 'readOnly'
92 | : undefined
93 | return React.createElement(
94 | getComponent(field.componentType),
95 | {
96 | disabled,
97 | readOnly,
98 | ...toJS(field.componentProps),
99 | value,
100 | onChange,
101 | onFocus,
102 | onBlur,
103 | },
104 | content
105 | )
106 | }
107 |
108 | return renderDecorator(renderComponent())
109 | }
110 |
111 | ReactiveInternal.displayName = 'ReactiveField'
112 |
113 | export const ReactiveField = observer(ReactiveInternal, {
114 | forwardRef: true,
115 | })
116 |
```
--------------------------------------------------------------------------------
/packages/path/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Path } from './index'
2 | interface INode {
3 | type?: string
4 | after?: Node
5 | depth?: number
6 | }
7 |
8 | export type Node =
9 | | IdentifierNode
10 | | WildcardOperatorNode
11 | | GroupExpressionNode
12 | | RangeExpressionNode
13 | | DestructorExpressionNode
14 | | ObjectPatternNode
15 | | ArrayPatternNode
16 | | DotOperatorNode
17 | | ExpandOperatorNode
18 | | INode
19 |
20 | export type IdentifierNode = {
21 | type: 'Identifier'
22 | value: string
23 | arrayIndex?: boolean
24 | } & INode
25 |
26 | export type IgnoreExpressionNode = {
27 | type: 'IgnoreExpression'
28 | value: string
29 | } & INode
30 |
31 | export type DotOperatorNode = {
32 | type: 'DotOperator'
33 | } & INode
34 |
35 | export type WildcardOperatorNode = {
36 | type: 'WildcardOperator'
37 | filter?: GroupExpressionNode | RangeExpressionNode
38 | optional?: boolean
39 | } & INode
40 |
41 | export type ExpandOperatorNode = {
42 | type: 'ExpandOperator'
43 | } & INode
44 |
45 | export type GroupExpressionNode = {
46 | type: 'GroupExpression'
47 | value: Node[]
48 | isExclude?: boolean
49 | } & INode
50 |
51 | export type RangeExpressionNode = {
52 | type: 'RangeExpression'
53 | start?: IdentifierNode
54 | end?: IdentifierNode
55 | } & INode
56 |
57 | export type DestructorExpressionNode = {
58 | type: 'DestructorExpression'
59 | value?: ObjectPatternNode | ArrayPatternNode
60 | source?: string
61 | } & INode
62 |
63 | export type ObjectPatternNode = {
64 | type: 'ObjectPattern'
65 | properties: ObjectPatternPropertyNode[]
66 | } & INode
67 |
68 | export type ObjectPatternPropertyNode = {
69 | type: 'ObjectPatternProperty'
70 | key: IdentifierNode
71 | value?: ObjectPatternNode[] | ArrayPatternNode[] | IdentifierNode
72 | } & INode
73 |
74 | export type ArrayPatternNode = {
75 | type: 'ArrayPattern'
76 | elements: ObjectPatternNode[] | ArrayPatternNode[] | IdentifierNode[]
77 | } & INode
78 |
79 | export type DestructorRule = {
80 | key?: string | number
81 | path?: Array<number | string>
82 | }
83 |
84 | export type MatcherFunction = ((path: Segments) => boolean) & {
85 | path: Path
86 | }
87 |
88 | export type Pattern =
89 | | string
90 | | number
91 | | Path
92 | | Segments
93 | | MatcherFunction
94 | | RegExp
95 |
96 | export type DestructorRules = DestructorRule[]
97 |
98 | export type Segments = Array<string | number>
99 |
100 | export const isType =
101 | <T>(type: string) =>
102 | (obj: any): obj is T => {
103 | return obj && obj.type === type
104 | }
105 |
106 | export const isIdentifier = isType<IdentifierNode>('Identifier')
107 |
108 | export const isIgnoreExpression =
109 | isType<IgnoreExpressionNode>('IgnoreExpression')
110 |
111 | export const isDotOperator = isType<DotOperatorNode>('DotOperator')
112 |
113 | export const isWildcardOperator =
114 | isType<WildcardOperatorNode>('WildcardOperator')
115 |
116 | export const isExpandOperator = isType<ExpandOperatorNode>('ExpandOperator')
117 |
118 | export const isGroupExpression = isType<GroupExpressionNode>('GroupExpression')
119 |
120 | export const isRangeExpression = isType<RangeExpressionNode>('RangeExpression')
121 |
122 | export const isDestructorExpression = isType<DestructorExpressionNode>(
123 | 'DestructorExpression'
124 | )
125 |
126 | export const isObjectPattern = isType<ObjectPatternNode>('ObjectPattern')
127 |
128 | export const isObjectPatternProperty = isType<ObjectPatternPropertyNode>(
129 | 'ObjectPatternProperty'
130 | )
131 |
132 | export const isArrayPattern = isType<ArrayPatternNode>('ArrayPattern')
133 |
134 | export type KeyType = string | number | symbol
135 |
136 | export type IAccessors = {
137 | get?: (source: any, key: KeyType) => any
138 | set?: (source: any, key: KeyType, value: any) => any
139 | has?: (source: any, key: KeyType) => boolean
140 | delete?: (source: any, key: KeyType) => any
141 | }
142 |
143 | export type IRegistry = {
144 | accessors?: IAccessors
145 | }
146 |
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/form-item/size.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <Form :form="form">
3 | <SchemaField>
4 | <SchemaStringField
5 | name="size"
6 | title="Radio.Group"
7 | x-decorator="FormItem"
8 | x-component="Radio.Group"
9 | :enum="[
10 | { value: 'small', label: 'Small' },
11 | { value: 'default', label: 'Default' },
12 | { value: 'large', label: 'Large' },
13 | ]"
14 | />
15 | <SchemaVoidField name="sizeWrap" x-component="Div">
16 | <SchemaStringField
17 | name="input"
18 | title="Input"
19 | x-decorator="FormItem"
20 | x-component="Input"
21 | required
22 | />
23 | <SchemaStringField
24 | name="select1"
25 | title="Multiple Select"
26 | x-decorator="FormItem"
27 | x-component="Select"
28 | :enum="[
29 | {
30 | label: '选项1',
31 | value: 1,
32 | },
33 | {
34 | label: '选项2',
35 | value: 2,
36 | },
37 | ]"
38 | :x-component-props="{
39 | multiple: true,
40 | placeholder: '请选择',
41 | }"
42 | required
43 | />
44 | <SchemaStringField
45 | name="select2"
46 | title="Select"
47 | x-decorator="FormItem"
48 | x-component="Select"
49 | :enum="[
50 | {
51 | label: '选项1',
52 | value: 1,
53 | },
54 | {
55 | label: '选项2',
56 | value: 2,
57 | },
58 | ]"
59 | :x-component-props="{
60 | placeholder: '请选择',
61 | }"
62 | required
63 | />
64 | <SchemaStringField
65 | name="Cascader"
66 | title="Cascader"
67 | x-decorator="FormItem"
68 | x-component="Cascader"
69 | required
70 | />
71 | <SchemaStringField
72 | name="DatePicker"
73 | title="DatePicker"
74 | x-decorator="FormItem"
75 | x-component="DatePicker"
76 | required
77 | />
78 | <SchemaStringField
79 | name="InputNumber"
80 | title="InputNumber"
81 | x-decorator="FormItem"
82 | x-component="InputNumber"
83 | required
84 | />
85 | <SchemaBooleanField
86 | name="Switch"
87 | title="Switch"
88 | x-decorator="FormItem"
89 | x-component="Switch"
90 | required
91 | />
92 | </SchemaVoidField>
93 | </SchemaField>
94 | </Form>
95 | </template>
96 |
97 | <script>
98 | import { createForm, onFieldChange } from '@formily/core'
99 | import { createSchemaField } from '@formily/vue'
100 | import {
101 | Form,
102 | FormItem,
103 | Input,
104 | Select,
105 | Cascader,
106 | DatePicker,
107 | Switch,
108 | InputNumber,
109 | Radio,
110 | } from '@formily/element'
111 |
112 | const Div = {
113 | functional: true,
114 | render(h, context) {
115 | return h('div', context.data, context.children)
116 | },
117 | }
118 |
119 | const form = createForm({
120 | values: {
121 | size: 'default',
122 | },
123 | effects: () => {
124 | onFieldChange('size', ['value'], (field, form) => {
125 | form.setFieldState('sizeWrap.*', (state) => {
126 | if (state.decorator[1]) {
127 | state.decorator[1].size = field.value
128 | }
129 | })
130 | })
131 | },
132 | })
133 | const fields = createSchemaField({
134 | components: {
135 | FormItem,
136 | Input,
137 | Select,
138 | Cascader,
139 | DatePicker,
140 | Switch,
141 | InputNumber,
142 | Radio,
143 | Div,
144 | },
145 | })
146 |
147 | export default {
148 | components: { Form, ...fields },
149 | data() {
150 | return {
151 | form,
152 | }
153 | },
154 | methods: {
155 | onSubmit(value) {
156 | console.log(value)
157 | },
158 | },
159 | }
160 | </script>
161 |
```
--------------------------------------------------------------------------------
/packages/element/src/form-item/grid.scss:
--------------------------------------------------------------------------------
```scss
1 | .#{$form-item-prefix}-item-col-24 {
2 | -webkit-box-flex: 0;
3 | -ms-flex: 0 0 100%;
4 | flex: 0 0 100%;
5 | max-width: 100%;
6 | }
7 |
8 | .#{$form-item-prefix}-item-col-23 {
9 | -webkit-box-flex: 0;
10 | -ms-flex: 0 0 95.83333333%;
11 | flex: 0 0 95.83333333%;
12 | max-width: 95.83333333%;
13 | }
14 |
15 | .#{$form-item-prefix}-item-col-22 {
16 | -webkit-box-flex: 0;
17 | -ms-flex: 0 0 91.66666667%;
18 | flex: 0 0 91.66666667%;
19 | max-width: 91.66666667%;
20 | }
21 |
22 | .#{$form-item-prefix}-item-col-21 {
23 | -webkit-box-flex: 0;
24 | -ms-flex: 0 0 87.5%;
25 | flex: 0 0 87.5%;
26 | max-width: 87.5%;
27 | }
28 |
29 | .#{$form-item-prefix}-item-col-20 {
30 | -webkit-box-flex: 0;
31 | -ms-flex: 0 0 83.33333333%;
32 | flex: 0 0 83.33333333%;
33 | max-width: 83.33333333%;
34 | }
35 |
36 | .#{$form-item-prefix}-item-col-19 {
37 | -webkit-box-flex: 0;
38 | -ms-flex: 0 0 79.16666667%;
39 | flex: 0 0 79.16666667%;
40 | max-width: 79.16666667%;
41 | }
42 |
43 | .#{$form-item-prefix}-item-col-18 {
44 | -webkit-box-flex: 0;
45 | -ms-flex: 0 0 75%;
46 | flex: 0 0 75%;
47 | max-width: 75%;
48 | }
49 |
50 | .#{$form-item-prefix}-item-col-17 {
51 | -webkit-box-flex: 0;
52 | -ms-flex: 0 0 70.83333333%;
53 | flex: 0 0 70.83333333%;
54 | max-width: 70.83333333%;
55 | }
56 |
57 | .#{$form-item-prefix}-item-col-16 {
58 | -webkit-box-flex: 0;
59 | -ms-flex: 0 0 66.66666667%;
60 | flex: 0 0 66.66666667%;
61 | max-width: 66.66666667%;
62 | }
63 |
64 | .#{$form-item-prefix}-item-col-15 {
65 | -webkit-box-flex: 0;
66 | -ms-flex: 0 0 62.5%;
67 | flex: 0 0 62.5%;
68 | max-width: 62.5%;
69 | }
70 |
71 | .#{$form-item-prefix}-item-col-14 {
72 | -webkit-box-flex: 0;
73 | -ms-flex: 0 0 58.33333333%;
74 | flex: 0 0 58.33333333%;
75 | max-width: 58.33333333%;
76 | }
77 |
78 | .#{$form-item-prefix}-item-col-13 {
79 | -webkit-box-flex: 0;
80 | -ms-flex: 0 0 54.16666667%;
81 | flex: 0 0 54.16666667%;
82 | max-width: 54.16666667%;
83 | }
84 |
85 | .#{$form-item-prefix}-item-col-12 {
86 | -webkit-box-flex: 0;
87 | -ms-flex: 0 0 50%;
88 | flex: 0 0 50%;
89 | max-width: 50%;
90 | }
91 |
92 | .#{$form-item-prefix}-item-col-11 {
93 | -webkit-box-flex: 0;
94 | -ms-flex: 0 0 45.83333333%;
95 | flex: 0 0 45.83333333%;
96 | max-width: 45.83333333%;
97 | }
98 |
99 | .#{$form-item-prefix}-item-col-10 {
100 | -webkit-box-flex: 0;
101 | -ms-flex: 0 0 41.66666667%;
102 | flex: 0 0 41.66666667%;
103 | max-width: 41.66666667%;
104 | }
105 |
106 | .#{$form-item-prefix}-item-col-9 {
107 | -webkit-box-flex: 0;
108 | -ms-flex: 0 0 37.5%;
109 | flex: 0 0 37.5%;
110 | max-width: 37.5%;
111 | }
112 |
113 | .#{$form-item-prefix}-item-col-8 {
114 | -webkit-box-flex: 0;
115 | -ms-flex: 0 0 33.33333333%;
116 | flex: 0 0 33.33333333%;
117 | max-width: 33.33333333%;
118 | }
119 |
120 | .#{$form-item-prefix}-item-col-7 {
121 | -webkit-box-flex: 0;
122 | -ms-flex: 0 0 29.16666667%;
123 | flex: 0 0 29.16666667%;
124 | max-width: 29.16666667%;
125 | }
126 |
127 | .#{$form-item-prefix}-item-col-6 {
128 | -webkit-box-flex: 0;
129 | -ms-flex: 0 0 25%;
130 | flex: 0 0 25%;
131 | max-width: 25%;
132 | }
133 |
134 | .#{$form-item-prefix}-item-col-5 {
135 | -webkit-box-flex: 0;
136 | -ms-flex: 0 0 20.83333333%;
137 | flex: 0 0 20.83333333%;
138 | max-width: 20.83333333%;
139 | }
140 |
141 | .#{$form-item-prefix}-item-col-4 {
142 | -webkit-box-flex: 0;
143 | -ms-flex: 0 0 16.66666667%;
144 | flex: 0 0 16.66666667%;
145 | max-width: 16.66666667%;
146 | }
147 |
148 | .#{$form-item-prefix}-item-col-3 {
149 | -webkit-box-flex: 0;
150 | -ms-flex: 0 0 12.5%;
151 | flex: 0 0 12.5%;
152 | max-width: 12.5%;
153 | }
154 |
155 | .#{$form-item-prefix}-item-col-2 {
156 | -webkit-box-flex: 0;
157 | -ms-flex: 0 0 8.33333333%;
158 | flex: 0 0 8.33333333%;
159 | max-width: 8.33333333%;
160 | }
161 |
162 | .#{$form-item-prefix}-item-col-1 {
163 | -webkit-box-flex: 0;
164 | -ms-flex: 0 0 4.16666667%;
165 | flex: 0 0 4.16666667%;
166 | max-width: 4.16666667%;
167 | }
168 |
169 | .#{$form-item-prefix}-item-col-0 {
170 | display: none;
171 | }
172 |
```
--------------------------------------------------------------------------------
/packages/core/src/models/ArrayField.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { isArr, move } from '@formily/shared'
2 | import { action, reaction } from '@formily/reactive'
3 | import {
4 | spliceArrayState,
5 | exchangeArrayState,
6 | cleanupArrayChildren,
7 | } from '../shared/internals'
8 | import { Field } from './Field'
9 | import { Form } from './Form'
10 | import { JSXComponent, IFieldProps, FormPathPattern } from '../types'
11 |
12 | export class ArrayField<
13 | Decorator extends JSXComponent = any,
14 | Component extends JSXComponent = any
15 | > extends Field<Decorator, Component, any, any[]> {
16 | displayName = 'ArrayField'
17 |
18 | constructor(
19 | address: FormPathPattern,
20 | props: IFieldProps<Decorator, Component>,
21 | form: Form,
22 | designable: boolean
23 | ) {
24 | super(address, props, form, designable)
25 | this.makeAutoCleanable()
26 | }
27 |
28 | protected makeAutoCleanable() {
29 | this.disposers.push(
30 | reaction(
31 | () => this.value?.length,
32 | (newLength, oldLength) => {
33 | if (oldLength && !newLength) {
34 | cleanupArrayChildren(this, 0)
35 | } else if (newLength < oldLength) {
36 | cleanupArrayChildren(this, newLength)
37 | }
38 | }
39 | )
40 | )
41 | }
42 |
43 | push = (...items: any[]) => {
44 | return action(() => {
45 | if (!isArr(this.value)) {
46 | this.value = []
47 | }
48 | this.value.push(...items)
49 | return this.onInput(this.value)
50 | })
51 | }
52 |
53 | pop = () => {
54 | if (!isArr(this.value)) return
55 | return action(() => {
56 | const index = this.value.length - 1
57 | spliceArrayState(this, {
58 | startIndex: index,
59 | deleteCount: 1,
60 | })
61 | this.value.pop()
62 | return this.onInput(this.value)
63 | })
64 | }
65 |
66 | insert = (index: number, ...items: any[]) => {
67 | return action(() => {
68 | if (!isArr(this.value)) {
69 | this.value = []
70 | }
71 | if (items.length === 0) {
72 | return
73 | }
74 | spliceArrayState(this, {
75 | startIndex: index,
76 | insertCount: items.length,
77 | })
78 | this.value.splice(index, 0, ...items)
79 | return this.onInput(this.value)
80 | })
81 | }
82 |
83 | remove = (index: number) => {
84 | if (!isArr(this.value)) return
85 | return action(() => {
86 | spliceArrayState(this, {
87 | startIndex: index,
88 | deleteCount: 1,
89 | })
90 | this.value.splice(index, 1)
91 | return this.onInput(this.value)
92 | })
93 | }
94 |
95 | shift = () => {
96 | if (!isArr(this.value)) return
97 | return action(() => {
98 | this.value.shift()
99 | return this.onInput(this.value)
100 | })
101 | }
102 |
103 | unshift = (...items: any[]) => {
104 | return action(() => {
105 | if (!isArr(this.value)) {
106 | this.value = []
107 | }
108 | spliceArrayState(this, {
109 | startIndex: 0,
110 | insertCount: items.length,
111 | })
112 | this.value.unshift(...items)
113 | return this.onInput(this.value)
114 | })
115 | }
116 |
117 | move = (fromIndex: number, toIndex: number) => {
118 | if (!isArr(this.value)) return
119 | if (fromIndex === toIndex) return
120 | return action(() => {
121 | move(this.value, fromIndex, toIndex)
122 | exchangeArrayState(this, {
123 | fromIndex,
124 | toIndex,
125 | })
126 | return this.onInput(this.value)
127 | })
128 | }
129 |
130 | moveUp = (index: number) => {
131 | if (!isArr(this.value)) return
132 | return this.move(index, index - 1 < 0 ? this.value.length - 1 : index - 1)
133 | }
134 |
135 | moveDown = (index: number) => {
136 | if (!isArr(this.value)) return
137 | return this.move(index, index + 1 >= this.value.length ? 0 : index + 1)
138 | }
139 | }
140 |
```
--------------------------------------------------------------------------------
/packages/reactive/src/externals.ts:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | isValid,
3 | isFn,
4 | isMap,
5 | isWeakMap,
6 | isSet,
7 | isWeakSet,
8 | isPlainObj,
9 | isArr,
10 | } from './checkers'
11 | import {
12 | ProxyRaw,
13 | MakeObModelSymbol,
14 | DependencyCollected,
15 | ObModelSymbol,
16 | } from './environment'
17 | import { getDataNode } from './tree'
18 | import { Annotation } from './types'
19 |
20 | const RAW_TYPE = Symbol('RAW_TYPE')
21 | const OBSERVABLE_TYPE = Symbol('OBSERVABLE_TYPE')
22 | const hasOwnProperty = Object.prototype.hasOwnProperty
23 |
24 | export const isObservable = (target: any) => {
25 | return ProxyRaw.has(target) || !!target?.[ObModelSymbol]
26 | }
27 |
28 | export const isAnnotation = (target: any): target is Annotation => {
29 | return target && !!target[MakeObModelSymbol]
30 | }
31 |
32 | export const isSupportObservable = (target: any) => {
33 | if (!isValid(target)) return false
34 | if (isArr(target)) return true
35 | if (isPlainObj(target)) {
36 | if (target[RAW_TYPE]) {
37 | return false
38 | }
39 | if (target[OBSERVABLE_TYPE]) {
40 | return true
41 | }
42 | if ('$$typeof' in target && '_owner' in target) {
43 | return false
44 | }
45 | if (target['_isAMomentObject']) {
46 | return false
47 | }
48 | if (target['_isJSONSchemaObject']) {
49 | return false
50 | }
51 | if (isFn(target['toJS'])) {
52 | return false
53 | }
54 | if (isFn(target['toJSON'])) {
55 | return false
56 | }
57 | return true
58 | }
59 | if (isMap(target) || isWeakMap(target) || isSet(target) || isWeakSet(target))
60 | return true
61 | return false
62 | }
63 |
64 | export const markRaw = <T>(target: T): T => {
65 | if (!target) return
66 | if (isFn(target)) {
67 | target.prototype[RAW_TYPE] = true
68 | } else {
69 | target[RAW_TYPE] = true
70 | }
71 | return target
72 | }
73 |
74 | export const markObservable = <T>(target: T): T => {
75 | if (!target) return
76 | if (isFn(target)) {
77 | target.prototype[OBSERVABLE_TYPE] = true
78 | } else {
79 | target[OBSERVABLE_TYPE] = true
80 | }
81 | return target
82 | }
83 |
84 | export const raw = <T>(target: T): T => {
85 | if (target?.[ObModelSymbol]) return target[ObModelSymbol]
86 | return ProxyRaw.get(target as any) || target
87 | }
88 |
89 | export const toJS = <T>(values: T): T => {
90 | const visited = new WeakSet<any>()
91 | const _toJS: typeof toJS = (values: any) => {
92 | if (visited.has(values)) {
93 | return values
94 | }
95 | if (values && values[RAW_TYPE]) return values
96 | if (isArr(values)) {
97 | if (isObservable(values)) {
98 | visited.add(values)
99 | const res: any = []
100 | values.forEach((item: any) => {
101 | res.push(_toJS(item))
102 | })
103 | visited.delete(values)
104 | return res
105 | }
106 | } else if (isPlainObj(values)) {
107 | if (isObservable(values)) {
108 | visited.add(values)
109 | const res: any = {}
110 | for (const key in values) {
111 | if (hasOwnProperty.call(values, key)) {
112 | res[key] = _toJS(values[key])
113 | }
114 | }
115 | visited.delete(values)
116 | return res
117 | }
118 | }
119 | return values
120 | }
121 |
122 | return _toJS(values)
123 | }
124 |
125 | export const contains = (target: any, property: any) => {
126 | const targetRaw = raw(target)
127 | const propertyRaw = raw(property)
128 | if (targetRaw === propertyRaw) return true
129 | const targetNode = getDataNode(targetRaw)
130 | const propertyNode = getDataNode(propertyRaw)
131 | if (!targetNode) return false
132 | if (!propertyNode) return false
133 | return targetNode.contains(propertyNode)
134 | }
135 |
136 | export const hasCollected = (callback?: () => void) => {
137 | DependencyCollected.value = false
138 | callback?.()
139 | return DependencyCollected.value
140 | }
141 |
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/ArrayTabs.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
1 | # ArrayTabs
2 |
3 | > 自增选项卡,对于纵向空间要求较高的场景可以考虑使用该组件
4 | >
5 | > 注意:该组件只适用于 Schema 场景,交互上请避免跨 Tab 联动
6 |
7 | ## Markup Schema 案例
8 |
9 | ```tsx
10 | import React from 'react'
11 | import {
12 | FormItem,
13 | Input,
14 | ArrayTabs,
15 | FormButtonGroup,
16 | Submit,
17 | } from '@formily/antd'
18 | import { createForm } from '@formily/core'
19 | import { FormProvider, createSchemaField } from '@formily/react'
20 |
21 | const SchemaField = createSchemaField({
22 | components: {
23 | FormItem,
24 | Input,
25 | ArrayTabs,
26 | },
27 | })
28 |
29 | const form = createForm()
30 |
31 | export default () => {
32 | return (
33 | <FormProvider form={form}>
34 | <SchemaField>
35 | <SchemaField.Array
36 | name="string_array"
37 | x-decorator="FormItem"
38 | title="字符串数组"
39 | maxItems={3}
40 | x-component="ArrayTabs"
41 | >
42 | <SchemaField.String
43 | x-decorator="FormItem"
44 | required
45 | x-component="Input"
46 | />
47 | </SchemaField.Array>
48 | <SchemaField.Array
49 | name="array"
50 | x-decorator="FormItem"
51 | title="对象数组"
52 | maxItems={3}
53 | x-component="ArrayTabs"
54 | >
55 | <SchemaField.Object>
56 | <SchemaField.String
57 | x-decorator="FormItem"
58 | title="AAA"
59 | name="aaa"
60 | required
61 | x-component="Input"
62 | />
63 | <SchemaField.String
64 | x-decorator="FormItem"
65 | title="BBB"
66 | name="bbb"
67 | required
68 | x-component="Input"
69 | />
70 | </SchemaField.Object>
71 | </SchemaField.Array>
72 | </SchemaField>
73 | <FormButtonGroup>
74 | <Submit onSubmit={console.log}>提交</Submit>
75 | </FormButtonGroup>
76 | </FormProvider>
77 | )
78 | }
79 | ```
80 |
81 | ## JSON Schema 案例
82 |
83 | ```tsx
84 | import React from 'react'
85 | import {
86 | FormItem,
87 | Input,
88 | ArrayTabs,
89 | FormButtonGroup,
90 | Submit,
91 | } from '@formily/antd'
92 | import { createForm } from '@formily/core'
93 | import { FormProvider, createSchemaField } from '@formily/react'
94 |
95 | const SchemaField = createSchemaField({
96 | components: {
97 | FormItem,
98 | Input,
99 | ArrayTabs,
100 | },
101 | })
102 |
103 | const form = createForm()
104 |
105 | const schema = {
106 | type: 'object',
107 | properties: {
108 | string_array: {
109 | type: 'array',
110 | title: '字符串数组',
111 | 'x-decorator': 'FormItem',
112 | maxItems: 3,
113 | 'x-component': 'ArrayTabs',
114 | items: {
115 | type: 'string',
116 | 'x-decorator': 'FormItem',
117 | required: true,
118 | 'x-component': 'Input',
119 | },
120 | },
121 | array: {
122 | type: 'array',
123 | title: '对象数组',
124 | 'x-decorator': 'FormItem',
125 | maxItems: 3,
126 | 'x-component': 'ArrayTabs',
127 | items: {
128 | type: 'object',
129 | properties: {
130 | aaa: {
131 | type: 'string',
132 | 'x-decorator': 'FormItem',
133 | title: 'AAA',
134 | required: true,
135 | 'x-component': 'Input',
136 | },
137 | bbb: {
138 | type: 'string',
139 | 'x-decorator': 'FormItem',
140 | title: 'BBB',
141 | required: true,
142 | 'x-component': 'Input',
143 | },
144 | },
145 | },
146 | },
147 | },
148 | }
149 |
150 | export default () => {
151 | return (
152 | <FormProvider form={form}>
153 | <SchemaField schema={schema} />
154 | <FormButtonGroup>
155 | <Submit onSubmit={console.log}>提交</Submit>
156 | </FormButtonGroup>
157 | </FormProvider>
158 | )
159 | }
160 | ```
161 |
162 | ## API
163 |
164 | ### ArrayTabs
165 |
166 | 参考 https://ant.design/components/tabs-cn/
167 |
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-cards/json-schema.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <FormProvider :form="form">
3 | <SchemaField :schema="schema" />
4 | <Submit @submit="log">提交</Submit>
5 | </FormProvider>
6 | </template>
7 |
8 | <script>
9 | import { createForm } from '@formily/core'
10 | import { FormProvider, createSchemaField } from '@formily/vue'
11 | import {
12 | FormItem,
13 | FormButtonGroup,
14 | Submit,
15 | Input,
16 | ArrayCards,
17 | } from '@formily/element'
18 | import { Button } from 'element-ui'
19 |
20 | const SchemaField = createSchemaField({
21 | components: {
22 | FormItem,
23 | Input,
24 | ArrayCards,
25 | },
26 | })
27 |
28 | export default {
29 | components: {
30 | FormProvider,
31 | FormButtonGroup,
32 | Button,
33 | Submit,
34 | ...SchemaField,
35 | },
36 |
37 | data() {
38 | const form = createForm()
39 | const schema = {
40 | type: 'object',
41 | properties: {
42 | string_array: {
43 | type: 'array',
44 | 'x-component': 'ArrayCards',
45 | maxItems: 3,
46 | 'x-decorator': 'FormItem',
47 | 'x-component-props': {
48 | title: '字符串数组',
49 | },
50 | items: {
51 | type: 'void',
52 | properties: {
53 | index: {
54 | type: 'void',
55 | 'x-component': 'ArrayCards.Index',
56 | },
57 | input: {
58 | type: 'string',
59 | 'x-decorator': 'FormItem',
60 | title: 'Input',
61 | required: true,
62 | 'x-component': 'Input',
63 | },
64 | remove: {
65 | type: 'void',
66 | 'x-component': 'ArrayCards.Remove',
67 | },
68 | moveUp: {
69 | type: 'void',
70 | 'x-component': 'ArrayCards.MoveUp',
71 | },
72 | moveDown: {
73 | type: 'void',
74 | 'x-component': 'ArrayCards.MoveDown',
75 | },
76 | },
77 | },
78 | properties: {
79 | addition: {
80 | type: 'void',
81 | title: '添加条目',
82 | 'x-component': 'ArrayCards.Addition',
83 | },
84 | },
85 | },
86 | array: {
87 | type: 'array',
88 | 'x-component': 'ArrayCards',
89 | maxItems: 3,
90 | 'x-decorator': 'FormItem',
91 | 'x-component-props': {
92 | title: '对象数组',
93 | },
94 | items: {
95 | type: 'object',
96 | properties: {
97 | index: {
98 | type: 'void',
99 | 'x-component': 'ArrayCards.Index',
100 | },
101 | input: {
102 | type: 'string',
103 | 'x-decorator': 'FormItem',
104 | title: 'Input',
105 | required: true,
106 | 'x-component': 'Input',
107 | },
108 | remove: {
109 | type: 'void',
110 | 'x-component': 'ArrayCards.Remove',
111 | },
112 | moveUp: {
113 | type: 'void',
114 | 'x-component': 'ArrayCards.MoveUp',
115 | },
116 | moveDown: {
117 | type: 'void',
118 | 'x-component': 'ArrayCards.MoveDown',
119 | },
120 | },
121 | },
122 | properties: {
123 | addition: {
124 | type: 'void',
125 | title: '添加条目',
126 | 'x-component': 'ArrayCards.Addition',
127 | },
128 | },
129 | },
130 | },
131 | }
132 |
133 | return {
134 | form,
135 | schema,
136 | }
137 | },
138 | methods: {
139 | log(values) {
140 | console.log(values)
141 | },
142 | },
143 | }
144 | </script>
145 |
146 | <style lang="scss" scoped></style>
147 |
```
--------------------------------------------------------------------------------
/packages/json-schema/src/__tests__/patches.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Schema } from '../schema'
2 | import {
3 | registerTypeDefaultComponents,
4 | registerVoidComponents,
5 | } from '../polyfills'
6 |
7 | registerVoidComponents(['MyCard'])
8 | registerTypeDefaultComponents({
9 | string: 'Input',
10 | })
11 |
12 | Schema.enablePolyfills(['1.0'])
13 |
14 | test('v1 polyfill', () => {
15 | const schema = new Schema({
16 | type: 'string',
17 | editable: true,
18 | } as any)
19 | expect(schema['x-editable']).toEqual(true)
20 | const schema1 = new Schema({
21 | type: 'string',
22 | visible: true,
23 | } as any)
24 | expect(schema1['x-visible']).toEqual(true)
25 | const schema2 = new Schema({
26 | type: 'string',
27 | display: false,
28 | } as any)
29 | expect(schema2['x-display']).toEqual('hidden')
30 | expect(schema2['x-display']).toEqual('hidden')
31 | const schema3 = new Schema({
32 | type: 'string',
33 | 'x-linkages': [
34 | {
35 | type: 'value:visible',
36 | condition: '{{$value == 123}}',
37 | },
38 | ],
39 | } as any)
40 | expect(schema3['x-reactions']).toEqual([
41 | {
42 | when: '{{$self.value == 123}}',
43 | fulfill: {
44 | state: {
45 | visible: true,
46 | },
47 | },
48 | otherwise: {
49 | state: {
50 | visible: false,
51 | },
52 | },
53 | },
54 | ])
55 | const schema4 = new Schema({
56 | type: 'string',
57 | 'x-linkages': [
58 | {
59 | type: 'value:schema',
60 | target: 'xxx',
61 | condition: '{{$value == 123}}',
62 | schema: {
63 | title: 'xxx',
64 | },
65 | otherwise: {
66 | title: '123',
67 | },
68 | },
69 | ],
70 | } as any)
71 | expect(schema4['x-reactions']).toEqual([
72 | {
73 | when: '{{$self.value == 123}}',
74 | target: 'xxx',
75 | fulfill: {
76 | schema: {
77 | version: '1.0',
78 | title: 'xxx',
79 | 'x-decorator': 'FormItem',
80 | },
81 | },
82 | otherwise: {
83 | schema: {
84 | version: '1.0',
85 | title: '123',
86 | 'x-decorator': 'FormItem',
87 | },
88 | },
89 | },
90 | ])
91 | const schema5 = new Schema({
92 | type: 'string',
93 | 'x-linkages': [
94 | {
95 | type: 'value:state',
96 | target: 'xxx',
97 | condition: '{{$value == 123}}',
98 | state: {
99 | title: 'xxx',
100 | },
101 | otherwise: {
102 | title: '123',
103 | },
104 | },
105 | ],
106 | } as any)
107 | expect(schema5['x-reactions']).toEqual([
108 | {
109 | when: '{{$self.value == 123}}',
110 | target: 'xxx',
111 | fulfill: {
112 | state: {
113 | title: 'xxx',
114 | },
115 | },
116 | otherwise: {
117 | state: {
118 | title: '123',
119 | },
120 | },
121 | },
122 | ])
123 | const schema6 = new Schema({
124 | type: 'string',
125 | 'x-props': {
126 | labelCol: 3,
127 | wrapperCol: 4,
128 | },
129 | 'x-linkages': [
130 | {
131 | type: 'value:visible',
132 | condition: null,
133 | },
134 | ],
135 | } as any)
136 | expect(schema6['x-component']).toEqual('Input')
137 | expect(schema6['x-decorator']).toEqual('FormItem')
138 | expect(schema6['x-decorator-props']).toEqual({
139 | labelCol: 3,
140 | wrapperCol: 4,
141 | })
142 | const schema7 = new Schema({
143 | type: 'object',
144 | 'x-component': 'MyCard',
145 | 'x-linkages': {},
146 | } as any)
147 | expect(schema7.type === 'void').toBeTruthy()
148 | new Schema({
149 | type: 'object',
150 | 'x-component': 'MyCard',
151 | 'x-linkages': [null],
152 | } as any)
153 | new Schema({
154 | type: 'object',
155 | 'x-component': 'MyCard',
156 | 'x-linkages': [{}],
157 | } as any)
158 | const schema8 = new Schema({
159 | type: 'string',
160 | 'x-rules': ['phone'],
161 | } as any)
162 | expect(schema8['x-validator']).toEqual(['phone'])
163 | })
164 |
```
--------------------------------------------------------------------------------
/packages/antd/src/form-tab/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { Fragment, useMemo } from 'react'
2 | import { Tabs, Badge } from 'antd'
3 | import { model, markRaw } from '@formily/reactive'
4 | import { TabPaneProps, TabsProps } from 'antd/lib/tabs'
5 | import {
6 | useField,
7 | ReactFC,
8 | observer,
9 | useFieldSchema,
10 | RecursionField,
11 | } from '@formily/react'
12 | import { Schema, SchemaKey } from '@formily/json-schema'
13 | import cls from 'classnames'
14 | import { usePrefixCls } from '../__builtins__'
15 | export interface IFormTab {
16 | activeKey: string
17 | setActiveKey(key: string): void
18 | }
19 |
20 | export interface IFormTabProps extends TabsProps {
21 | formTab?: IFormTab
22 | }
23 |
24 | export interface IFormTabPaneProps extends TabPaneProps {
25 | key: string | number
26 | }
27 |
28 | interface IFeedbackBadgeProps {
29 | name: SchemaKey
30 | tab: React.ReactNode
31 | }
32 |
33 | type ComposedFormTab = React.FC<React.PropsWithChildren<IFormTabProps>> & {
34 | TabPane: React.FC<React.PropsWithChildren<IFormTabPaneProps>>
35 | createFormTab: (defaultActiveKey?: string) => IFormTab
36 | }
37 |
38 | const useTabs = () => {
39 | const tabsField = useField()
40 | const schema = useFieldSchema()
41 | const tabs: { name: SchemaKey; props: any; schema: Schema }[] = []
42 | schema.mapProperties((schema, name) => {
43 | const field = tabsField.query(tabsField.address.concat(name)).take()
44 | if (field?.display === 'none' || field?.display === 'hidden') return
45 | if (schema['x-component']?.indexOf('TabPane') > -1) {
46 | tabs.push({
47 | name,
48 | props: {
49 | key: schema?.['x-component-props']?.key || name,
50 | ...schema?.['x-component-props'],
51 | },
52 | schema,
53 | })
54 | }
55 | })
56 | return tabs
57 | }
58 |
59 | const FeedbackBadge: ReactFC<IFeedbackBadgeProps> = observer((props) => {
60 | const field = useField()
61 | const errors = field.form.queryFeedbacks({
62 | type: 'error',
63 | address: `${field.address.concat(props.name)}.*`,
64 | })
65 | if (errors.length) {
66 | return (
67 | <Badge size="small" className="errors-badge" count={errors.length}>
68 | {props.tab}
69 | </Badge>
70 | )
71 | }
72 | return <Fragment>{props.tab}</Fragment>
73 | })
74 |
75 | const createFormTab = (defaultActiveKey?: string) => {
76 | const formTab = model({
77 | activeKey: defaultActiveKey,
78 | setActiveKey(key: string) {
79 | formTab.activeKey = key
80 | },
81 | })
82 | return markRaw(formTab)
83 | }
84 |
85 | export const FormTab: ComposedFormTab = observer(
86 | ({ formTab, ...props }: IFormTabProps) => {
87 | const tabs = useTabs()
88 | const _formTab = useMemo(() => {
89 | return formTab ? formTab : createFormTab()
90 | }, [])
91 | const prefixCls = usePrefixCls('formily-tab', props)
92 | const activeKey = props.activeKey || _formTab?.activeKey
93 |
94 | return (
95 | <Tabs
96 | {...props}
97 | className={cls(prefixCls, props.className)}
98 | activeKey={activeKey}
99 | onChange={(key) => {
100 | props.onChange?.(key)
101 | _formTab?.setActiveKey?.(key)
102 | }}
103 | >
104 | {tabs.map(({ props, schema, name }, key) => (
105 | <Tabs.TabPane
106 | key={key}
107 | {...props}
108 | tab={<FeedbackBadge name={name} tab={props.tab} />}
109 | forceRender
110 | >
111 | <RecursionField schema={schema} name={name} />
112 | </Tabs.TabPane>
113 | ))}
114 | </Tabs>
115 | )
116 | }
117 | ) as unknown as ComposedFormTab
118 |
119 | const TabPane: React.FC<React.PropsWithChildren<IFormTabPaneProps>> = ({
120 | children,
121 | }) => {
122 | return <Fragment>{children}</Fragment>
123 | }
124 |
125 | FormTab.TabPane = TabPane
126 | FormTab.createFormTab = createFormTab
127 |
128 | export default FormTab
129 |
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-table/markup-schema.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <FormProvider :form="form">
3 | <SchemaField>
4 | <SchemaArrayField
5 | name="array"
6 | x-decorator="FormItem"
7 | x-component="ArrayTable"
8 | :x-component-props="{
9 | pagination: { pageSize: 10 },
10 | }"
11 | >
12 | <SchemaObjectField>
13 | <SchemaVoidField
14 | x-component="ArrayTable.Column"
15 | :x-component-props="{ width: 80, title: 'Index' }"
16 | ><SchemaVoidField
17 | x-decorator="FormItem"
18 | x-component="ArrayTable.Index"
19 | />
20 | </SchemaVoidField>
21 | <SchemaVoidField
22 | x-component="ArrayTable.Column"
23 | :x-component-props="{ prop: 'a1', title: 'A1', width: 200 }"
24 | >
25 | <SchemaStringField
26 | x-decorator="Editable"
27 | name="a1"
28 | :required="true"
29 | x-component="Input"
30 | />
31 | </SchemaVoidField>
32 | <SchemaVoidField
33 | x-component="ArrayTable.Column"
34 | :x-component-props="{ title: 'A2', width: 200 }"
35 | >
36 | <SchemaStringField
37 | x-decorator="FormItem"
38 | name="a2"
39 | :required="true"
40 | x-component="Input"
41 | />
42 | </SchemaVoidField>
43 | <SchemaVoidField
44 | x-component="ArrayTable.Column"
45 | :x-component-props="{ title: 'A3' }"
46 | >
47 | <SchemaStringField
48 | name="a3"
49 | :required="true"
50 | x-decorator="FormItem"
51 | x-component="Input"
52 | />
53 | </SchemaVoidField>
54 | <SchemaVoidField
55 | x-component="ArrayTable.Column"
56 | :x-component-props="{
57 | title: 'Operations',
58 | prop: 'operations',
59 | width: 200,
60 | fixed: 'right',
61 | }"
62 | >
63 | <SchemaVoidField x-component="FormItem">
64 | <SchemaVoidField x-component="ArrayTable.Remove" />
65 | <SchemaVoidField x-component="ArrayTable.MoveUp" />
66 | <SchemaVoidField x-component="ArrayTable.MoveDown" />
67 | </SchemaVoidField>
68 | </SchemaVoidField>
69 | </SchemaObjectField>
70 | <SchemaVoidField x-component="ArrayTable.Addition" title="添加条目" />
71 | </SchemaArrayField>
72 | </SchemaField>
73 | <Submit @submit="log">提交</Submit>
74 | <Button
75 | @click="
76 | () => {
77 | form.setInitialValues({
78 | array: range(100000),
79 | })
80 | }
81 | "
82 | >
83 | 加载10W条超大数据
84 | </Button>
85 | <Alert
86 | :style="{ marginTop: '10px' }"
87 | title="注意:开启formily插件的页面,因为后台有数据通信,会占用浏览器算力,最好在无痕模式(无formily插件)下测试"
88 | type="warning"
89 | />
90 | </FormProvider>
91 | </template>
92 |
93 | <script>
94 | import { createForm } from '@formily/core'
95 | import { FormProvider, createSchemaField } from '@formily/vue'
96 | import { Submit, FormItem, ArrayTable, Input, Editable } from '@formily/element'
97 | import { Button, Alert } from 'element-ui'
98 |
99 | const fields = createSchemaField({
100 | components: {
101 | FormItem,
102 | ArrayTable,
103 | Input,
104 | Editable,
105 | },
106 | })
107 |
108 | export default {
109 | components: { FormProvider, Submit, Button, Alert, ...fields },
110 | data() {
111 | const form = createForm()
112 | return {
113 | form,
114 | }
115 | },
116 | methods: {
117 | log(...v) {
118 | console.log(...v)
119 | },
120 | range(count) {
121 | return Array.from(new Array(count)).map((_, key) => ({
122 | aaa: key,
123 | }))
124 | },
125 | },
126 | }
127 | </script>
128 |
```
--------------------------------------------------------------------------------
/packages/next/src/form-layout/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { createContext, useContext } from 'react'
2 | import { useResponsiveFormLayout } from './useResponsiveFormLayout'
3 | import { usePrefixCls } from '../__builtins__'
4 | import cls from 'classnames'
5 |
6 | export interface IFormLayoutProps {
7 | prefix?: string
8 | className?: string
9 | style?: React.CSSProperties
10 | colon?: boolean
11 | labelAlign?: 'right' | 'left' | ('right' | 'left')[]
12 | wrapperAlign?: 'right' | 'left' | ('right' | 'left')[]
13 | labelWrap?: boolean
14 | labelWidth?: number
15 | wrapperWidth?: number
16 | wrapperWrap?: boolean
17 | labelCol?: number | number[]
18 | wrapperCol?: number | number[]
19 | fullness?: boolean
20 | size?: 'small' | 'default' | 'large'
21 | layout?:
22 | | 'vertical'
23 | | 'horizontal'
24 | | 'inline'
25 | | ('vertical' | 'horizontal' | 'inline')[]
26 | direction?: 'rtl' | 'ltr'
27 | inset?: boolean
28 | shallow?: boolean
29 | tooltipLayout?: 'icon' | 'text'
30 | tooltipIcon?: React.ReactNode
31 | feedbackLayout?: 'loose' | 'terse' | 'popover' | 'none'
32 | bordered?: boolean
33 | breakpoints?: number[]
34 | gridColumnGap?: number
35 | gridRowGap?: number
36 | spaceGap?: number
37 | }
38 |
39 | export interface IFormLayoutContext
40 | extends Omit<
41 | IFormLayoutProps,
42 | 'labelAlign' | 'wrapperAlign' | 'layout' | 'labelCol' | 'wrapperCol'
43 | > {
44 | labelAlign?: 'right' | 'left'
45 | wrapperAlign?: 'right' | 'left'
46 | layout?: 'vertical' | 'horizontal' | 'inline'
47 | labelCol?: number
48 | wrapperCol?: number
49 | }
50 |
51 | export const FormLayoutDeepContext = createContext<IFormLayoutContext>(null)
52 |
53 | export const FormLayoutShallowContext = createContext<IFormLayoutContext>(null)
54 |
55 | export const useFormDeepLayout = () => useContext(FormLayoutDeepContext)
56 |
57 | export const useFormShallowLayout = () => useContext(FormLayoutShallowContext)
58 |
59 | export const useFormLayout = () => ({
60 | ...useFormDeepLayout(),
61 | ...useFormShallowLayout(),
62 | })
63 |
64 | export const FormLayout: React.FC<React.PropsWithChildren<IFormLayoutProps>> & {
65 | useFormLayout: () => IFormLayoutContext
66 | useFormDeepLayout: () => IFormLayoutContext
67 | useFormShallowLayout: () => IFormLayoutContext
68 | } = ({ shallow = true, children, prefix, className, style, ...otherProps }) => {
69 | const { ref, props } = useResponsiveFormLayout(otherProps)
70 | const deepLayout = useFormDeepLayout()
71 | const formPrefixCls = usePrefixCls('form', { prefix })
72 | const layoutPrefixCls = usePrefixCls('formily-layout', { prefix })
73 | const layoutClassName = cls(
74 | layoutPrefixCls,
75 | {
76 | [`${formPrefixCls}-${props.layout}`]: true,
77 | [`${formPrefixCls}-rtl`]: props.direction === 'rtl',
78 | [`${formPrefixCls}-${props.size}`]: props.size,
79 | },
80 | className
81 | )
82 | const renderChildren = () => {
83 | const newDeepLayout = {
84 | ...deepLayout,
85 | }
86 | if (!shallow) {
87 | Object.assign(newDeepLayout, props)
88 | } else {
89 | if (props.size) {
90 | newDeepLayout.size = props.size
91 | }
92 | if (props.colon) {
93 | newDeepLayout.colon = props.colon
94 | }
95 | }
96 | return (
97 | <FormLayoutDeepContext.Provider value={newDeepLayout}>
98 | <FormLayoutShallowContext.Provider value={shallow ? props : undefined}>
99 | {children}
100 | </FormLayoutShallowContext.Provider>
101 | </FormLayoutDeepContext.Provider>
102 | )
103 | }
104 | return (
105 | <div ref={ref} className={layoutClassName} style={style}>
106 | {renderChildren()}
107 | </div>
108 | )
109 | }
110 |
111 | FormLayout.useFormDeepLayout = useFormDeepLayout
112 | FormLayout.useFormShallowLayout = useFormShallowLayout
113 | FormLayout.useFormLayout = useFormLayout
114 |
115 | export default FormLayout
116 |
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/Submit.md:
--------------------------------------------------------------------------------
```markdown
1 | # Submit
2 |
3 | > Submit button
4 |
5 | ## Ordinary submission
6 |
7 | ```tsx
8 | import React from 'react'
9 | import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd'
10 | import { createForm } from '@formily/core'
11 | import { FormProvider, createSchemaField } from '@formily/react'
12 |
13 | const SchemaField = createSchemaField({
14 | components: {
15 | Input,
16 | FormItem,
17 | },
18 | })
19 |
20 | const form = createForm()
21 |
22 | export default () => (
23 | <FormProvider form={form}>
24 | <SchemaField>
25 | <SchemaField.String
26 | name="input"
27 | title="input box"
28 | required
29 | x-decorator="FormItem"
30 | x-component="Input"
31 | />
32 | <SchemaField.String
33 | name="input2"
34 | title="input box"
35 | default="123"
36 | required
37 | x-decorator="FormItem"
38 | x-component="Input"
39 | />
40 | </SchemaField>
41 | <FormButtonGroup>
42 | <Submit onSubmit={console.log}>Submit</Submit>
43 | </FormButtonGroup>
44 | </FormProvider>
45 | )
46 | ```
47 |
48 | ## Prevent Duplicate Submission (Loading)
49 |
50 | ```tsx
51 | import React from 'react'
52 | import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd'
53 | import { createForm } from '@formily/core'
54 | import { FormProvider, createSchemaField } from '@formily/react'
55 |
56 | const SchemaField = createSchemaField({
57 | components: {
58 | Input,
59 | FormItem,
60 | },
61 | })
62 |
63 | const form = createForm()
64 |
65 | export default () => (
66 | <FormProvider form={form}>
67 | <SchemaField>
68 | <SchemaField.String
69 | name="input"
70 | title="input box"
71 | required
72 | x-decorator="FormItem"
73 | x-component="Input"
74 | />
75 | <SchemaField.String
76 | name="input2"
77 | title="input box"
78 | default="123"
79 | required
80 | x-decorator="FormItem"
81 | x-component="Input"
82 | />
83 | </SchemaField>
84 | <FormButtonGroup>
85 | <Submit
86 | onSubmit={(values) => {
87 | return new Promise((resolve) => {
88 | setTimeout(() => {
89 | console.log(values)
90 | resolve()
91 | }, 2000)
92 | })
93 | }}
94 | onSubmitFailed={console.log}
95 | >
96 | submit
97 | </Submit>
98 | </FormButtonGroup>
99 | </FormProvider>
100 | )
101 | ```
102 |
103 | ## API
104 |
105 | For button-related API properties, we can refer to https://ant.design/components/button-cn/, and the rest are the unique API properties of the Submit component
106 |
107 | | Property name | Type | Description | Default value |
108 | | --------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------------- |
109 | | onClick | `(event: MouseEvent) => void \| boolean` | Click event, if it returns false, it can block submission | - |
110 | | onSubmit | `(values: any) => Promise<any> \| any` | Submit event callback | - |
111 | | onSubmitSuccess | (payload: any) => void | Submit successful response event | - |
112 | | onSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Submit verification failure event callback | - |
113 |
```
--------------------------------------------------------------------------------
/packages/next/docs/components/Submit.md:
--------------------------------------------------------------------------------
```markdown
1 | # Submit
2 |
3 | > Submit button
4 |
5 | ## Ordinary submission
6 |
7 | ```tsx
8 | import React from 'react'
9 | import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next'
10 | import { createForm } from '@formily/core'
11 | import { FormProvider, createSchemaField } from '@formily/react'
12 |
13 | const SchemaField = createSchemaField({
14 | components: {
15 | Input,
16 | FormItem,
17 | },
18 | })
19 |
20 | const form = createForm()
21 |
22 | export default () => (
23 | <FormProvider form={form}>
24 | <SchemaField>
25 | <SchemaField.String
26 | name="input"
27 | title="input box"
28 | required
29 | x-decorator="FormItem"
30 | x-component="Input"
31 | />
32 | <SchemaField.String
33 | name="input2"
34 | title="input box"
35 | default="123"
36 | required
37 | x-decorator="FormItem"
38 | x-component="Input"
39 | />
40 | </SchemaField>
41 | <FormButtonGroup>
42 | <Submit onSubmit={console.log}>Submit</Submit>
43 | </FormButtonGroup>
44 | </FormProvider>
45 | )
46 | ```
47 |
48 | ## Prevent Duplicate Submission (Loading)
49 |
50 | ```tsx
51 | import React from 'react'
52 | import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next'
53 | import { createForm } from '@formily/core'
54 | import { FormProvider, createSchemaField } from '@formily/react'
55 |
56 | const SchemaField = createSchemaField({
57 | components: {
58 | Input,
59 | FormItem,
60 | },
61 | })
62 |
63 | const form = createForm()
64 |
65 | export default () => (
66 | <FormProvider form={form}>
67 | <SchemaField>
68 | <SchemaField.String
69 | name="input"
70 | title="input box"
71 | required
72 | x-decorator="FormItem"
73 | x-component="Input"
74 | />
75 | <SchemaField.String
76 | name="input2"
77 | title="input box"
78 | default="123"
79 | required
80 | x-decorator="FormItem"
81 | x-component="Input"
82 | />
83 | </SchemaField>
84 | <FormButtonGroup>
85 | <Submit
86 | onSubmit={(values) => {
87 | return new Promise((resolve) => {
88 | setTimeout(() => {
89 | console.log(values)
90 | resolve()
91 | }, 2000)
92 | })
93 | }}
94 | onSubmitFailed={console.log}
95 | >
96 | submit
97 | </Submit>
98 | </FormButtonGroup>
99 | </FormProvider>
100 | )
101 | ```
102 |
103 | ## API
104 |
105 | For button-related API properties, we can refer to https://fusion.design/pc/component/basic/button, and the rest are the unique API properties of the Submit component
106 |
107 | | Property name | Type | Description | Default value |
108 | | --------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------------- |
109 | | onClick | `(event: MouseEvent) => void \| boolean` | Click event, if it returns false, it can block submission | - |
110 | | onSubmit | `(values: any) => Promise<any> \| any` | Submit event callback | - |
111 | | onSubmitSuccess | (payload: any) => void | Submit successful response event | - |
112 | | onSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Submit verification failure event callback | - |
113 |
```
--------------------------------------------------------------------------------
/packages/react/docs/api/components/ArrayField.zh-CN.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | order: 1
3 | ---
4 |
5 | # ArrayField
6 |
7 | ## 描述
8 |
9 | 作为@formily/core 的 [createArrayField](https://core.formilyjs.org/zh-CN/api/models/form#createarrayfield) React 实现,它是专门用于将 ViewModel 与输入控件做绑定的桥接组件,ArrayField 组件属性参考[IFieldFactoryProps](https://core.formilyjs.org/zh-CN/api/models/form#ifieldfactoryprops)
10 |
11 | <Alert>
12 | 我们在使用 ArrayField 组件的时候,一定要记得传name属性。同时要使用render props形式来组织子组件
13 | </Alert>
14 |
15 | ## 签名
16 |
17 | ```ts
18 | type ArrayField = React.FC<React.PropsWithChildren<IFieldFactoryProps>>
19 | ```
20 |
21 | ## 自定义组件用例
22 |
23 | ```tsx
24 | import React from 'react'
25 | import { createForm, ArrayField as ArrayFieldType } from '@formily/core'
26 | import {
27 | FormProvider,
28 | Field,
29 | ArrayField,
30 | useField,
31 | observer,
32 | } from '@formily/react'
33 | import { Input, Button, Space } from 'antd'
34 |
35 | const form = createForm()
36 |
37 | const ArrayComponent = observer(() => {
38 | const field = useField<ArrayFieldType>()
39 | return (
40 | <>
41 | <div>
42 | {field.value?.map((item, index) => (
43 | <div key={index} style={{ display: 'flex-block', marginBottom: 10 }}>
44 | <Space>
45 | <Field name={index} component={[Input]} />
46 | <Button
47 | onClick={() => {
48 | field.remove(index)
49 | }}
50 | >
51 | Remove
52 | </Button>
53 | <Button
54 | onClick={() => {
55 | field.moveUp(index)
56 | }}
57 | >
58 | Move Up
59 | </Button>
60 | <Button
61 | onClick={() => {
62 | field.moveDown(index)
63 | }}
64 | >
65 | Move Down
66 | </Button>
67 | </Space>
68 | </div>
69 | ))}
70 | </div>
71 | <Button
72 | onClick={() => {
73 | field.push('')
74 | }}
75 | >
76 | Add
77 | </Button>
78 | </>
79 | )
80 | })
81 |
82 | export default () => (
83 | <FormProvider form={form}>
84 | <ArrayField name="array" component={[ArrayComponent]} />
85 | </FormProvider>
86 | )
87 | ```
88 |
89 | ## RenderProps 用例
90 |
91 | ```tsx
92 | import React from 'react'
93 | import { createForm } from '@formily/core'
94 | import { FormProvider, Field, ArrayField } from '@formily/react'
95 | import { Input, Button, Space } from 'antd'
96 |
97 | const form = createForm()
98 |
99 | export default () => (
100 | <FormProvider form={form}>
101 | <ArrayField name="array">
102 | {(field) => {
103 | return (
104 | <>
105 | <div>
106 | {field.value?.map((item, index) => (
107 | <div
108 | key={index}
109 | style={{ display: 'flex-block', marginBottom: 10 }}
110 | >
111 | <Space>
112 | <Field name={index} component={[Input]} />
113 | <Button
114 | onClick={() => {
115 | field.remove(index)
116 | }}
117 | >
118 | Remove
119 | </Button>
120 | <Button
121 | onClick={() => {
122 | field.moveUp(index)
123 | }}
124 | >
125 | Move Up
126 | </Button>
127 | <Button
128 | onClick={() => {
129 | field.moveDown(index)
130 | }}
131 | >
132 | Move Down
133 | </Button>
134 | </Space>
135 | </div>
136 | ))}
137 | </div>
138 | <Button onClick={() => field.push('')}>Add</Button>
139 | </>
140 | )
141 | }}
142 | </ArrayField>
143 | </FormProvider>
144 | )
145 | ```
146 |
```
--------------------------------------------------------------------------------
/packages/antd/src/form-layout/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { createContext, useContext } from 'react'
2 | import { useResponsiveFormLayout } from './useResponsiveFormLayout'
3 | import { usePrefixCls } from '../__builtins__'
4 | import cls from 'classnames'
5 |
6 | export interface IFormLayoutProps {
7 | prefixCls?: string
8 | className?: string
9 | style?: React.CSSProperties
10 | colon?: boolean
11 | requiredMark?: boolean | 'optional'
12 | labelAlign?: 'right' | 'left' | ('right' | 'left')[]
13 | wrapperAlign?: 'right' | 'left' | ('right' | 'left')[]
14 | labelWrap?: boolean
15 | labelWidth?: number
16 | wrapperWidth?: number
17 | wrapperWrap?: boolean
18 | labelCol?: number | number[]
19 | wrapperCol?: number | number[]
20 | fullness?: boolean
21 | size?: 'small' | 'default' | 'large'
22 | layout?:
23 | | 'vertical'
24 | | 'horizontal'
25 | | 'inline'
26 | | ('vertical' | 'horizontal' | 'inline')[]
27 | direction?: 'rtl' | 'ltr'
28 | inset?: boolean
29 | shallow?: boolean
30 | tooltipLayout?: 'icon' | 'text'
31 | tooltipIcon?: React.ReactNode
32 | feedbackLayout?: 'loose' | 'terse' | 'popover' | 'none'
33 | bordered?: boolean
34 | breakpoints?: number[]
35 | spaceGap?: number
36 | gridColumnGap?: number
37 | gridRowGap?: number
38 | }
39 |
40 | export interface IFormLayoutContext
41 | extends Omit<
42 | IFormLayoutProps,
43 | 'labelAlign' | 'wrapperAlign' | 'layout' | 'labelCol' | 'wrapperCol'
44 | > {
45 | labelAlign?: 'right' | 'left'
46 | wrapperAlign?: 'right' | 'left'
47 | layout?: 'vertical' | 'horizontal' | 'inline'
48 | labelCol?: number
49 | wrapperCol?: number
50 | }
51 |
52 | export const FormLayoutDeepContext = createContext<IFormLayoutContext>(null)
53 |
54 | export const FormLayoutShallowContext = createContext<IFormLayoutContext>(null)
55 |
56 | export const useFormDeepLayout = () => useContext(FormLayoutDeepContext)
57 |
58 | export const useFormShallowLayout = () => useContext(FormLayoutShallowContext)
59 |
60 | export const useFormLayout = () => ({
61 | ...useFormDeepLayout(),
62 | ...useFormShallowLayout(),
63 | })
64 |
65 | export const FormLayout: React.FC<React.PropsWithChildren<IFormLayoutProps>> & {
66 | useFormLayout: () => IFormLayoutContext
67 | useFormDeepLayout: () => IFormLayoutContext
68 | useFormShallowLayout: () => IFormLayoutContext
69 | } = ({
70 | shallow = true,
71 | children,
72 | prefixCls,
73 | className,
74 | style,
75 | ...otherProps
76 | }) => {
77 | const { ref, props } = useResponsiveFormLayout(otherProps)
78 | const deepLayout = useFormDeepLayout()
79 | const formPrefixCls = usePrefixCls('form', { prefixCls })
80 | const layoutPrefixCls = usePrefixCls('formily-layout', { prefixCls })
81 | const layoutClassName = cls(
82 | layoutPrefixCls,
83 | {
84 | [`${formPrefixCls}-${props.layout}`]: true,
85 | [`${formPrefixCls}-rtl`]: props.direction === 'rtl',
86 | [`${formPrefixCls}-${props.size}`]: props.size,
87 | },
88 | className
89 | )
90 | const renderChildren = () => {
91 | const newDeepLayout = {
92 | ...deepLayout,
93 | }
94 | if (!shallow) {
95 | Object.assign(newDeepLayout, props)
96 | } else {
97 | if (props.size) {
98 | newDeepLayout.size = props.size
99 | }
100 | if (props.colon) {
101 | newDeepLayout.colon = props.colon
102 | }
103 | }
104 | return (
105 | <FormLayoutDeepContext.Provider value={newDeepLayout}>
106 | <FormLayoutShallowContext.Provider value={shallow ? props : undefined}>
107 | {children}
108 | </FormLayoutShallowContext.Provider>
109 | </FormLayoutDeepContext.Provider>
110 | )
111 | }
112 | return (
113 | <div ref={ref} className={layoutClassName} style={style}>
114 | {renderChildren()}
115 | </div>
116 | )
117 | }
118 |
119 | FormLayout.useFormDeepLayout = useFormDeepLayout
120 | FormLayout.useFormShallowLayout = useFormShallowLayout
121 | FormLayout.useFormLayout = useFormLayout
122 |
123 | export default FormLayout
124 |
```