This is page 13 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/next/src/form-tab/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import React, { Fragment, useMemo } from 'react'
2 | import { Tab as Tabs, Badge } from '@alifd/next'
3 | import { model, markRaw } from '@formily/reactive'
4 | import { isValid } from '@formily/shared'
5 | import {
6 | ItemProps as TabPaneProps,
7 | TabProps as TabsProps,
8 | } from '@alifd/next/lib/tab'
9 | import {
10 | useField,
11 | observer,
12 | ReactFC,
13 | useFieldSchema,
14 | RecursionField,
15 | } from '@formily/react'
16 | import { Schema, SchemaKey } from '@formily/json-schema'
17 | import cls from 'classnames'
18 | import { usePrefixCls } from '../__builtins__'
19 | export interface IFormTab {
20 | activeKey: SchemaKey
21 | setActiveKey(key: SchemaKey): void
22 | }
23 |
24 | export interface IFormTabProps extends TabsProps {
25 | formTab?: IFormTab
26 | }
27 |
28 | export interface IFormTabPaneProps extends TabPaneProps {
29 | key: SchemaKey
30 | }
31 |
32 | interface IFeedbackBadgeProps {
33 | name: SchemaKey
34 | tab: React.ReactNode
35 | }
36 |
37 | type ComposedFormTab = React.FC<React.PropsWithChildren<IFormTabProps>> & {
38 | TabPane: React.FC<React.PropsWithChildren<IFormTabPaneProps>>
39 | createFormTab: (defaultActiveKey?: React.ReactText) => IFormTab
40 | }
41 |
42 | const useTabs = () => {
43 | const tabsField = useField()
44 | const schema = useFieldSchema()
45 | const tabs: { name: SchemaKey; props: any; schema: Schema }[] = []
46 | schema.mapProperties((schema, name) => {
47 | const field = tabsField.query(tabsField.address.concat(name)).take()
48 | if (field?.display === 'none' || field?.display === 'hidden') return
49 | if (schema['x-component']?.indexOf('TabPane') > -1) {
50 | tabs.push({
51 | name,
52 | props: {
53 | key: schema?.['x-component-props']?.key || name,
54 | ...schema?.['x-component-props'],
55 | },
56 | schema,
57 | })
58 | }
59 | })
60 | return tabs
61 | }
62 |
63 | const createFormTab = (defaultActiveKey?: string) => {
64 | const formTab = model({
65 | activeKey: defaultActiveKey,
66 | setActiveKey(key: string) {
67 | formTab.activeKey = key
68 | },
69 | })
70 | return markRaw(formTab)
71 | }
72 |
73 | const FeedbackBadge: ReactFC<IFeedbackBadgeProps> = observer((props) => {
74 | const field = useField()
75 | const errors = field.form.queryFeedbacks({
76 | type: 'error',
77 | address: `${field.address.concat(props.name)}.*`,
78 | })
79 | if (errors.length) {
80 | return (
81 | <Badge className="errors-badge" count={errors.length}>
82 | {props.tab}
83 | </Badge>
84 | )
85 | }
86 | return <Fragment>{props.tab}</Fragment>
87 | })
88 |
89 | export const FormTab: ComposedFormTab = observer(
90 | ({ formTab, ...props }: IFormTabProps) => {
91 | const tabs = useTabs()
92 | const _formTab = useMemo(() => {
93 | return formTab ? formTab : createFormTab()
94 | }, [])
95 | const prefixCls = usePrefixCls('formily-tab', props)
96 | const activeKey = props.activeKey || _formTab?.activeKey
97 |
98 | return (
99 | <Tabs
100 | {...props}
101 | {...(isValid(activeKey) && { activeKey })}
102 | className={cls(prefixCls, props.className)}
103 | onChange={(key) => {
104 | props.onChange?.(key)
105 | _formTab?.setActiveKey?.(key)
106 | }}
107 | lazyLoad={false}
108 | >
109 | {tabs.map(({ props, schema, name }, key) => (
110 | <Tabs.Item
111 | key={key}
112 | {...props}
113 | tab={<FeedbackBadge name={name} tab={props.tab} />}
114 | >
115 | <RecursionField schema={schema} name={name} />
116 | </Tabs.Item>
117 | ))}
118 | </Tabs>
119 | )
120 | }
121 | ) as unknown as ComposedFormTab
122 |
123 | const TabPane: React.FC<React.PropsWithChildren<IFormTabPaneProps>> = ({
124 | children,
125 | }) => {
126 | return <Fragment>{children}</Fragment>
127 | }
128 |
129 | FormTab.TabPane = TabPane
130 | FormTab.createFormTab = createFormTab
131 |
132 | export default FormTab
133 |
```
--------------------------------------------------------------------------------
/packages/core/docs/api/entry/FormHooksAPI.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | order: 3
3 | ---
4 |
5 | # Form Hooks API
6 |
7 | ## createEffectHook
8 |
9 | #### Description
10 |
11 | Create a custom hook listener
12 |
13 | #### Signature
14 |
15 | ```ts
16 | interface createEffectHook {
17 | (
18 | type: string,
19 | callback?: (
20 | payload: any,
21 | form: Form,
22 | ...ctx: any[] //user-injected context
23 | ) => (...args: any[]) => void //High-level callbacks are used to process the encapsulation of the listener and help users achieve parameter customization capabilities
24 | )
25 | }
26 | ```
27 |
28 | #### Example
29 |
30 | ```tsx
31 | import React, { useMemo, useState } from 'react'
32 | import { createForm, createEffectHook } from '@formily/core'
33 | import { ActionResponse } from './ActionResponse'
34 |
35 | const onCustomEvent = createEffectHook(
36 | 'custom-event',
37 | (payload, form) => (listener) => {
38 | listener(payload, form)
39 | }
40 | )
41 |
42 | export default () => {
43 | const [response, setResponse] = useState('')
44 | const form = useMemo(
45 | () =>
46 | createForm({
47 | effects() {
48 | onCustomEvent((payload, form) => {
49 | setResponse(payload + 'Form:' + form.id)
50 | })
51 | },
52 | }),
53 | []
54 | )
55 | return (
56 | <ActionResponse response={response}>
57 | <button
58 | onClick={() => {
59 | form.notify('custom-event', 'This is Custom Event')
60 | }}
61 | >
62 | Notify
63 | </button>
64 | </ActionResponse>
65 | )
66 | }
67 | ```
68 |
69 | ## createEffectContext
70 |
71 | #### Description
72 |
73 | In the effects function, if we abstract a lot of fine-grained hooks, we need to pass it layer by layer if we want to read the top-level context data in hooks, which is obviously very inefficient, so formily provides createEffectContext to help users quickly obtain context data
74 |
75 | #### Signature
76 |
77 | ```ts
78 | interface createEffectContext<T> {
79 | (defaultValue: T): {
80 | provide(value: T): void
81 | consume(): T
82 | }
83 | }
84 | ```
85 |
86 | #### Example
87 |
88 | ```tsx
89 | import React, { useMemo, useState } from 'react'
90 | import { createForm, onFormSubmit, createEffectContext } from '@formily/core'
91 | import { ActionResponse } from './ActionResponse'
92 |
93 | const { provide, consume } = createEffectContext()
94 |
95 | const useMyHook = () => {
96 | const setResponse = consume()
97 | onFormSubmit(() => {
98 | setResponse('Context communication succeeded')
99 | })
100 | }
101 |
102 | export default () => {
103 | const [response, setResponse] = useState('')
104 | const form = useMemo(
105 | () =>
106 | createForm({
107 | effects() {
108 | provide(setResponse)
109 | useMyHook()
110 | },
111 | }),
112 | []
113 | )
114 | return (
115 | <ActionResponse response={response}>
116 | <button
117 | onClick={() => {
118 | form.submit()
119 | }}
120 | >
121 | submit
122 | </button>
123 | </ActionResponse>
124 | )
125 | }
126 | ```
127 |
128 | ## useEffectForm
129 |
130 | #### Description
131 |
132 | useEffectForm is actually a convenient usage of EffectContext, because most scene users will read Form instances, so there is no need to manually define an EffectFormContext
133 |
134 | #### Signature
135 |
136 | ```ts
137 | interface useEffectForm {
138 | (): Form
139 | }
140 | ```
141 |
142 | #### Example
143 |
144 | ```tsx
145 | import React, { useMemo, useState } from 'react'
146 | import { createForm, useEffectForm, createEffectContext } from '@formily/core'
147 | import { ActionResponse } from './ActionResponse'
148 |
149 | const { provide, consume } = createEffectContext()
150 |
151 | const useMyHook = () => {
152 | const form = useEffectForm()
153 | const setResponse = consume()
154 | setResponse('Communication successful:' + form.id)
155 | }
156 |
157 | export default () => {
158 | const [response, setResponse] = useState('')
159 | useMemo(
160 | () =>
161 | createForm({
162 | effects() {
163 | provide(setResponse)
164 | useMyHook()
165 | },
166 | }),
167 | []
168 | )
169 | return <ActionResponse response={response} />
170 | }
171 | ```
172 |
```
--------------------------------------------------------------------------------
/packages/antd/src/form-item/grid.less:
--------------------------------------------------------------------------------
```
1 | .@{form-item-cls} {
2 | .@{form-item-cls}-item-col-24 {
3 | -webkit-box-flex: 0;
4 | -ms-flex: 0 0 100%;
5 | flex: 0 0 100%;
6 | max-width: 100%;
7 | }
8 |
9 | .@{form-item-cls}-item-col-23 {
10 | -webkit-box-flex: 0;
11 | -ms-flex: 0 0 95.83333333%;
12 | flex: 0 0 95.83333333%;
13 | max-width: 95.83333333%;
14 | }
15 |
16 | .@{form-item-cls}-item-col-22 {
17 | -webkit-box-flex: 0;
18 | -ms-flex: 0 0 91.66666667%;
19 | flex: 0 0 91.66666667%;
20 | max-width: 91.66666667%;
21 | }
22 |
23 | .@{form-item-cls}-item-col-21 {
24 | -webkit-box-flex: 0;
25 | -ms-flex: 0 0 87.5%;
26 | flex: 0 0 87.5%;
27 | max-width: 87.5%;
28 | }
29 |
30 | .@{form-item-cls}-item-col-20 {
31 | -webkit-box-flex: 0;
32 | -ms-flex: 0 0 83.33333333%;
33 | flex: 0 0 83.33333333%;
34 | max-width: 83.33333333%;
35 | }
36 |
37 | .@{form-item-cls}-item-col-19 {
38 | -webkit-box-flex: 0;
39 | -ms-flex: 0 0 79.16666667%;
40 | flex: 0 0 79.16666667%;
41 | max-width: 79.16666667%;
42 | }
43 |
44 | .@{form-item-cls}-item-col-18 {
45 | -webkit-box-flex: 0;
46 | -ms-flex: 0 0 75%;
47 | flex: 0 0 75%;
48 | max-width: 75%;
49 | }
50 |
51 | .@{form-item-cls}-item-col-17 {
52 | -webkit-box-flex: 0;
53 | -ms-flex: 0 0 70.83333333%;
54 | flex: 0 0 70.83333333%;
55 | max-width: 70.83333333%;
56 | }
57 |
58 | .@{form-item-cls}-item-col-16 {
59 | -webkit-box-flex: 0;
60 | -ms-flex: 0 0 66.66666667%;
61 | flex: 0 0 66.66666667%;
62 | max-width: 66.66666667%;
63 | }
64 |
65 | .@{form-item-cls}-item-col-15 {
66 | -webkit-box-flex: 0;
67 | -ms-flex: 0 0 62.5%;
68 | flex: 0 0 62.5%;
69 | max-width: 62.5%;
70 | }
71 |
72 | .@{form-item-cls}-item-col-14 {
73 | -webkit-box-flex: 0;
74 | -ms-flex: 0 0 58.33333333%;
75 | flex: 0 0 58.33333333%;
76 | max-width: 58.33333333%;
77 | }
78 |
79 | .@{form-item-cls}-item-col-13 {
80 | -webkit-box-flex: 0;
81 | -ms-flex: 0 0 54.16666667%;
82 | flex: 0 0 54.16666667%;
83 | max-width: 54.16666667%;
84 | }
85 |
86 | .@{form-item-cls}-item-col-12 {
87 | -webkit-box-flex: 0;
88 | -ms-flex: 0 0 50%;
89 | flex: 0 0 50%;
90 | max-width: 50%;
91 | }
92 |
93 | .@{form-item-cls}-item-col-11 {
94 | -webkit-box-flex: 0;
95 | -ms-flex: 0 0 45.83333333%;
96 | flex: 0 0 45.83333333%;
97 | max-width: 45.83333333%;
98 | }
99 |
100 | .@{form-item-cls}-item-col-10 {
101 | -webkit-box-flex: 0;
102 | -ms-flex: 0 0 41.66666667%;
103 | flex: 0 0 41.66666667%;
104 | max-width: 41.66666667%;
105 | }
106 |
107 | .@{form-item-cls}-item-col-9 {
108 | -webkit-box-flex: 0;
109 | -ms-flex: 0 0 37.5%;
110 | flex: 0 0 37.5%;
111 | max-width: 37.5%;
112 | }
113 |
114 | .@{form-item-cls}-item-col-8 {
115 | -webkit-box-flex: 0;
116 | -ms-flex: 0 0 33.33333333%;
117 | flex: 0 0 33.33333333%;
118 | max-width: 33.33333333%;
119 | }
120 |
121 | .@{form-item-cls}-item-col-7 {
122 | -webkit-box-flex: 0;
123 | -ms-flex: 0 0 29.16666667%;
124 | flex: 0 0 29.16666667%;
125 | max-width: 29.16666667%;
126 | }
127 |
128 | .@{form-item-cls}-item-col-6 {
129 | -webkit-box-flex: 0;
130 | -ms-flex: 0 0 25%;
131 | flex: 0 0 25%;
132 | max-width: 25%;
133 | }
134 |
135 | .@{form-item-cls}-item-col-5 {
136 | -webkit-box-flex: 0;
137 | -ms-flex: 0 0 20.83333333%;
138 | flex: 0 0 20.83333333%;
139 | max-width: 20.83333333%;
140 | }
141 |
142 | .@{form-item-cls}-item-col-4 {
143 | -webkit-box-flex: 0;
144 | -ms-flex: 0 0 16.66666667%;
145 | flex: 0 0 16.66666667%;
146 | max-width: 16.66666667%;
147 | }
148 |
149 | .@{form-item-cls}-item-col-3 {
150 | -webkit-box-flex: 0;
151 | -ms-flex: 0 0 12.5%;
152 | flex: 0 0 12.5%;
153 | max-width: 12.5%;
154 | }
155 |
156 | .@{form-item-cls}-item-col-2 {
157 | -webkit-box-flex: 0;
158 | -ms-flex: 0 0 8.33333333%;
159 | flex: 0 0 8.33333333%;
160 | max-width: 8.33333333%;
161 | }
162 |
163 | .@{form-item-cls}-item-col-1 {
164 | -webkit-box-flex: 0;
165 | -ms-flex: 0 0 4.16666667%;
166 | flex: 0 0 4.16666667%;
167 | max-width: 4.16666667%;
168 | }
169 |
170 | .@{form-item-cls}-item-col-0 {
171 | display: none;
172 | }
173 | }
174 |
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/ArrayTabs.md:
--------------------------------------------------------------------------------
```markdown
1 | # ArrayTabs
2 |
3 | > Self-increasing tab, you can consider using this component for scenarios with high vertical space requirements
4 | >
5 | > Note: This component is only applicable to Schema scenarios, please avoid cross-tab linkage in interaction
6 |
7 | ## Markup Schema example
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="string array"
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="Object array"
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</Submit>
75 | </FormButtonGroup>
76 | </FormProvider>
77 | )
78 | }
79 | ```
80 |
81 | ## JSON Schema case
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: 'String array',
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: 'Object array',
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</Submit>
156 | </FormButtonGroup>
157 | </FormProvider>
158 | )
159 | }
160 | ```
161 |
162 | ## API
163 |
164 | ### ArrayTabs
165 |
166 | Reference https://ant.design/components/tabs-cn/
167 |
```
--------------------------------------------------------------------------------
/packages/reactive-vue/src/observer/observerInVue2.ts:
--------------------------------------------------------------------------------
```typescript
1 | // https://github.com/mobxjs/mobx-vue/blob/master/src/observer.ts
2 |
3 | /**
4 | * @author Kuitos
5 | * @homepage https://github.com/kuitos/
6 | * @since 2018-05-22 16:39
7 | */
8 | import { Tracker, batch } from '@formily/reactive'
9 | import collectDataForVue from './collectData'
10 | import { Vue2 as Vue } from 'vue-demi'
11 | import { IObserverOptions } from '../types'
12 |
13 | const noop = () => {}
14 | const disposerSymbol = Symbol('disposerSymbol')
15 |
16 | function observer(Component: any, observerOptions?: IObserverOptions): any {
17 | const name =
18 | observerOptions?.name ||
19 | (Component as any).name ||
20 | (Component as any)._componentTag ||
21 | (Component.constructor && Component.constructor.name) ||
22 | '<component>'
23 |
24 | const originalOptions =
25 | typeof Component === 'object' ? Component : (Component as any).options
26 | // To not mutate the original component options, we need to construct a new one
27 | const dataDefinition = originalOptions.data
28 | const options = {
29 | name,
30 | ...originalOptions,
31 | data(vm: any) {
32 | return collectDataForVue(vm || this, dataDefinition)
33 | },
34 | // overrider the cached constructor to avoid extending skip
35 | // @see https://github.com/vuejs/vue/blob/6cc070063bd211229dff5108c99f7d11b6778550/src/core/global-api/extend.js#L24
36 | _Ctor: {},
37 | }
38 |
39 | // we couldn't use the Component as super class when Component was a VueClass, that will invoke the lifecycle twice after we called Component.extend
40 | const superProto =
41 | typeof Component === 'function' &&
42 | Object.getPrototypeOf(Component.prototype)
43 | const Super =
44 | superProto instanceof (Vue as any) ? superProto.constructor : Vue
45 | const ExtendedComponent = Super.extend(options)
46 |
47 | const { $mount, $destroy } = ExtendedComponent.prototype
48 |
49 | ExtendedComponent.prototype.$mount = function (this: any, ...args: any[]) {
50 | let mounted = false
51 | this[disposerSymbol] = noop
52 |
53 | let nativeRenderOfVue: any
54 |
55 | const reactiveRender = () => {
56 | batch(() => {
57 | tracker.track(() => {
58 | if (!mounted) {
59 | $mount.apply(this, args)
60 | mounted = true
61 | nativeRenderOfVue = this._watcher.getter
62 | // rewrite the native render method of vue with our reactive tracker render
63 | // thus if component updated by vue watcher, we could re track and collect dependencies by @formily/reactive
64 | this._watcher.getter = reactiveRender
65 | } else {
66 | nativeRenderOfVue.call(this, this)
67 | }
68 | })
69 | })
70 |
71 | return this
72 | }
73 |
74 | reactiveRender.$vm = this
75 |
76 | const tracker = new Tracker(() => {
77 | if (
78 | reactiveRender.$vm._isBeingDestroyed ||
79 | reactiveRender.$vm._isDestroyed
80 | ) {
81 | return tracker.dispose()
82 | }
83 |
84 | if (
85 | observerOptions?.scheduler &&
86 | typeof observerOptions.scheduler === 'function'
87 | ) {
88 | observerOptions.scheduler(reactiveRender)
89 | } else {
90 | reactiveRender()
91 | }
92 | })
93 |
94 | this[disposerSymbol] = tracker.dispose
95 |
96 | return reactiveRender()
97 | }
98 |
99 | ExtendedComponent.prototype.$destroy = function (this: any) {
100 | ;(this as any)[disposerSymbol]()
101 | $destroy.apply(this)
102 | }
103 |
104 | const extendedComponentNamePropertyDescriptor =
105 | Object.getOwnPropertyDescriptor(ExtendedComponent, 'name') || {}
106 | if (extendedComponentNamePropertyDescriptor.configurable === true) {
107 | Object.defineProperty(ExtendedComponent, 'name', {
108 | writable: false,
109 | value: name,
110 | enumerable: false,
111 | configurable: false,
112 | })
113 | }
114 |
115 | return ExtendedComponent
116 | }
117 |
118 | export { observer, observer as Observer }
119 |
```
--------------------------------------------------------------------------------
/packages/next/src/form-item/grid.scss:
--------------------------------------------------------------------------------
```scss
1 | .#{$form-item-cls} {
2 | .#{$form-item-cls}-item-col-24 {
3 | -webkit-box-flex: 0;
4 | -ms-flex: 0 0 100%;
5 | flex: 0 0 100%;
6 | max-width: 100%;
7 | }
8 |
9 | .#{$form-item-cls}-item-col-23 {
10 | -webkit-box-flex: 0;
11 | -ms-flex: 0 0 95.83333333%;
12 | flex: 0 0 95.83333333%;
13 | max-width: 95.83333333%;
14 | }
15 |
16 | .#{$form-item-cls}-item-col-22 {
17 | -webkit-box-flex: 0;
18 | -ms-flex: 0 0 91.66666667%;
19 | flex: 0 0 91.66666667%;
20 | max-width: 91.66666667%;
21 | }
22 |
23 | .#{$form-item-cls}-item-col-21 {
24 | -webkit-box-flex: 0;
25 | -ms-flex: 0 0 87.5%;
26 | flex: 0 0 87.5%;
27 | max-width: 87.5%;
28 | }
29 |
30 | .#{$form-item-cls}-item-col-20 {
31 | -webkit-box-flex: 0;
32 | -ms-flex: 0 0 83.33333333%;
33 | flex: 0 0 83.33333333%;
34 | max-width: 83.33333333%;
35 | }
36 |
37 | .#{$form-item-cls}-item-col-19 {
38 | -webkit-box-flex: 0;
39 | -ms-flex: 0 0 79.16666667%;
40 | flex: 0 0 79.16666667%;
41 | max-width: 79.16666667%;
42 | }
43 |
44 | .#{$form-item-cls}-item-col-18 {
45 | -webkit-box-flex: 0;
46 | -ms-flex: 0 0 75%;
47 | flex: 0 0 75%;
48 | max-width: 75%;
49 | }
50 |
51 | .#{$form-item-cls}-item-col-17 {
52 | -webkit-box-flex: 0;
53 | -ms-flex: 0 0 70.83333333%;
54 | flex: 0 0 70.83333333%;
55 | max-width: 70.83333333%;
56 | }
57 |
58 | .#{$form-item-cls}-item-col-16 {
59 | -webkit-box-flex: 0;
60 | -ms-flex: 0 0 66.66666667%;
61 | flex: 0 0 66.66666667%;
62 | max-width: 66.66666667%;
63 | }
64 |
65 | .#{$form-item-cls}-item-col-15 {
66 | -webkit-box-flex: 0;
67 | -ms-flex: 0 0 62.5%;
68 | flex: 0 0 62.5%;
69 | max-width: 62.5%;
70 | }
71 |
72 | .#{$form-item-cls}-item-col-14 {
73 | -webkit-box-flex: 0;
74 | -ms-flex: 0 0 58.33333333%;
75 | flex: 0 0 58.33333333%;
76 | max-width: 58.33333333%;
77 | }
78 |
79 | .#{$form-item-cls}-item-col-13 {
80 | -webkit-box-flex: 0;
81 | -ms-flex: 0 0 54.16666667%;
82 | flex: 0 0 54.16666667%;
83 | max-width: 54.16666667%;
84 | }
85 |
86 | .#{$form-item-cls}-item-col-12 {
87 | -webkit-box-flex: 0;
88 | -ms-flex: 0 0 50%;
89 | flex: 0 0 50%;
90 | max-width: 50%;
91 | }
92 |
93 | .#{$form-item-cls}-item-col-11 {
94 | -webkit-box-flex: 0;
95 | -ms-flex: 0 0 45.83333333%;
96 | flex: 0 0 45.83333333%;
97 | max-width: 45.83333333%;
98 | }
99 |
100 | .#{$form-item-cls}-item-col-10 {
101 | -webkit-box-flex: 0;
102 | -ms-flex: 0 0 41.66666667%;
103 | flex: 0 0 41.66666667%;
104 | max-width: 41.66666667%;
105 | }
106 |
107 | .#{$form-item-cls}-item-col-9 {
108 | -webkit-box-flex: 0;
109 | -ms-flex: 0 0 37.5%;
110 | flex: 0 0 37.5%;
111 | max-width: 37.5%;
112 | }
113 |
114 | .#{$form-item-cls}-item-col-8 {
115 | -webkit-box-flex: 0;
116 | -ms-flex: 0 0 33.33333333%;
117 | flex: 0 0 33.33333333%;
118 | max-width: 33.33333333%;
119 | }
120 |
121 | .#{$form-item-cls}-item-col-7 {
122 | -webkit-box-flex: 0;
123 | -ms-flex: 0 0 29.16666667%;
124 | flex: 0 0 29.16666667%;
125 | max-width: 29.16666667%;
126 | }
127 |
128 | .#{$form-item-cls}-item-col-6 {
129 | -webkit-box-flex: 0;
130 | -ms-flex: 0 0 25%;
131 | flex: 0 0 25%;
132 | max-width: 25%;
133 | }
134 |
135 | .#{$form-item-cls}-item-col-5 {
136 | -webkit-box-flex: 0;
137 | -ms-flex: 0 0 20.83333333%;
138 | flex: 0 0 20.83333333%;
139 | max-width: 20.83333333%;
140 | }
141 |
142 | .#{$form-item-cls}-item-col-4 {
143 | -webkit-box-flex: 0;
144 | -ms-flex: 0 0 16.66666667%;
145 | flex: 0 0 16.66666667%;
146 | max-width: 16.66666667%;
147 | }
148 |
149 | .#{$form-item-cls}-item-col-3 {
150 | -webkit-box-flex: 0;
151 | -ms-flex: 0 0 12.5%;
152 | flex: 0 0 12.5%;
153 | max-width: 12.5%;
154 | }
155 |
156 | .#{$form-item-cls}-item-col-2 {
157 | -webkit-box-flex: 0;
158 | -ms-flex: 0 0 8.33333333%;
159 | flex: 0 0 8.33333333%;
160 | max-width: 8.33333333%;
161 | }
162 |
163 | .#{$form-item-cls}-item-col-1 {
164 | -webkit-box-flex: 0;
165 | -ms-flex: 0 0 4.16666667%;
166 | flex: 0 0 4.16666667%;
167 | max-width: 4.16666667%;
168 | }
169 |
170 | .#{$form-item-cls}-item-col-0 {
171 | display: none;
172 | }
173 | }
174 |
```
--------------------------------------------------------------------------------
/packages/json-schema/src/polyfills/SPECIFICATION_1_0.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { registerPolyfills } from '../patches'
2 | import { toArr, isArr, isStr, lowerCase, isValid } from '@formily/shared'
3 | import { ISchema } from '../types'
4 |
5 | const VOID_COMPONENTS = [
6 | 'card',
7 | 'block',
8 | 'grid-col',
9 | 'grid-row',
10 | 'grid',
11 | 'layout',
12 | 'step',
13 | 'tab',
14 | 'text-box',
15 | ]
16 |
17 | const TYPE_DEFAULT_COMPONENTS = {}
18 |
19 | const transformCondition = (condition: string) => {
20 | if (isStr(condition)) {
21 | return condition.replace(/\$value/, '$self.value')
22 | }
23 | }
24 |
25 | const transformXLinkage = (linkages: any[]) => {
26 | if (isArr(linkages)) {
27 | return linkages.reduce((buf, item) => {
28 | if (!item) return buf
29 | if (item.type === 'value:visible') {
30 | return buf.concat({
31 | target: item.target,
32 | when: transformCondition(item.condition),
33 | fulfill: {
34 | state: {
35 | visible: true,
36 | },
37 | },
38 | otherwise: {
39 | state: {
40 | visible: false,
41 | },
42 | },
43 | })
44 | } else if (item.type === 'value:schema') {
45 | return buf.concat({
46 | target: item.target,
47 | when: transformCondition(item.condition),
48 | fulfill: {
49 | schema: SpecificationV1Polyfill({ version: '1.0', ...item.schema }),
50 | },
51 | otherwise: {
52 | schema: SpecificationV1Polyfill({
53 | version: '1.0',
54 | ...item.otherwise,
55 | }),
56 | },
57 | })
58 | } else if (item.type === 'value:state') {
59 | return buf.concat({
60 | target: item.target,
61 | when: transformCondition(item.condition),
62 | fulfill: {
63 | state: item.state,
64 | },
65 | otherwise: {
66 | state: item.otherwise,
67 | },
68 | })
69 | }
70 | }, [])
71 | }
72 | return []
73 | }
74 |
75 | const SpecificationV1Polyfill = (schema: ISchema) => {
76 | if (isValid(schema['editable'])) {
77 | schema['x-editable'] = schema['x-editable'] || schema['editable']
78 | delete schema['editable']
79 | }
80 | if (isValid(schema['visible'])) {
81 | schema['x-visible'] = schema['x-visible'] || schema['visible']
82 | delete schema['visible']
83 | }
84 | if (isValid(schema['display'])) {
85 | schema['x-display'] =
86 | schema['x-display'] || (schema['display'] ? 'visible' : 'hidden')
87 | delete schema['display']
88 | }
89 | if (isValid(schema['x-props'])) {
90 | schema['x-decorator-props'] =
91 | schema['x-decorator-props'] || schema['x-props']
92 | delete schema['display']
93 | }
94 | if (schema['x-linkages']) {
95 | schema['x-reactions'] = toArr(schema['x-reactions']).concat(
96 | transformXLinkage(schema['x-linkages'])
97 | )
98 | delete schema['x-linkages']
99 | }
100 | if (schema['x-component']) {
101 | if (
102 | VOID_COMPONENTS.some(
103 | (component) => lowerCase(component) === lowerCase(schema['x-component'])
104 | )
105 | ) {
106 | schema['type'] = 'void'
107 | }
108 | } else {
109 | if (TYPE_DEFAULT_COMPONENTS[schema['type']]) {
110 | schema['x-component'] = TYPE_DEFAULT_COMPONENTS[schema['type']]
111 | }
112 | }
113 | if (
114 | !schema['x-decorator'] &&
115 | schema['type'] !== 'void' &&
116 | schema['type'] !== 'object'
117 | ) {
118 | schema['x-decorator'] = schema['x-decorator'] || 'FormItem'
119 | }
120 | if (schema['x-rules']) {
121 | schema['x-validator'] = []
122 | .concat(schema['x-validator'] || [])
123 | .concat(schema['x-rules'])
124 | }
125 | return schema
126 | }
127 |
128 | registerPolyfills('1.0', SpecificationV1Polyfill)
129 |
130 | export const registerVoidComponents = (components: string[]) => {
131 | VOID_COMPONENTS.push(...components)
132 | }
133 |
134 | export const registerTypeDefaultComponents = (maps: Record<string, string>) => {
135 | Object.assign(TYPE_DEFAULT_COMPONENTS, maps)
136 | }
137 |
```
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Formily - Alibaba unified front-end form solution
3 | order: 10
4 | hero:
5 | title: Alibaba Formily
6 | desc: Alibaba Unified Front-end Form Solution
7 | actions:
8 | - text: Introduction
9 | link: /guide
10 | - text: Quick start
11 | link: /guide/quick-start
12 | features:
13 | - icon: https://img.alicdn.com/imgextra/i2/O1CN016i72sH1c5wh1kyy9U_!!6000000003550-55-tps-800-800.svg
14 | title: Easier to Use
15 | desc: Out of the box, rich cases
16 | - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg
17 | title: More Efficient
18 | desc: Fool writing, ultra-high performance
19 | - icon: https://img.alicdn.com/imgextra/i3/O1CN01xlETZk1G0WSQT6Xii_!!6000000000560-55-tps-800-800.svg
20 | title: More Professional
21 | desc: Complete, flexible and elegant
22 | footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by self
23 | ---
24 |
25 | ```tsx
26 | /**
27 | * inline: true
28 | */
29 | import React from 'react'
30 | import { Section } from './site/Section'
31 | import './site/styles.less'
32 |
33 | export default () => (
34 | <Section
35 | title="Fool Writing, Ultra-high Performance"
36 | style={{ marginTop: 40 }}
37 | titleStyle={{ paddingBottom: 100, fontWeight: 'bold' }}
38 | >
39 | <iframe
40 | className="codesandbox"
41 | src="https://codesandbox.io/embed/formilyyaliceshi-vbu4w?fontsize=12&module=%2FApp.tsx&theme=dark"
42 | allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
43 | sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
44 | ></iframe>
45 | </Section>
46 | )
47 | ```
48 |
49 | ```tsx
50 | /**
51 | * inline: true
52 | */
53 | import React from 'react'
54 | import { Section } from './site/Section'
55 | import './site/styles.less'
56 |
57 | export default () => (
58 | <Section
59 | title="Form Builder,Efficient Development"
60 | style={{ marginTop: 140, fontWeight: 'bold' }}
61 | titleStyle={{ paddingBottom: 140 }}
62 | scale={1.2}
63 | >
64 | <a href="//designable-antd.formilyjs.org" target="_blank" rel="noreferrer">
65 | <img src="//img.alicdn.com/imgextra/i2/O1CN01eI9FLz22tZek2jv7E_!!6000000007178-2-tps-3683-2272.png" />
66 | </a>
67 | </Section>
68 | )
69 | ```
70 |
71 | ```tsx
72 | /**
73 | * inline: true
74 | */
75 | import React from 'react'
76 | import { Section } from './site/Section'
77 | import './site/styles.less'
78 |
79 | export default () => (
80 | <Section
81 | title="Pure Core, More Extensibility"
82 | style={{ marginTop: 140 }}
83 | titleStyle={{ paddingBottom: 100, fontWeight: 'bold' }}
84 | >
85 | <a href="//core.formilyjs.org" target="_blank" rel="noreferrer">
86 | <img src="//img.alicdn.com/imgextra/i4/O1CN019qbf1b1ChnTfT9x3X_!!6000000000113-55-tps-1939-1199.svg" />
87 | </a>
88 | </Section>
89 | )
90 | ```
91 |
92 | ```tsx
93 | /**
94 | * inline: true
95 | */
96 | import React from 'react'
97 | import { Section } from './site/Section'
98 | import { Contributors } from './site/Contributors'
99 | import './site/styles.less'
100 |
101 | export default () => (
102 | <Section
103 | title="Active Community & Genius People"
104 | style={{ marginTop: 100 }}
105 | titleStyle={{ paddingBottom: 140, fontWeight: 'bold' }}
106 | >
107 | <Contributors />
108 | </Section>
109 | )
110 | ```
111 |
112 | ```tsx
113 | /**
114 | * inline: true
115 | */
116 | import React from 'react'
117 | import { Section } from './site/Section'
118 | import { QrCode, QrCodeGroup } from './site/QrCode'
119 | import './site/styles.less'
120 |
121 | export default () => (
122 | <Section
123 | title="High-Quality Community Group"
124 | style={{ marginTop: 140 }}
125 | titleStyle={{ paddingBottom: 20, fontWeight: 'bold' }}
126 | >
127 | <QrCodeGroup>
128 | <QrCode link="//img.alicdn.com/imgextra/i1/O1CN011zlc5b1uu1BDUpNg1_!!6000000006096-2-tps-978-1380.png" />
129 | </QrCodeGroup>
130 | </Section>
131 | )
132 | ```
133 |
```
--------------------------------------------------------------------------------
/packages/vue/src/shared/connect.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { isVue2, markRaw, defineComponent, getCurrentInstance } from 'vue-demi'
2 | import { isFn, isStr, FormPath, each, isValid } from '@formily/shared'
3 | import { isVoidField, GeneralField } from '@formily/core'
4 | import { observer } from '@formily/reactive-vue'
5 |
6 | import { useField } from '../hooks/useField'
7 | import h from './h'
8 |
9 | import type {
10 | VueComponent,
11 | IComponentMapper,
12 | IStateMapper,
13 | VueComponentProps,
14 | } from '../types'
15 |
16 | export function mapProps<T extends VueComponent = VueComponent>(
17 | ...args: IStateMapper<VueComponentProps<T>>[]
18 | ) {
19 | const transform = (input: VueComponentProps<T>, field: GeneralField) =>
20 | args.reduce((props, mapper) => {
21 | if (isFn(mapper)) {
22 | props = Object.assign(props, mapper(props, field))
23 | } else {
24 | each(mapper, (to, extract) => {
25 | const extractValue = FormPath.getIn(field, extract)
26 | const targetValue = isStr(to) ? to : extract
27 | const originalValue = FormPath.getIn(props, targetValue)
28 | if (extract === 'value') {
29 | if (to !== extract) {
30 | delete props['value']
31 | }
32 | }
33 | if (isValid(originalValue) && !isValid(extractValue)) return
34 | FormPath.setIn(props, targetValue, extractValue)
35 | })
36 | }
37 | return props
38 | }, input)
39 |
40 | return (target: T) => {
41 | return observer(
42 | defineComponent({
43 | name: target.name ? `Connected${target.name}` : `ConnectedComponent`,
44 | setup(props, { attrs, slots, listeners }: any) {
45 | const fieldRef = useField()
46 | return () => {
47 | const newAttrs = fieldRef.value
48 | ? transform({ ...attrs } as VueComponentProps<T>, fieldRef.value)
49 | : { ...attrs }
50 | return h(
51 | target,
52 | {
53 | attrs: newAttrs,
54 | on: listeners,
55 | },
56 | slots
57 | )
58 | }
59 | },
60 | })
61 | )
62 | }
63 | }
64 |
65 | export function mapReadPretty<T extends VueComponent, C extends VueComponent>(
66 | component: C,
67 | readPrettyProps?: Record<string, any>
68 | ) {
69 | return (target: T) => {
70 | return observer(
71 | defineComponent({
72 | name: target.name ? `Read${target.name}` : `ReadComponent`,
73 | setup(props, { attrs, slots, listeners }: Record<string, any>) {
74 | const fieldRef = useField()
75 | return () => {
76 | const field = fieldRef.value
77 | return h(
78 | field && !isVoidField(field) && field.pattern === 'readPretty'
79 | ? component
80 | : target,
81 | {
82 | attrs: {
83 | ...readPrettyProps,
84 | ...attrs,
85 | },
86 | on: listeners,
87 | },
88 | slots
89 | )
90 | }
91 | },
92 | })
93 | )
94 | }
95 | }
96 |
97 | export function connect<T extends VueComponent>(
98 | target: T,
99 | ...args: IComponentMapper[]
100 | ): T {
101 | const Component = args.reduce((target: VueComponent, mapper) => {
102 | return mapper(target)
103 | }, target)
104 | /* istanbul ignore else */
105 | if (isVue2) {
106 | const functionalComponent = defineComponent({
107 | functional: true,
108 | name: target.name,
109 | render(h, context) {
110 | return h(Component, context.data, context.children)
111 | },
112 | })
113 | return markRaw(functionalComponent) as T
114 | } else {
115 | const functionalComponent = defineComponent({
116 | name: target.name,
117 | setup(props, { attrs, slots }) {
118 | return () => {
119 | return h(Component, { props, attrs }, slots)
120 | }
121 | },
122 | })
123 | return markRaw(functionalComponent) as T
124 | }
125 | }
126 |
```
--------------------------------------------------------------------------------
/packages/path/src/destructor.ts:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | Segments,
3 | Node,
4 | DestructorRules,
5 | isArrayPattern,
6 | isObjectPattern,
7 | isIdentifier,
8 | isDestructorExpression,
9 | } from './types'
10 | import { isNum } from './shared'
11 |
12 | type Mutators = {
13 | getIn: (segments: Segments, source: any) => any
14 | setIn: (segments: Segments, source: any, value: any) => void
15 | deleteIn?: (segments: Segments, source: any) => any
16 | existIn?: (segments: Segments, source: any, start: number) => boolean
17 | }
18 |
19 | const DestructorCache = new Map()
20 |
21 | const isValid = (val: any) => val !== undefined && val !== null
22 |
23 | export const getDestructor = (source: string) => {
24 | return DestructorCache.get(source)
25 | }
26 |
27 | export const setDestructor = (source: string, rules: DestructorRules) => {
28 | DestructorCache.set(source, rules)
29 | }
30 |
31 | export const parseDestructorRules = (node: Node): DestructorRules => {
32 | const rules = []
33 | if (isObjectPattern(node)) {
34 | let index = 0
35 | node.properties.forEach((child) => {
36 | rules[index] = {
37 | path: [],
38 | }
39 | rules[index].key = child.key.value
40 | rules[index].path.push(child.key.value)
41 | if (isIdentifier(child.value)) {
42 | rules[index].key = child.value.value
43 | }
44 | const basePath = rules[index].path
45 | const childRules = parseDestructorRules(child.value as Node)
46 | let k = index
47 | childRules.forEach((rule) => {
48 | if (rules[k]) {
49 | rules[k].key = rule.key
50 | rules[k].path = basePath.concat(rule.path)
51 | } else {
52 | rules[k] = {
53 | key: rule.key,
54 | path: basePath.concat(rule.path),
55 | }
56 | }
57 | k++
58 | })
59 | if (k > index) {
60 | index = k
61 | } else {
62 | index++
63 | }
64 | })
65 | return rules
66 | } else if (isArrayPattern(node)) {
67 | let index = 0
68 | node.elements.forEach((child, key) => {
69 | rules[index] = {
70 | path: [],
71 | }
72 | rules[index].key = key
73 | rules[index].path.push(key)
74 | if (isIdentifier(child)) {
75 | rules[index].key = child.value
76 | }
77 | const basePath = rules[index].path
78 | const childRules = parseDestructorRules(child as Node)
79 | let k = index
80 | childRules.forEach((rule) => {
81 | if (rules[k]) {
82 | rules[k].key = rule.key
83 | rules[k].path = basePath.concat(rule.path)
84 | } else {
85 | rules[k] = {
86 | key: rule.key,
87 | path: basePath.concat(rule.path),
88 | }
89 | }
90 | k++
91 | })
92 | if (k > index) {
93 | index = k
94 | } else {
95 | index++
96 | }
97 | })
98 | return rules
99 | }
100 | if (isDestructorExpression(node)) {
101 | return parseDestructorRules(node.value)
102 | }
103 | return rules
104 | }
105 |
106 | export const setInByDestructor = (
107 | source: any,
108 | rules: DestructorRules,
109 | value: any,
110 | mutators: Mutators
111 | ) => {
112 | rules.forEach(({ key, path }) => {
113 | mutators.setIn([key], source, mutators.getIn(path, value))
114 | })
115 | }
116 |
117 | export const getInByDestructor = (
118 | source: any,
119 | rules: DestructorRules,
120 | mutators: Mutators
121 | ) => {
122 | let response = {}
123 | if (rules.length) {
124 | if (isNum(rules[0].path[0])) {
125 | response = []
126 | }
127 | }
128 | source = isValid(source) ? source : {}
129 | rules.forEach(({ key, path }) => {
130 | mutators.setIn(path, response, source[key])
131 | })
132 | return response
133 | }
134 |
135 | export const deleteInByDestructor = (
136 | source: any,
137 | rules: DestructorRules,
138 | mutators: Mutators
139 | ) => {
140 | rules.forEach(({ key }) => {
141 | mutators.deleteIn([key], source)
142 | })
143 | }
144 |
145 | export const existInByDestructor = (
146 | source: any,
147 | rules: DestructorRules,
148 | start: number,
149 | mutators: Mutators
150 | ) => {
151 | return rules.every(({ key }) => {
152 | return mutators.existIn([key], source, start)
153 | })
154 | }
155 |
```
--------------------------------------------------------------------------------
/packages/element/docs/demos/guide/array-table/effects-markup-schema.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <FormProvider :form="form">
3 | <SchemaField>
4 | <SchemaBooleanField
5 | name="hideFirstColumn"
6 | x-decorator="FormItem"
7 | x-component="Switch"
8 | title="隐藏A2"
9 | />
10 | <SchemaArrayField
11 | name="array"
12 | x-decorator="FormItem"
13 | x-component="ArrayTable"
14 | >
15 | <SchemaObjectField>
16 | <SchemaVoidField
17 | name="column1"
18 | x-component="ArrayTable.Column"
19 | :x-component-props="{ width: 80, title: 'Index' }"
20 | ><SchemaVoidField x-component="ArrayTable.Index" />
21 | </SchemaVoidField>
22 | <SchemaVoidField
23 | name="column2"
24 | x-component="ArrayTable.Column"
25 | :x-component-props="{
26 | title: '显隐->A2',
27 | width: 100,
28 | }"
29 | >
30 | <SchemaBooleanField
31 | name="a1"
32 | x-decorator="FormItem"
33 | x-component="Switch"
34 | />
35 | </SchemaVoidField>
36 | <SchemaVoidField
37 | x-component="ArrayTable.Column"
38 | name="column3"
39 | :x-component-props="{ title: 'A2', width: 200 }"
40 | >
41 | <SchemaStringField
42 | x-decorator="FormItem"
43 | name="a2"
44 | x-component="Input"
45 | />
46 | </SchemaVoidField>
47 | <SchemaVoidField
48 | name="column4"
49 | x-component="ArrayTable.Column"
50 | :x-component-props="{ title: 'A3' }"
51 | >
52 | <SchemaStringField
53 | name="a3"
54 | x-decorator="FormItem"
55 | x-component="Input"
56 | />
57 | </SchemaVoidField>
58 | <SchemaVoidField
59 | name="column5"
60 | x-component="ArrayTable.Column"
61 | :x-component-props="{
62 | title: 'Operations',
63 | prop: 'operations',
64 | width: 200,
65 | fixed: 'right',
66 | }"
67 | >
68 | <SchemaVoidField x-component="FormItem">
69 | <SchemaVoidField x-component="ArrayTable.Remove" />
70 | <SchemaVoidField x-component="ArrayTable.MoveUp" />
71 | <SchemaVoidField x-component="ArrayTable.MoveDown" />
72 | </SchemaVoidField>
73 | </SchemaVoidField>
74 | </SchemaObjectField>
75 | <SchemaVoidField x-component="ArrayTable.Addition" title="添加条目" />
76 | </SchemaArrayField>
77 | </SchemaField>
78 | <Submit @submit="log">提交</Submit>
79 | </FormProvider>
80 | </template>
81 |
82 | <script>
83 | import { createForm, onFieldChange, onFieldReact } from '@formily/core'
84 | import { FormProvider, createSchemaField } from '@formily/vue'
85 | import {
86 | Submit,
87 | FormItem,
88 | ArrayTable,
89 | Input,
90 | Editable,
91 | Switch,
92 | } from '@formily/element'
93 |
94 | const fields = createSchemaField({
95 | components: {
96 | FormItem,
97 | ArrayTable,
98 | Input,
99 | Editable,
100 | Switch,
101 | },
102 | })
103 |
104 | export default {
105 | components: { FormProvider, Submit, ...fields },
106 | data() {
107 | const form = createForm({
108 | effects: () => {
109 | //主动联动模式
110 | onFieldChange('hideFirstColumn', ['value'], (field) => {
111 | field.query('array.column3').take((target) => {
112 | console.log('target', target)
113 | target.visible = !field.value
114 | })
115 | field.query('array.*.a2').take((target) => {
116 | target.visible = !field.value
117 | })
118 | })
119 | //被动联动模式
120 | onFieldReact('array.*.a2', (field) => {
121 | field.visible = !field.query('.a1').get('value')
122 | })
123 | },
124 | })
125 |
126 | return {
127 | form,
128 | }
129 | },
130 | methods: {
131 | log(...v) {
132 | console.log(...v)
133 | },
134 | },
135 | }
136 | </script>
137 |
```
--------------------------------------------------------------------------------
/packages/reactive/src/internals.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { isFn, isCollectionType, isNormalType } from './checkers'
2 | import {
3 | RawProxy,
4 | ProxyRaw,
5 | MakeObModelSymbol,
6 | RawShallowProxy,
7 | } from './environment'
8 | import { baseHandlers, collectionHandlers } from './handlers'
9 | import { buildDataTree, getDataNode } from './tree'
10 | import { isSupportObservable } from './externals'
11 | import { PropertyKey, IVisitor, BoundaryFunction } from './types'
12 |
13 | const createNormalProxy = (target: any, shallow?: boolean) => {
14 | const proxy = new Proxy(target, baseHandlers)
15 | ProxyRaw.set(proxy, target)
16 | if (shallow) {
17 | RawShallowProxy.set(target, proxy)
18 | } else {
19 | RawProxy.set(target, proxy)
20 | }
21 | return proxy
22 | }
23 |
24 | const createCollectionProxy = (target: any, shallow?: boolean) => {
25 | const proxy = new Proxy(target, collectionHandlers)
26 | ProxyRaw.set(proxy, target)
27 | if (shallow) {
28 | RawShallowProxy.set(target, proxy)
29 | } else {
30 | RawProxy.set(target, proxy)
31 | }
32 | return proxy
33 | }
34 |
35 | const createShallowProxy = (target: any) => {
36 | if (isNormalType(target)) return createNormalProxy(target, true)
37 | if (isCollectionType(target)) return createCollectionProxy(target, true)
38 | // never reach
39 | return target
40 | }
41 |
42 | export const createObservable = (
43 | target: any,
44 | key?: PropertyKey,
45 | value?: any,
46 | shallow?: boolean
47 | ) => {
48 | if (typeof value !== 'object') return value
49 | const raw = ProxyRaw.get(value)
50 | if (!!raw) {
51 | const node = getDataNode(raw)
52 | if (!node.target) node.target = target
53 | node.key = key
54 | return value
55 | }
56 |
57 | if (!isSupportObservable(value)) return value
58 |
59 | if (target) {
60 | const parentRaw = ProxyRaw.get(target) || target
61 | const isShallowParent = RawShallowProxy.get(parentRaw)
62 | if (isShallowParent) return value
63 | }
64 |
65 | buildDataTree(target, key, value)
66 | if (shallow) return createShallowProxy(value)
67 | if (isNormalType(value)) return createNormalProxy(value)
68 | if (isCollectionType(value)) return createCollectionProxy(value)
69 | // never reach
70 | return value
71 | }
72 |
73 | export const createAnnotation = <T extends (visitor: IVisitor) => any>(
74 | maker: T
75 | ) => {
76 | const annotation = (target: any): ReturnType<T> => {
77 | return maker({ value: target })
78 | }
79 | if (isFn(maker)) {
80 | annotation[MakeObModelSymbol] = maker
81 | }
82 | return annotation
83 | }
84 |
85 | export const getObservableMaker = (target: any) => {
86 | if (target[MakeObModelSymbol]) {
87 | if (!target[MakeObModelSymbol][MakeObModelSymbol]) {
88 | return target[MakeObModelSymbol]
89 | }
90 | return getObservableMaker(target[MakeObModelSymbol])
91 | }
92 | }
93 |
94 | export const createBoundaryFunction = (
95 | start: (...args: any) => void,
96 | end: (...args: any) => void
97 | ) => {
98 | function boundary<F extends (...args: any) => any>(fn?: F): ReturnType<F> {
99 | let results: ReturnType<F>
100 | try {
101 | start()
102 | if (isFn(fn)) {
103 | results = fn()
104 | }
105 | } finally {
106 | end()
107 | }
108 | return results
109 | }
110 |
111 | boundary.bound = createBindFunction(boundary)
112 | return boundary
113 | }
114 |
115 | export const createBindFunction = <Boundary extends BoundaryFunction>(
116 | boundary: Boundary
117 | ) => {
118 | function bind<F extends (...args: any[]) => any>(
119 | callback?: F,
120 | context?: any
121 | ): F {
122 | return ((...args: any[]) =>
123 | boundary(() => callback.apply(context, args))) as any
124 | }
125 | return bind
126 | }
127 |
128 | export const createBoundaryAnnotation = (
129 | start: (...args: any) => void,
130 | end: (...args: any) => void
131 | ) => {
132 | const boundary = createBoundaryFunction(start, end)
133 | const annotation = createAnnotation(({ target, key }) => {
134 | target[key] = boundary.bound(target[key], target)
135 | return target
136 | })
137 | boundary[MakeObModelSymbol] = annotation
138 | boundary.bound[MakeObModelSymbol] = annotation
139 | return boundary
140 | }
141 |
```
--------------------------------------------------------------------------------
/packages/antd/src/__builtins__/sort.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { DndContext, DragEndEvent, DragStartEvent } from '@dnd-kit/core'
2 | import {
3 | SortableContext,
4 | useSortable,
5 | verticalListSortingStrategy,
6 | } from '@dnd-kit/sortable'
7 | import { ReactFC } from '@formily/reactive-react'
8 | import React, { createContext, useContext, useMemo } from 'react'
9 |
10 | export interface ISortableContainerProps {
11 | list: any[]
12 | start?: number
13 | accessibility?: {
14 | container?: Element
15 | }
16 | onSortStart?: (event: DragStartEvent) => void
17 | onSortEnd?: (event: { oldIndex: number; newIndex: number }) => void
18 | }
19 |
20 | export function SortableContainer<T extends React.HTMLAttributes<HTMLElement>>(
21 | Component: ReactFC<T>
22 | ): ReactFC<ISortableContainerProps & T> {
23 | return ({
24 | list,
25 | start = 0,
26 | accessibility,
27 | onSortStart,
28 | onSortEnd,
29 | ...props
30 | }) => {
31 | const _onSortEnd = (event: DragEndEvent) => {
32 | const { active, over } = event
33 | const oldIndex = (active.id as number) - 1
34 | const newIndex = (over?.id as number) - 1
35 | onSortEnd?.({
36 | oldIndex,
37 | newIndex,
38 | })
39 | }
40 |
41 | return (
42 | <DndContext
43 | accessibility={accessibility}
44 | onDragStart={onSortStart}
45 | onDragEnd={_onSortEnd}
46 | >
47 | <SortableContext
48 | items={list.map((_, index) => index + start + 1)}
49 | strategy={verticalListSortingStrategy}
50 | >
51 | <Component {...(props as unknown as T)}>{props.children}</Component>
52 | </SortableContext>
53 | </DndContext>
54 | )
55 | }
56 | }
57 |
58 | export const useSortableItem = () => {
59 | return useContext(SortableItemContext)
60 | }
61 |
62 | export const SortableItemContext = createContext<
63 | Partial<ReturnType<typeof useSortable>>
64 | >({})
65 |
66 | export interface ISortableElementProps {
67 | index?: number
68 | lockAxis?: 'x' | 'y'
69 | }
70 |
71 | export function SortableElement<T extends React.HTMLAttributes<HTMLElement>>(
72 | Component: ReactFC<T>
73 | ): ReactFC<T & ISortableElementProps> {
74 | return ({ index = 0, lockAxis, ...props }) => {
75 | const sortable = useSortable({
76 | id: index + 1,
77 | })
78 | const { setNodeRef, transform, transition, isDragging } = sortable
79 | if (transform) {
80 | switch (lockAxis) {
81 | case 'x':
82 | transform.y = 0
83 | break
84 | case 'y':
85 | transform.x = 0
86 | break
87 | default:
88 | break
89 | }
90 | }
91 |
92 | const style = useMemo(() => {
93 | const itemStyle: React.CSSProperties = {
94 | position: 'relative',
95 | touchAction: 'none',
96 | zIndex: 1,
97 | transform: `translate3d(${transform?.x || 0}px, ${
98 | transform?.y || 0
99 | }px, 0)`,
100 | transition: `${transform ? 'all 200ms ease' : ''}`,
101 | }
102 | const dragStyle: React.CSSProperties = {
103 | transition,
104 | opacity: '0.8',
105 | transform: `translate3d(${transform?.x || 0}px, ${
106 | transform?.y || 0
107 | }px, 0)`,
108 | }
109 |
110 | const computedStyle = isDragging
111 | ? {
112 | ...itemStyle,
113 | ...dragStyle,
114 | ...props.style,
115 | }
116 | : {
117 | ...itemStyle,
118 | ...props.style,
119 | }
120 |
121 | return computedStyle
122 | }, [isDragging, transform, transition, props.style])
123 |
124 | return (
125 | <SortableItemContext.Provider value={sortable}>
126 | {Component({
127 | ...props,
128 | style,
129 | ref: setNodeRef,
130 | } as unknown as T)}
131 | </SortableItemContext.Provider>
132 | )
133 | }
134 | }
135 |
136 | export function SortableHandle<T extends React.HTMLAttributes<HTMLElement>>(
137 | Component: ReactFC<T>
138 | ): ReactFC<T> {
139 | return (props: T) => {
140 | const { attributes, listeners } = useSortableItem()
141 | return <Component {...props} {...attributes} {...listeners} />
142 | }
143 | }
144 |
```
--------------------------------------------------------------------------------
/packages/vue/src/types/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Component } from 'vue'
2 | import * as VueDemi from 'vue-demi'
3 | import {
4 | Form,
5 | IFieldFactoryProps,
6 | IVoidFieldFactoryProps,
7 | GeneralField,
8 | Field,
9 | ObjectField,
10 | FormPatternTypes,
11 | FieldDisplayTypes,
12 | FieldValidator,
13 | } from '@formily/core'
14 | import type { FormPathPattern } from '@formily/shared'
15 | import type { ISchema, Schema, SchemaKey } from '@formily/json-schema'
16 |
17 | class Helper<Props> {
18 | Return = VueDemi.defineComponent({} as { props: Record<keyof Props, any> })
19 | }
20 |
21 | export type DefineComponent<Props> = Helper<Props>['Return']
22 |
23 | export type VueComponent = Component
24 |
25 | export type VueComponentOptionsWithProps = {
26 | props: unknown
27 | }
28 |
29 | export type VueComponentProps<T extends VueComponent> =
30 | T extends VueComponentOptionsWithProps ? T['props'] : T
31 |
32 | export interface IProviderProps {
33 | form: Form
34 | }
35 |
36 | export type IFieldProps<
37 | D extends VueComponent = VueComponent,
38 | C extends VueComponent = VueComponent
39 | > = IFieldFactoryProps<D, C>
40 |
41 | export type IVoidFieldProps<
42 | D extends VueComponent = VueComponent,
43 | C extends VueComponent = VueComponent
44 | > = IVoidFieldFactoryProps<D, C>
45 |
46 | export type IArrayFieldProps = IFieldProps
47 | export type IObjectFieldProps = IFieldProps
48 |
49 | export interface IReactiveFieldProps {
50 | fieldType: 'Field' | 'ArrayField' | 'ObjectField' | 'VoidField'
51 | fieldProps: IFieldProps | IVoidFieldProps
52 | }
53 |
54 | export interface IComponentMapper<T extends VueComponent = any> {
55 | (target: T): VueComponent
56 | }
57 |
58 | export type IStateMapper<Props> =
59 | | {
60 | [key in keyof Field]?: keyof Props | boolean
61 | }
62 | | ((props: Props, field: GeneralField) => Props)
63 |
64 | export type SchemaVueComponents = Record<string, VueComponent>
65 |
66 | export interface ISchemaFieldVueFactoryOptions<
67 | Components extends SchemaVueComponents = any
68 | > {
69 | components?: Components
70 | scope?: any
71 | }
72 |
73 | export interface ISchemaFieldProps
74 | extends Omit<IRecursionFieldProps, 'name' | 'schema'> {
75 | schema?: ISchema
76 | components?: {
77 | [key: string]: VueComponent
78 | }
79 | scope?: any
80 | name?: SchemaKey
81 | }
82 |
83 | export interface ISchemaMapper {
84 | (schema: Schema, name: SchemaKey): Schema
85 | }
86 |
87 | export interface ISchemaFilter {
88 | (schema: Schema, name: SchemaKey): boolean
89 | }
90 |
91 | export interface IRecursionFieldProps {
92 | schema: Schema
93 | name?: SchemaKey
94 | basePath?: FormPathPattern
95 | onlyRenderProperties?: boolean
96 | onlyRenderSelf?: boolean
97 | mapProperties?: ISchemaMapper
98 | filterProperties?: ISchemaFilter
99 | }
100 |
101 | export type ObjectKey = string | number | boolean | symbol
102 |
103 | export type KeyOfComponents<T> = keyof T
104 |
105 | export type ComponentPath<
106 | T,
107 | Key extends KeyOfComponents<T> = KeyOfComponents<T>
108 | > = Key extends string ? Key : never
109 |
110 | export type ComponentPropsByPathValue<
111 | T extends SchemaVueComponents,
112 | P extends ComponentPath<T>
113 | > = P extends keyof T ? VueComponentProps<T[P]> : never
114 |
115 | export type ISchemaMarkupFieldProps<
116 | Components extends SchemaVueComponents = SchemaVueComponents,
117 | Decorator extends ComponentPath<Components> = ComponentPath<Components>,
118 | Component extends ComponentPath<Components> = ComponentPath<Components>
119 | > = ISchema<
120 | Decorator,
121 | Component,
122 | ComponentPropsByPathValue<Components, Decorator>,
123 | ComponentPropsByPathValue<Components, Component>,
124 | FormPatternTypes,
125 | FieldDisplayTypes,
126 | FieldValidator,
127 | string,
128 | GeneralField
129 | >
130 |
131 | export type ISchemaTypeFieldProps<
132 | Components extends SchemaVueComponents = SchemaVueComponents,
133 | Decorator extends ComponentPath<Components> = ComponentPath<Components>,
134 | Component extends ComponentPath<Components> = ComponentPath<Components>
135 | > = Omit<ISchemaMarkupFieldProps<Components, Decorator, Component>, 'type'>
136 |
137 | export type IExpressionScopeProps = {
138 | value: any
139 | }
140 |
```
--------------------------------------------------------------------------------
/packages/core/docs/guide/mvvm.md:
--------------------------------------------------------------------------------
```markdown
1 | # MVVM
2 |
3 | ## OOP architecture
4 |
5 | **MVVM** (**Model–view–viewmodel**) is an OOP software architecture model. Its core is to separate the logic and view of our application to improve code maintainability and application robustness. We can use a picture to describe:
6 |
7 | 
8 |
9 | To explain, the View (view layer) is responsible for maintaining the UI structure and style, and is responsible for data binding with the ViewModel (view model). The data binding relationship here is two-way, that is, the ViewModel (view model) data occurs. Changes will trigger the update of the View (view layer), and at the same time changes in the data of the view layer will trigger the changes of the ViewModel (view model). Model is more biased towards the actual business data processing model. Both ViewModel and Model are congested models, and both are injected with business logic from different fields. For example, the business logic of ViewModel is more biased towards the domain logic of the view interaction layer, while the business logic of Model is more biased towards the processing logic of business data.
10 |
11 | So, what should the Formily solution be positioned in MVVM?
12 |
13 | Obviously, Formily provides two tiers of View and ViewModel capabilities. View is @formily/react @formily/vue, which is specifically used to bridge communication with @formily/core. Therefore, @formily/core is positioned at the ViewModel layer. ,
14 |
15 | Where is the Model layer?
16 |
17 | Of course it is our actual business code layer, this layer formily will not manage, so at this layer, whether users maintain a Model in OOP mode or maintain a series of business logic function sets in FP mode, formily Don't care.
18 |
19 | Therefore, this also makes formily's intrusion into the business very low, because formily's goal is to reduce the cost of users designing ViewModels, allowing users to focus more on the realization of business logic.
20 |
21 | ## FP architecture
22 |
23 | Remember before the React team used the simplest expression **UI = fn(State)** to express the entire React system? Such a functional UI is very simple and clear. Will it conflict with the MVVM model?
24 |
25 | There is no conflict, because in the MVVM mode, the relationship between View and ViewModel is actually approximately equal to **UI = fn(State)**, because ViewModel is a congestion model injected with logic, which is related to **fn(State) ** can achieve the same goal, but it is a more OOP expression, but **fn(State)** is a more functional expression, the state exists as an anemia model, through one function after another, Immutable updates to the anemia model are finally reflected in the UI.
26 |
27 | Therefore, from the perspective of separation of logic and data, functional expression is clearer, but functional expression requires all data to be Immutable. Therefore, in scenarios with high performance requirements, the benefits of using a functional model will not be too great, of course, this is only the case in the js language. On the contrary, the MVVM model requires more data for Reactive data, that is, a responsive data model that can manipulate data by reference, so that data changes can be accurately monitored, and finally reflected on the UI.
28 |
29 | Therefore, in the form scenario, the performance advantage of the MVVM mode will be better. The most important thing is that most of the GUI products that have survived for decades almost all use MVVM coincidentally. It seems that in the front-end field, the function The type system will be more academic. In terms of the actual benefits to the business, MVVM is still the first choice.
30 |
```
--------------------------------------------------------------------------------
/packages/react/docs/api/components/ArrayField.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | order: 1
3 | ---
4 |
5 | # ArrayField
6 |
7 | ## Description
8 |
9 | As @formily/core's [createArrayField](https://core.formilyjs.org/api/models/form#createarrayfield) React implementation, it is a bridge component specifically used to bind ViewModel and input controls, ArrayField component Property reference [IFieldFactoryProps](https://core.formilyjs.org/api/models/form#ifieldfactoryprops)
10 |
11 | <Alert>
12 | When we use the ArrayField component, we must remember to pass the name attribute. At the same time, use render props to organize sub-components
13 | </Alert>
14 |
15 | ## Signature
16 |
17 | ```ts
18 | type ArrayField = React.FC<React.PropsWithChildren<IFieldFactoryProps>>
19 | ```
20 |
21 | ## Custom component use case
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 use cases
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/next/src/form-button-group/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * 1. FormItem网格布局
3 | * 2. 居中,居右,居左布局
4 | * 3. 行内布局
5 | * 4. 吸底布局
6 | */
7 | import React, { useRef, useLayoutEffect, useState } from 'react'
8 | import StickyBox from 'react-sticky-box'
9 | import { ReactFC } from '@formily/react'
10 | import { Space, ISpaceProps } from '../space'
11 | import { BaseItem, IFormItemProps } from '../form-item'
12 | import { usePrefixCls } from '../__builtins__'
13 | import cls from 'classnames'
14 | interface IStickyProps extends React.ComponentProps<typeof StickyBox> {
15 | align?: React.CSSProperties['textAlign']
16 | }
17 |
18 | type IFormButtonGroupProps = Omit<ISpaceProps, 'align' | 'size'> & {
19 | align?: React.CSSProperties['textAlign']
20 | gutter?: number
21 | }
22 |
23 | type ComposedButtonGroup = ReactFC<IFormButtonGroupProps> & {
24 | Sticky: ReactFC<IStickyProps>
25 | FormItem: ReactFC<
26 | IFormItemProps & {
27 | gutter?: number
28 | }
29 | >
30 | }
31 |
32 | function getInheritedBackgroundColor(el: HTMLElement) {
33 | // get default style for current browser
34 | let defaultStyle = getDefaultBackground() // typically "rgba(0, 0, 0, 0)"
35 |
36 | // get computed color for el
37 | let backgroundColor = window.getComputedStyle(el).backgroundColor
38 |
39 | // if we got a real value, return it
40 | if (backgroundColor != defaultStyle) return backgroundColor
41 |
42 | // if we've reached the top parent el without getting an explicit color, return default
43 | if (!el.parentElement) return defaultStyle
44 |
45 | // otherwise, recurse and try again on parent element
46 | return getInheritedBackgroundColor(el.parentElement)
47 | }
48 |
49 | function getDefaultBackground() {
50 | // have to add to the document in order to use getComputedStyle
51 | let div = document.createElement('div')
52 | document.head.appendChild(div)
53 | let bg = window.getComputedStyle(div).backgroundColor
54 | document.head.removeChild(div)
55 | return bg
56 | }
57 |
58 | export const FormButtonGroup: ComposedButtonGroup = ({
59 | align = 'left',
60 | gutter,
61 | ...props
62 | }) => {
63 | const prefixCls = usePrefixCls('formily-button-group')
64 | return (
65 | <Space
66 | {...props}
67 | size={gutter}
68 | className={cls(prefixCls, props.className)}
69 | style={{
70 | ...props.style,
71 | justifyContent:
72 | align === 'left'
73 | ? 'flex-start'
74 | : align === 'right'
75 | ? 'flex-end'
76 | : 'center',
77 | display: 'flex',
78 | }}
79 | >
80 | {props.children}
81 | </Space>
82 | )
83 | }
84 |
85 | FormButtonGroup.FormItem = ({ gutter, ...props }) => {
86 | return (
87 | <BaseItem
88 | {...props}
89 | label=" "
90 | style={{
91 | margin: 0,
92 | padding: 0,
93 | ...props.style,
94 | width: '100%',
95 | }}
96 | colon={false}
97 | >
98 | {props.children?.['length'] ? (
99 | <Space size={gutter}>{props.children}</Space>
100 | ) : (
101 | props.children
102 | )}
103 | </BaseItem>
104 | )
105 | }
106 |
107 | FormButtonGroup.Sticky = ({ align = 'left', ...props }) => {
108 | const ref = useRef()
109 | const [color, setColor] = useState('transparent')
110 | const prefixCls = usePrefixCls('formily-button-group')
111 |
112 | useLayoutEffect(() => {
113 | if (ref.current) {
114 | const computed = getInheritedBackgroundColor(ref.current)
115 | if (computed !== color) {
116 | setColor(computed)
117 | }
118 | }
119 | })
120 | return (
121 | <StickyBox
122 | {...props}
123 | className={cls(`${prefixCls}-sticky`, props.className)}
124 | style={{
125 | backgroundColor: color,
126 | ...props.style,
127 | }}
128 | bottom
129 | >
130 | <div
131 | ref={ref}
132 | className={`${prefixCls}-sticky-inner`}
133 | style={{
134 | ...props.style,
135 | justifyContent:
136 | align === 'left'
137 | ? 'flex-start'
138 | : align === 'right'
139 | ? 'flex-end'
140 | : 'center',
141 | }}
142 | >
143 | {props.children}
144 | </div>
145 | </StickyBox>
146 | )
147 | }
148 |
149 | export default FormButtonGroup
150 |
```
--------------------------------------------------------------------------------
/packages/antd/docs/components/Form.md:
--------------------------------------------------------------------------------
```markdown
1 | # Form
2 |
3 | > The combination of FormProvider + FormLayout + form tags can help us quickly implement forms that are submitted with carriage return and can be laid out in batches
4 |
5 | ## Use Cases
6 |
7 | ```tsx
8 | import React from 'react'
9 | import {
10 | Input,
11 | Select,
12 | Form,
13 | FormItem,
14 | FormGrid,
15 | FormButtonGroup,
16 | Submit,
17 | } from '@formily/antd'
18 | import { createForm } from '@formily/core'
19 | import { Field } from '@formily/react'
20 |
21 | const form = createForm()
22 |
23 | export default () => (
24 | <Form
25 | form={form}
26 | layout="vertical"
27 | feedbackLayout="terse"
28 | onAutoSubmit={console.log}
29 | onAutoSubmitFailed={console.log}
30 | >
31 | <FormGrid maxColumns={4}>
32 | <Field
33 | name="aa"
34 | title="select box"
35 | decorator={[FormItem]}
36 | component={[Select]}
37 | dataSource={[
38 | {
39 | label: 'Option 1',
40 | value: 1,
41 | },
42 | {
43 | label: 'Option 2',
44 | value: 2,
45 | },
46 | ]}
47 | />
48 | <Field
49 | name="bb"
50 | title="input box"
51 | required
52 | decorator={[FormItem]}
53 | component={[Input]}
54 | />
55 | <Field
56 | name="cc"
57 | title="input box"
58 | decorator={[FormItem]}
59 | component={[Input]}
60 | />
61 | <Field
62 | name="dd"
63 | title="input box"
64 | decorator={[FormItem]}
65 | component={[Input]}
66 | />
67 | <Field
68 | name="ee"
69 | title="input box"
70 | decorator={[FormItem]}
71 | component={[Input]}
72 | />
73 | <FormButtonGroup.FormItem>
74 | <Submit>Query</Submit>
75 | </FormButtonGroup.FormItem>
76 | </FormGrid>
77 | </Form>
78 | )
79 | ```
80 |
81 | <Alert style="margin-top:20px">
82 | Note: To realize the carriage return submission, we cannot pass the onSubmit event to it when using the Submit component, otherwise the carriage return submission will become invalid. The purpose of this is to prevent users from writing onSubmit event listeners in multiple places at the same time, and processing logic If they are inconsistent, it is difficult to locate the problem when submitting.
83 | </Alert>
84 |
85 | ## API
86 |
87 | For layout-related API properties, we can refer to [FormLayout](./form-layout), and the rest are the unique API properties of the Form component
88 |
89 | | Property name | Type | Description | Default value |
90 | | ---------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ------------- |
91 | | form | [Form](https://core.formilyjs.org/api/models/form) | Form example | - |
92 | | component | string | Rendering component, can be specified as custom component rendering | `form` |
93 | | previewTextPlaceholder | ReactNode | Preview State Placeholder | `N/A` |
94 | | onAutoSubmit | `(values:any)=>any` | Carriage return submit event callback | - |
95 | | onAutoSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Carriage return submission verification failure event callback | - |
96 |
```
--------------------------------------------------------------------------------
/packages/shared/src/compare.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { isArr } from './checkers'
2 | import { instOf } from './instanceof'
3 | const isArray = isArr
4 | const keyList = Object.keys
5 | const hasProp = Object.prototype.hasOwnProperty
6 |
7 | /* eslint-disable */
8 | function equal(a: any, b: any) {
9 | // fast-deep-equal index.js 2.0.1
10 | if (a === b) {
11 | return true
12 | }
13 |
14 | if (a && b && typeof a === 'object' && typeof b === 'object') {
15 | const arrA = isArray(a)
16 | const arrB = isArray(b)
17 | let i: number
18 | let length: number
19 | let key: string | number
20 |
21 | if (arrA && arrB) {
22 | length = a.length
23 | if (length !== b.length) {
24 | return false
25 | }
26 | for (i = length; i-- !== 0; ) {
27 | if (!equal(a[i], b[i])) {
28 | return false
29 | }
30 | }
31 | return true
32 | }
33 |
34 | if (arrA !== arrB) {
35 | return false
36 | }
37 | const momentA = a && a._isAMomentObject
38 | const momentB = b && b._isAMomentObject
39 | if (momentA !== momentB) return false
40 | if (momentA && momentB) return a.isSame(b)
41 | const immutableA = a && a.toJS
42 | const immutableB = b && b.toJS
43 | if (immutableA !== immutableB) return false
44 | if (immutableA) return a.is ? a.is(b) : a === b
45 | const dateA = instOf(a, 'Date')
46 | const dateB = instOf(b, 'Date')
47 | if (dateA !== dateB) {
48 | return false
49 | }
50 | if (dateA && dateB) {
51 | return a.getTime() === b.getTime()
52 | }
53 | const regexpA = instOf(a, 'RegExp')
54 | const regexpB = instOf(b, 'RegExp')
55 | if (regexpA !== regexpB) {
56 | return false
57 | }
58 | if (regexpA && regexpB) {
59 | return a.toString() === b.toString()
60 | }
61 | const urlA = instOf(a, 'URL')
62 | const urlB = instOf(b, 'URL')
63 |
64 | if (urlA !== urlB) {
65 | return false
66 | }
67 |
68 | if (urlA && urlB) {
69 | return a.href === b.href
70 | }
71 |
72 | const schemaA = a && a.toJSON
73 | const schemaB = b && b.toJSON
74 | if (schemaA !== schemaB) return false
75 | if (schemaA && schemaB) return equal(a.toJSON(), b.toJSON())
76 |
77 | const keys = keyList(a)
78 | length = keys.length
79 |
80 | if (length !== keyList(b).length) {
81 | return false
82 | }
83 |
84 | for (i = length; i-- !== 0; ) {
85 | if (!hasProp.call(b, keys[i])) {
86 | return false
87 | }
88 | }
89 | // end fast-deep-equal
90 |
91 | // Custom handling for React
92 | for (i = length; i-- !== 0; ) {
93 | key = keys[i]
94 |
95 | if (key === '_owner' && a.$$typeof) {
96 | // React-specific: avoid traversing React elements' _owner.
97 | // _owner contains circular references
98 | // and is not needed when comparing the actual elements (and not their owners)
99 | // .$$typeof and ._store on just reasonable markers of a react element
100 | continue
101 | } else {
102 | // all other properties should be traversed as usual
103 | if (!equal(a[key], b[key])) {
104 | return false
105 | }
106 | }
107 | }
108 |
109 | // fast-deep-equal index.js 2.0.1
110 | return true
111 | }
112 |
113 | return a !== a && b !== b
114 | }
115 | // end fast-deep-equal
116 |
117 | export const isEqual = function exportedEqual(a: any, b: any) {
118 | try {
119 | return equal(a, b)
120 | } catch (error) {
121 | /* istanbul ignore next */
122 | if (
123 | (error.message && error.message.match(/stack|recursion/i)) ||
124 | error.number === -2146828260
125 | ) {
126 | // warn on circular references, don't crash
127 | // browsers give this different errors name and messages:
128 | // chrome/safari: "RangeError", "Maximum call stack size exceeded"
129 | // firefox: "InternalError", too much recursion"
130 | // edge: "Error", "Out of stack space"
131 | console.warn(
132 | 'Warning: react-fast-compare does not handle circular references.',
133 | error.name,
134 | error.message
135 | )
136 | return false
137 | }
138 | // some other error. we should definitely know about these
139 | /* istanbul ignore next */
140 | throw error
141 | }
142 | }
143 |
```
--------------------------------------------------------------------------------
/packages/antd/src/form-button-group/index.tsx:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * 1. FormItem网格布局
3 | * 2. 居中,居右,居左布局
4 | * 3. 行内布局
5 | * 4. 吸底布局
6 | */
7 | import React, { useRef, useLayoutEffect, useState } from 'react'
8 | import { ReactFC } from '@formily/react'
9 | import { Space } from 'antd'
10 | import { SpaceProps } from 'antd/lib/space'
11 | import { BaseItem, IFormItemProps } from '../form-item'
12 | import { usePrefixCls } from '../__builtins__'
13 | import StickyBox from 'react-sticky-box'
14 | import cls from 'classnames'
15 | interface IStickyProps extends React.ComponentProps<typeof StickyBox> {
16 | align?: React.CSSProperties['textAlign']
17 | }
18 |
19 | type IFormButtonGroupProps = Omit<SpaceProps, 'align' | 'size'> & {
20 | align?: React.CSSProperties['textAlign']
21 | gutter?: number
22 | }
23 |
24 | type ComposedButtonGroup = ReactFC<IFormButtonGroupProps> & {
25 | Sticky: ReactFC<React.PropsWithChildren<IStickyProps>>
26 | FormItem: ReactFC<
27 | IFormItemProps & {
28 | gutter?: number
29 | }
30 | >
31 | }
32 |
33 | function getInheritedBackgroundColor(el: HTMLElement) {
34 | // get default style for current browser
35 | const defaultStyle = getDefaultBackground() // typically "rgba(0, 0, 0, 0)"
36 |
37 | // get computed color for el
38 | const backgroundColor = window.getComputedStyle(el).backgroundColor
39 |
40 | // if we got a real value, return it
41 | if (backgroundColor != defaultStyle) return backgroundColor
42 |
43 | // if we've reached the top parent el without getting an explicit color, return default
44 | if (!el.parentElement) return defaultStyle
45 |
46 | // otherwise, recurse and try again on parent element
47 | return getInheritedBackgroundColor(el.parentElement)
48 | }
49 |
50 | function getDefaultBackground() {
51 | // have to add to the document in order to use getComputedStyle
52 | let div = document.createElement('div')
53 | document.head.appendChild(div)
54 | let bg = window.getComputedStyle(div).backgroundColor
55 | document.head.removeChild(div)
56 | return bg
57 | }
58 |
59 | export const FormButtonGroup: ComposedButtonGroup = ({
60 | align = 'left',
61 | gutter,
62 | ...props
63 | }) => {
64 | const prefixCls = usePrefixCls('formily-button-group')
65 | return (
66 | <Space
67 | {...props}
68 | size={gutter}
69 | className={cls(prefixCls, props.className)}
70 | style={{
71 | ...props.style,
72 | justifyContent:
73 | align === 'left'
74 | ? 'flex-start'
75 | : align === 'right'
76 | ? 'flex-end'
77 | : 'center',
78 | display: 'flex',
79 | }}
80 | >
81 | {props.children}
82 | </Space>
83 | )
84 | }
85 |
86 | FormButtonGroup.FormItem = ({ gutter, ...props }) => {
87 | return (
88 | <BaseItem
89 | {...props}
90 | label=" "
91 | style={{
92 | margin: 0,
93 | padding: 0,
94 | ...props.style,
95 | width: '100%',
96 | }}
97 | colon={false}
98 | >
99 | {props.children?.['length'] ? (
100 | <Space size={gutter}>{props.children}</Space>
101 | ) : (
102 | props.children
103 | )}
104 | </BaseItem>
105 | )
106 | }
107 |
108 | FormButtonGroup.Sticky = ({ align = 'left', ...props }) => {
109 | const ref = useRef()
110 | const [color, setColor] = useState('transparent')
111 | const prefixCls = usePrefixCls('formily-button-group')
112 |
113 | useLayoutEffect(() => {
114 | if (ref.current) {
115 | const computed = getInheritedBackgroundColor(ref.current)
116 | if (computed !== color) {
117 | setColor(computed)
118 | }
119 | }
120 | })
121 | return (
122 | <StickyBox
123 | {...props}
124 | className={cls(`${prefixCls}-sticky`, props.className)}
125 | style={{
126 | backgroundColor: color,
127 | ...props.style,
128 | }}
129 | bottom
130 | >
131 | <div
132 | ref={ref}
133 | className={`${prefixCls}-sticky-inner`}
134 | style={{
135 | ...props.style,
136 | justifyContent:
137 | align === 'left'
138 | ? 'flex-start'
139 | : align === 'right'
140 | ? 'flex-end'
141 | : 'center',
142 | }}
143 | >
144 | {props.children}
145 | </div>
146 | </StickyBox>
147 | )
148 | }
149 |
150 | export default FormButtonGroup
151 |
```
--------------------------------------------------------------------------------
/packages/reactive/src/annotations/computed.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ObModelSymbol, ReactionStack } from '../environment'
2 | import { createAnnotation } from '../internals'
3 | import { buildDataTree } from '../tree'
4 | import { isFn } from '../checkers'
5 | import {
6 | bindTargetKeyWithCurrentReaction,
7 | runReactionsFromTargetKey,
8 | bindComputedReactions,
9 | hasRunningReaction,
10 | isUntracking,
11 | batchStart,
12 | batchEnd,
13 | releaseBindingReactions,
14 | } from '../reaction'
15 |
16 | interface IValue<T = any> {
17 | value?: T
18 | }
19 | export interface IComputed {
20 | <T>(compute: () => T): IValue<T>
21 | <T>(compute: { get?: () => T; set?: (value: T) => void }): IValue<T>
22 | }
23 |
24 | const getDescriptor = Object.getOwnPropertyDescriptor
25 |
26 | const getProto = Object.getPrototypeOf
27 |
28 | const ClassDescriptorSymbol = Symbol('ClassDescriptorSymbol')
29 |
30 | function getPropertyDescriptor(obj: any, key: PropertyKey) {
31 | if (!obj) return
32 | return getDescriptor(obj, key) || getPropertyDescriptor(getProto(obj), key)
33 | }
34 |
35 | function getPropertyDescriptorCache(obj: any, key: PropertyKey) {
36 | const constructor = obj.constructor
37 | if (constructor === Object || constructor === Array)
38 | return getPropertyDescriptor(obj, key)
39 | const cache = constructor[ClassDescriptorSymbol] || {}
40 | const descriptor = cache[key]
41 | if (descriptor) return descriptor
42 | const newDesc = getPropertyDescriptor(obj, key)
43 | constructor[ClassDescriptorSymbol] = cache
44 | cache[key] = newDesc
45 | return newDesc
46 | }
47 |
48 | function getPrototypeDescriptor(
49 | target: any,
50 | key: PropertyKey,
51 | value: any
52 | ): PropertyDescriptor {
53 | if (!target) {
54 | if (value) {
55 | if (isFn(value)) {
56 | return { get: value }
57 | } else {
58 | return value
59 | }
60 | }
61 | return {}
62 | }
63 | const descriptor = getPropertyDescriptorCache(target, key)
64 | if (descriptor) {
65 | return descriptor
66 | }
67 | return {}
68 | }
69 |
70 | export const computed: IComputed = createAnnotation(
71 | ({ target, key, value }) => {
72 | const store: IValue = {}
73 |
74 | const proxy = {}
75 |
76 | const context = target ? target : store
77 | const property = target ? key : 'value'
78 | const descriptor = getPrototypeDescriptor(target, property, value)
79 |
80 | function compute() {
81 | store.value = descriptor.get?.call(context)
82 | }
83 | function reaction() {
84 | if (ReactionStack.indexOf(reaction) === -1) {
85 | releaseBindingReactions(reaction)
86 | try {
87 | ReactionStack.push(reaction)
88 | compute()
89 | } finally {
90 | ReactionStack.pop()
91 | }
92 | }
93 | }
94 | reaction._name = 'ComputedReaction'
95 | reaction._scheduler = () => {
96 | reaction._dirty = true
97 | runReactionsFromTargetKey({
98 | target: context,
99 | key: property,
100 | value: store.value,
101 | type: 'set',
102 | })
103 | }
104 | reaction._isComputed = true
105 | reaction._dirty = true
106 | reaction._context = context
107 | reaction._property = property
108 |
109 | function get() {
110 | if (hasRunningReaction()) {
111 | bindComputedReactions(reaction)
112 | }
113 | if (!isUntracking()) {
114 | //如果允许untracked过程中收集依赖,那么永远不会存在绑定,因为_dirty已经设置为false
115 | if (reaction._dirty) {
116 | reaction()
117 | reaction._dirty = false
118 | }
119 | } else {
120 | compute()
121 | }
122 | bindTargetKeyWithCurrentReaction({
123 | target: context,
124 | key: property,
125 | type: 'get',
126 | })
127 | return store.value
128 | }
129 |
130 | function set(value: any) {
131 | try {
132 | batchStart()
133 | descriptor.set?.call(context, value)
134 | } finally {
135 | batchEnd()
136 | }
137 | }
138 | if (target) {
139 | Object.defineProperty(target, key, {
140 | get,
141 | set,
142 | enumerable: true,
143 | })
144 | return target
145 | } else {
146 | Object.defineProperty(proxy, 'value', {
147 | set,
148 | get,
149 | })
150 | buildDataTree(target, key, store)
151 | proxy[ObModelSymbol] = store
152 | }
153 | return proxy
154 | }
155 | )
156 |
```
--------------------------------------------------------------------------------
/packages/react/docs/api/components/RecursionField.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | order: 5
3 | ---
4 |
5 | # RecursionField
6 |
7 | ## Description
8 |
9 | The recursive rendering component is mainly based on [JSON-Schema](/api/shared/schema) for recursive rendering. It is the core rendering component inside the [SchemaField](/api/components/schema-field) component. Of course, it can It is used separately from SchemaField. When we use it, it is mainly used in custom components to implement custom components with recursive rendering capabilities.
10 |
11 | ## Signature
12 |
13 | ```ts
14 | interface IRecursionFieldProps {
15 | schema: ISchema //Field schema
16 | name?: string //Path name
17 | basePath?: FormPathPattern //base path
18 | propsRecursion?: boolean //Whether to recursiveliy pass mapProperties and filterProperties
19 | onlyRenderProperties?: boolean //Whether to only render properties
20 | onlyRenderSelf?: boolean //Whether to only render itself without rendering properties
21 | mapProperties?: (schema: Schema, name: string) => Schema //schema properties mapper, mainly used to rewrite the schema
22 | filterProperties?: (schema: Schema, name: string) => boolean //schema properties filter, the filtered schema nodes will not be rendered
23 | }
24 |
25 | type RecursionField = React.FC<React.PropsWithChildren<IRecursionFieldProps>>
26 | ```
27 |
28 | ## Example
29 |
30 | ### Simple recursion
31 |
32 | ```tsx
33 | import React from 'react'
34 | import { createForm } from '@formily/core'
35 | import { FormProvider, createSchemaField, RecursionField } from '@formily/react'
36 | import { Input } from 'antd'
37 |
38 | const form = createForm()
39 |
40 | const Custom = (props) => {
41 | return <RecursionField schema={props.schema} onlyRenderProperties />
42 | }
43 |
44 | const SchemaField = createSchemaField({
45 | components: {
46 | Custom,
47 | Input,
48 | },
49 | })
50 |
51 | export default () => (
52 | <FormProvider form={form}>
53 | <SchemaField>
54 | <SchemaField.Object
55 | name="custom"
56 | x-component="Custom"
57 | x-component-props={{
58 | schema: {
59 | type: 'object',
60 | properties: {
61 | input: {
62 | type: 'string',
63 | 'x-component': 'Input',
64 | },
65 | },
66 | },
67 | }}
68 | />
69 | </SchemaField>
70 | </FormProvider>
71 | )
72 | ```
73 |
74 | We can read independent schema objects from component properties and pass them to RecursionField for rendering
75 |
76 | ### Incremental list recursion
77 |
78 | ```tsx
79 | import React from 'react'
80 | import { createForm } from '@formily/core'
81 | import {
82 | FormProvider,
83 | createSchemaField,
84 | RecursionField,
85 | useField,
86 | useFieldSchema,
87 | observer,
88 | } from '@formily/react'
89 | import { Input, Space, Button } from 'antd'
90 |
91 | const form = createForm()
92 |
93 | const ArrayItems = observer((props) => {
94 | const field = useField()
95 | const schema = useFieldSchema()
96 | return (
97 | <div>
98 | {props.value?.map((item, index) => {
99 | return (
100 | <div key={index} style={{ marginBottom: 10 }}>
101 | <Space>
102 | <RecursionField schema={schema.items} name={index} />
103 | <Button
104 | onClick={() => {
105 | field.remove(index)
106 | }}
107 | >
108 | Remove
109 | </Button>
110 | </Space>
111 | </div>
112 | )
113 | })}
114 | <Button
115 | onClick={() => {
116 | field.push({})
117 | }}
118 | >
119 | Add
120 | </Button>
121 | </div>
122 | )
123 | })
124 |
125 | const SchemaField = createSchemaField({
126 | components: {
127 | ArrayItems,
128 | Input,
129 | },
130 | })
131 |
132 | export default () => (
133 | <FormProvider form={form}>
134 | <SchemaField>
135 | <SchemaField.Array name="custom" x-component="ArrayItems">
136 | <SchemaField.Object>
137 | <SchemaField.String name="input" x-component="Input" />
138 | </SchemaField.Object>
139 | </SchemaField.Array>
140 | </SchemaField>
141 | </FormProvider>
142 | )
143 | ```
144 |
145 | Use [useField](/api/hooks/useField) and [useFieldSchema](/api/shared/use-field-schema) to get the field instance and field schema in the current field context
146 |
```