This is page 33 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/element/src/form-item/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isVoidField } from '@formily/core' 2 | import { connect, h, mapProps } from '@formily/vue' 3 | import { Tooltip } from 'element-ui' 4 | import ResizeObserver from 'resize-observer-polyfill' 5 | import { Component } from 'vue' 6 | import { 7 | defineComponent, 8 | onBeforeUnmount, 9 | provide, 10 | ref, 11 | Ref, 12 | watch, 13 | } from 'vue-demi' 14 | import { FormLayoutShallowContext, useFormLayout } from '../form-layout' 15 | import { stylePrefix } from '../__builtins__/configs' 16 | import { 17 | composeExport, 18 | resolveComponent, 19 | useCompatRef, 20 | } from '../__builtins__/shared' 21 | 22 | export type FormItemProps = { 23 | className?: string 24 | required?: boolean 25 | label?: string | Component 26 | colon?: boolean 27 | tooltip?: string | Component 28 | layout?: 'vertical' | 'horizontal' | 'inline' 29 | labelStyle?: Record<string, any> 30 | labelAlign?: 'left' | 'right' 31 | labelWrap?: boolean 32 | labelWidth?: number 33 | wrapperWidth?: number 34 | labelCol?: number 35 | wrapperCol?: number 36 | wrapperAlign?: 'left' | 'right' 37 | wrapperWrap?: boolean 38 | wrapperStyle?: Record<string, any> 39 | fullness?: boolean 40 | addonBefore?: string | Component 41 | addonAfter?: string | Component 42 | size?: 'small' | 'default' | 'large' 43 | extra?: string 44 | feedbackText?: string | Component 45 | feedbackLayout?: 'loose' | 'terse' | 'popover' | 'none' | (string & {}) 46 | feedbackStatus?: 'error' | 'warning' | 'success' | 'pending' | (string & {}) 47 | tooltipLayout?: 'icon' | 'text' 48 | feedbackIcon?: string | Component 49 | asterisk?: boolean 50 | gridSpan?: number 51 | bordered?: boolean 52 | inset?: boolean 53 | } 54 | 55 | const useOverflow = (containerRef: Ref<HTMLElement>) => { 56 | const overflow = ref(false) 57 | let resizeObserver: ResizeObserver | undefined 58 | 59 | const cleanup = () => { 60 | if (resizeObserver) { 61 | resizeObserver.unobserve(containerRef.value) 62 | resizeObserver = null 63 | } 64 | } 65 | 66 | const observer = () => { 67 | const container = containerRef.value 68 | const content = container.querySelector('label') 69 | const containerWidth = container.getBoundingClientRect().width 70 | const contentWidth = content?.getBoundingClientRect().width 71 | 72 | if (containerWidth !== 0) { 73 | if (contentWidth > containerWidth) { 74 | overflow.value = true 75 | } else { 76 | overflow.value = false 77 | } 78 | } 79 | } 80 | 81 | const stopWatch = watch( 82 | () => containerRef.value, 83 | (el) => { 84 | cleanup() 85 | 86 | if (el) { 87 | resizeObserver = new ResizeObserver(observer) 88 | resizeObserver.observe(el) 89 | } 90 | }, 91 | { immediate: true, flush: 'post' } 92 | ) 93 | 94 | onBeforeUnmount(() => { 95 | cleanup() 96 | stopWatch() 97 | }) 98 | 99 | return overflow 100 | } 101 | 102 | const ICON_MAP = { 103 | error: () => h('i', { class: 'el-icon-circle-close' }, {}), 104 | success: () => h('i', { class: 'el-icon-circle-check' }, {}), 105 | warning: () => h('i', { class: 'el-icon-warning-outline' }, {}), 106 | } 107 | 108 | export const FormBaseItem = defineComponent<FormItemProps>({ 109 | name: 'FormItem', 110 | props: { 111 | className: {}, 112 | required: {}, 113 | label: {}, 114 | colon: {}, 115 | layout: {}, 116 | tooltip: {}, 117 | labelStyle: {}, 118 | labelAlign: {}, 119 | labelWrap: {}, 120 | labelWidth: {}, 121 | wrapperWidth: {}, 122 | labelCol: {}, 123 | wrapperCol: {}, 124 | wrapperAlign: {}, 125 | wrapperWrap: {}, 126 | wrapperStyle: {}, 127 | fullness: {}, 128 | addonBefore: {}, 129 | addonAfter: {}, 130 | size: {}, 131 | extra: {}, 132 | feedbackText: {}, 133 | feedbackLayout: {}, 134 | tooltipLayout: {}, 135 | feedbackStatus: {}, 136 | feedbackIcon: {}, 137 | asterisk: {}, 138 | gridSpan: {}, 139 | bordered: { default: true }, 140 | inset: { default: false }, 141 | }, 142 | setup(props, { slots, refs }) { 143 | const active = ref(false) 144 | const deepLayoutRef = useFormLayout() 145 | 146 | const prefixCls = `${stylePrefix}-form-item` 147 | 148 | const { elRef: containerRef, elRefBinder } = useCompatRef(refs) 149 | const overflow = useOverflow(containerRef) 150 | 151 | provide(FormLayoutShallowContext, ref(null)) 152 | 153 | return () => { 154 | const gridStyles: Record<string, any> = {} 155 | 156 | const deepLayout = deepLayoutRef.value 157 | const { 158 | label, 159 | colon = deepLayout.colon ?? true, 160 | layout = deepLayout.layout ?? 'horizontal', 161 | tooltip, 162 | labelStyle = {}, 163 | labelWrap = deepLayout.labelWrap ?? false, 164 | labelWidth = deepLayout.labelWidth, 165 | wrapperWidth = deepLayout.wrapperWidth, 166 | labelCol = deepLayout.labelCol, 167 | wrapperCol = deepLayout.wrapperCol, 168 | wrapperAlign = deepLayout.wrapperAlign ?? 'left', 169 | wrapperWrap = deepLayout.wrapperWrap, 170 | wrapperStyle = {}, 171 | fullness = deepLayout.fullness, 172 | addonBefore, 173 | addonAfter, 174 | size = deepLayout.size, 175 | extra, 176 | feedbackText, 177 | feedbackLayout = deepLayout.feedbackLayout ?? 'loose', 178 | tooltipLayout = deepLayout.tooltipLayout ?? 'icon', 179 | feedbackStatus, 180 | feedbackIcon, 181 | asterisk, 182 | bordered = deepLayout.bordered, 183 | inset = deepLayout.inset, 184 | } = props 185 | const labelAlign = 186 | deepLayout.layout === 'vertical' 187 | ? props.labelAlign ?? deepLayout.labelAlign ?? 'left' 188 | : props.labelAlign ?? deepLayout.labelAlign ?? 'right' 189 | 190 | // 固定宽度 191 | let enableCol = false 192 | if (labelWidth || wrapperWidth) { 193 | if (labelWidth) { 194 | labelStyle.width = `${labelWidth}px` 195 | labelStyle.maxWidth = `${labelWidth}px` 196 | } 197 | if (wrapperWidth) { 198 | wrapperStyle.width = `${wrapperWidth}px` 199 | wrapperStyle.maxWidth = `${wrapperWidth}px` 200 | } 201 | // 栅格模式 202 | } else if (labelCol || wrapperCol) { 203 | enableCol = true 204 | } 205 | 206 | const formatChildren = 207 | feedbackLayout === 'popover' 208 | ? h( 209 | 'el-popover', 210 | { 211 | props: { 212 | disabled: !feedbackText, 213 | placement: 'top', 214 | }, 215 | }, 216 | { 217 | reference: () => 218 | h('div', {}, { default: () => slots.default?.() }), 219 | default: () => [ 220 | h( 221 | 'div', 222 | { 223 | class: { 224 | [`${prefixCls}-${feedbackStatus}-help`]: 225 | !!feedbackStatus, 226 | [`${prefixCls}-help`]: true, 227 | }, 228 | }, 229 | { 230 | default: () => [ 231 | feedbackStatus && 232 | ['error', 'success', 'warning'].includes(feedbackStatus) 233 | ? ICON_MAP[ 234 | feedbackStatus as 'error' | 'success' | 'warning' 235 | ]() 236 | : '', 237 | resolveComponent(feedbackText), 238 | ], 239 | } 240 | ), 241 | ], 242 | } 243 | ) 244 | : slots.default?.() 245 | 246 | const renderLabelText = () => { 247 | const labelChildren = h( 248 | 'div', 249 | { 250 | class: `${prefixCls}-label-content`, 251 | ref: elRefBinder, 252 | }, 253 | { 254 | default: () => [ 255 | asterisk && 256 | h( 257 | 'span', 258 | { class: `${prefixCls}-asterisk` }, 259 | { default: () => ['*'] } 260 | ), 261 | h('label', {}, { default: () => [resolveComponent(label)] }), 262 | ], 263 | } 264 | ) 265 | const isTextTooltip = tooltip && tooltipLayout === 'text' 266 | if (isTextTooltip || overflow.value) { 267 | return h( 268 | Tooltip, 269 | { 270 | props: { 271 | placement: 'top', 272 | }, 273 | }, 274 | { 275 | default: () => [labelChildren], 276 | content: () => 277 | h( 278 | 'div', 279 | {}, 280 | { 281 | default: () => [ 282 | overflow.value && resolveComponent(label), 283 | isTextTooltip && resolveComponent(tooltip), 284 | ], 285 | } 286 | ), 287 | } 288 | ) 289 | } else { 290 | return labelChildren 291 | } 292 | } 293 | const renderTooltipIcon = () => { 294 | if (tooltip && tooltipLayout === 'icon') { 295 | return h( 296 | 'span', 297 | { 298 | class: `${prefixCls}-label-tooltip`, 299 | }, 300 | { 301 | default: () => [ 302 | h( 303 | Tooltip, 304 | { 305 | props: { 306 | placement: 'top', 307 | }, 308 | }, 309 | { 310 | default: () => [h('i', { class: 'el-icon-info' }, {})], 311 | content: () => 312 | h( 313 | 'div', 314 | { 315 | class: `${prefixCls}-label-tooltip-content`, 316 | }, 317 | { 318 | default: () => [resolveComponent(tooltip)], 319 | } 320 | ), 321 | } 322 | ), 323 | ], 324 | } 325 | ) 326 | } 327 | } 328 | const renderLabel = 329 | label && 330 | h( 331 | 'div', 332 | { 333 | class: { 334 | [`${prefixCls}-label`]: true, 335 | [`${prefixCls}-label-tooltip`]: 336 | (tooltip && tooltipLayout === 'text') || overflow.value, 337 | [`${prefixCls}-item-col-${labelCol}`]: enableCol && !!labelCol, 338 | }, 339 | style: labelStyle, 340 | }, 341 | { 342 | default: () => [ 343 | // label content 344 | renderLabelText(), 345 | // label tooltip 346 | renderTooltipIcon(), 347 | // label colon 348 | label && 349 | h( 350 | 'span', 351 | { 352 | class: `${prefixCls}-colon`, 353 | }, 354 | { default: () => [colon ? ':' : ''] } 355 | ), 356 | ], 357 | } 358 | ) 359 | 360 | const renderFeedback = 361 | !!feedbackText && 362 | feedbackLayout !== 'popover' && 363 | feedbackLayout !== 'none' && 364 | h( 365 | 'div', 366 | { 367 | class: { 368 | [`${prefixCls}-${feedbackStatus}-help`]: !!feedbackStatus, 369 | [`${prefixCls}-help`]: true, 370 | [`${prefixCls}-help-enter`]: true, 371 | [`${prefixCls}-help-enter-active`]: true, 372 | }, 373 | }, 374 | { default: () => [resolveComponent(feedbackText)] } 375 | ) 376 | 377 | const renderExtra = 378 | extra && 379 | h( 380 | 'div', 381 | { class: `${prefixCls}-extra` }, 382 | { default: () => [resolveComponent(extra)] } 383 | ) 384 | const renderContent = h( 385 | 'div', 386 | { 387 | class: { 388 | [`${prefixCls}-control`]: true, 389 | [`${prefixCls}-item-col-${wrapperCol}`]: enableCol && !!wrapperCol, 390 | }, 391 | }, 392 | { 393 | default: () => [ 394 | h( 395 | 'div', 396 | { class: `${prefixCls}-control-content` }, 397 | { 398 | default: () => [ 399 | addonBefore && 400 | h( 401 | 'div', 402 | { class: `${prefixCls}-addon-before` }, 403 | { 404 | default: () => [resolveComponent(addonBefore)], 405 | } 406 | ), 407 | h( 408 | 'div', 409 | { 410 | class: { 411 | [`${prefixCls}-control-content-component`]: true, 412 | [`${prefixCls}-control-content-component-has-feedback-icon`]: 413 | !!feedbackIcon, 414 | }, 415 | style: wrapperStyle, 416 | }, 417 | { 418 | default: () => [ 419 | formatChildren, 420 | feedbackIcon && 421 | h( 422 | 'div', 423 | { class: `${prefixCls}-feedback-icon` }, 424 | { 425 | default: () => [ 426 | typeof feedbackIcon === 'string' 427 | ? h('i', { class: feedbackIcon }, {}) 428 | : resolveComponent(feedbackIcon), 429 | ], 430 | } 431 | ), 432 | ], 433 | } 434 | ), 435 | addonAfter && 436 | h( 437 | 'div', 438 | { class: `${prefixCls}-addon-after` }, 439 | { 440 | default: () => [resolveComponent(addonAfter)], 441 | } 442 | ), 443 | ], 444 | } 445 | ), 446 | renderFeedback, 447 | renderExtra, 448 | ], 449 | } 450 | ) 451 | return h( 452 | 'div', 453 | { 454 | style: { 455 | ...gridStyles, 456 | }, 457 | attrs: { 458 | 'data-grid-span': props.gridSpan, 459 | }, 460 | class: { 461 | [`${prefixCls}`]: true, 462 | [`${prefixCls}-layout-${layout}`]: true, 463 | [`${prefixCls}-${feedbackStatus}`]: !!feedbackStatus, 464 | [`${prefixCls}-feedback-has-text`]: !!feedbackText, 465 | [`${prefixCls}-size-${size}`]: !!size, 466 | [`${prefixCls}-feedback-layout-${feedbackLayout}`]: 467 | !!feedbackLayout, 468 | [`${prefixCls}-fullness`]: !!fullness || !!inset || !!feedbackIcon, 469 | [`${prefixCls}-inset`]: !!inset, 470 | [`${prefixCls}-active`]: active.value, 471 | [`${prefixCls}-inset-active`]: !!inset && active.value, 472 | [`${prefixCls}-label-align-${labelAlign}`]: true, 473 | [`${prefixCls}-control-align-${wrapperAlign}`]: true, 474 | [`${prefixCls}-label-wrap`]: !!labelWrap, 475 | [`${prefixCls}-control-wrap`]: !!wrapperWrap, 476 | [`${prefixCls}-bordered-none`]: 477 | bordered === false || !!inset || !!feedbackIcon, 478 | [`${props.className}`]: !!props.className, 479 | }, 480 | on: { 481 | focus: () => { 482 | if (feedbackIcon || inset) { 483 | active.value = true 484 | } 485 | }, 486 | blur: () => { 487 | if (feedbackIcon || inset) { 488 | active.value = false 489 | } 490 | }, 491 | }, 492 | }, 493 | { 494 | default: () => [renderLabel, renderContent], 495 | } 496 | ) 497 | } 498 | }, 499 | }) 500 | 501 | const Item = connect( 502 | FormBaseItem, 503 | mapProps( 504 | { validateStatus: true, title: 'label', required: true }, 505 | (props, field) => { 506 | if (isVoidField(field)) return props 507 | if (!field) return props 508 | const takeMessage = () => { 509 | if (field.validating) return 510 | if (props.feedbackText) return props.feedbackText 511 | if (field.selfErrors.length) return field.selfErrors 512 | if (field.selfWarnings.length) return field.selfWarnings 513 | if (field.selfSuccesses.length) return field.selfSuccesses 514 | } 515 | const errorMessages = takeMessage() 516 | return { 517 | feedbackText: Array.isArray(errorMessages) 518 | ? errorMessages.join(', ') 519 | : errorMessages, 520 | extra: props.extra || field.description, 521 | } 522 | }, 523 | (props, field) => { 524 | if (isVoidField(field)) return props 525 | if (!field) return props 526 | return { 527 | feedbackStatus: 528 | field.validateStatus === 'validating' 529 | ? 'pending' 530 | : (Array.isArray(field.decorator) && 531 | field.decorator[1]?.feedbackStatus) || 532 | field.validateStatus, 533 | } 534 | }, 535 | (props, field) => { 536 | if (isVoidField(field)) return props 537 | 538 | if (!field) return props 539 | let asterisk = false 540 | if (field.required && field.pattern !== 'readPretty') { 541 | asterisk = true 542 | } 543 | if ('asterisk' in props) { 544 | asterisk = props.asterisk 545 | } 546 | return { 547 | asterisk, 548 | } 549 | } 550 | ) 551 | ) 552 | 553 | export const FormItem = composeExport(Item, { 554 | BaseItem: FormBaseItem, 555 | }) 556 | 557 | export default FormItem 558 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/ArrayCollapse.md: -------------------------------------------------------------------------------- ```markdown 1 | # ArrayCollapse 2 | 3 | > Folding panel, it is more suitable to use ArrayCollapse for scenes with more fields in each row and more linkage 4 | > 5 | > Note: This component is only applicable to Schema scenarios 6 | 7 | ## Markup Schema example 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | FormItem, 13 | Input, 14 | ArrayCollapse, 15 | FormButtonGroup, 16 | Submit, 17 | } from '@formily/next' 18 | import { createForm } from '@formily/core' 19 | import { FormProvider, createSchemaField } from '@formily/react' 20 | import { Button } from '@alifd/next' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | FormItem, 25 | Input, 26 | ArrayCollapse, 27 | }, 28 | }) 29 | 30 | const form = createForm() 31 | 32 | export default () => { 33 | return ( 34 | <FormProvider form={form}> 35 | <SchemaField> 36 | <SchemaField.Array 37 | name="string_array" 38 | maxItems={3} 39 | x-decorator="FormItem" 40 | x-component="ArrayCollapse" 41 | x-component-props={{ 42 | accordion: true, 43 | defaultOpenPanelCount: 3, 44 | }} 45 | > 46 | <SchemaField.Void 47 | x-component="ArrayCollapse.CollapsePanel" 48 | x-component-props={{ 49 | title: 'String array', 50 | }} 51 | > 52 | <SchemaField.Void x-component="ArrayCollapse.Index" /> 53 | <SchemaField.String 54 | name="input" 55 | x-decorator="FormItem" 56 | title="Input" 57 | required 58 | x-component="Input" 59 | /> 60 | <SchemaField.Void x-component="ArrayCollapse.Remove" /> 61 | <SchemaField.Void x-component="ArrayCollapse.MoveUp" /> 62 | <SchemaField.Void x-component="ArrayCollapse.MoveDown" /> 63 | </SchemaField.Void> 64 | <SchemaField.Void 65 | x-component="ArrayCollapse.Addition" 66 | title="Add entry" 67 | /> 68 | </SchemaField.Array> 69 | <SchemaField.Array 70 | name="array" 71 | maxItems={3} 72 | x-decorator="FormItem" 73 | x-component="ArrayCollapse" 74 | > 75 | <SchemaField.Object 76 | x-component="ArrayCollapse.CollapsePanel" 77 | x-component-props={{ 78 | title: 'Object array', 79 | }} 80 | > 81 | <SchemaField.Void x-component="ArrayCollapse.Index" /> 82 | <SchemaField.String 83 | name="input" 84 | x-decorator="FormItem" 85 | title="Input" 86 | required 87 | x-component="Input" 88 | /> 89 | <SchemaField.Void x-component="ArrayCollapse.Remove" /> 90 | <SchemaField.Void x-component="ArrayCollapse.MoveUp" /> 91 | <SchemaField.Void x-component="ArrayCollapse.MoveDown" /> 92 | </SchemaField.Object> 93 | <SchemaField.Void 94 | x-component="ArrayCollapse.Addition" 95 | title="Add entry" 96 | /> 97 | </SchemaField.Array> 98 | <SchemaField.Array 99 | name="string_array_unshift" 100 | maxItems={3} 101 | x-decorator="FormItem" 102 | x-component="ArrayCollapse" 103 | x-component-props={{ 104 | defaultOpenPanelCount: 8, 105 | }} 106 | > 107 | <SchemaField.Void 108 | x-component="ArrayCollapse.CollapsePanel" 109 | x-component-props={{ 110 | title: 'String array', 111 | }} 112 | > 113 | <SchemaField.Void x-component="ArrayCollapse.Index" /> 114 | <SchemaField.String 115 | name="input" 116 | x-decorator="FormItem" 117 | title="Input" 118 | required 119 | x-component="Input" 120 | /> 121 | <SchemaField.Void x-component="ArrayCollapse.Remove" /> 122 | <SchemaField.Void x-component="ArrayCollapse.MoveUp" /> 123 | <SchemaField.Void x-component="ArrayCollapse.MoveDown" /> 124 | </SchemaField.Void> 125 | <SchemaField.Void 126 | x-component="ArrayCollapse.Addition" 127 | title="Add entry (unshift)" 128 | x-component-props={{ 129 | method: 'unshift', 130 | }} 131 | /> 132 | </SchemaField.Array> 133 | </SchemaField> 134 | <FormButtonGroup> 135 | <Button 136 | onClick={() => { 137 | form.setInitialValues({ 138 | array: Array.from({ length: 10 }).map(() => ({ 139 | input: 'default value', 140 | })), 141 | string_array: Array.from({ length: 10 }).map( 142 | () => 'default value' 143 | ), 144 | string_array_unshift: Array.from({ length: 10 }).map( 145 | () => 'default value' 146 | ), 147 | }) 148 | }} 149 | > 150 | Load default data 151 | </Button> 152 | <Submit onSubmit={console.log}>Submit</Submit> 153 | </FormButtonGroup> 154 | </FormProvider> 155 | ) 156 | } 157 | ``` 158 | 159 | ## JSON Schema case 160 | 161 | ```tsx 162 | import React from 'react' 163 | import { 164 | FormItem, 165 | Input, 166 | ArrayCollapse, 167 | FormButtonGroup, 168 | Submit, 169 | } from '@formily/next' 170 | import { createForm } from '@formily/core' 171 | import { FormProvider, createSchemaField } from '@formily/react' 172 | 173 | const SchemaField = createSchemaField({ 174 | components: { 175 | FormItem, 176 | Input, 177 | ArrayCollapse, 178 | }, 179 | }) 180 | 181 | const form = createForm() 182 | 183 | const schema = { 184 | type: 'object', 185 | properties: { 186 | string_array: { 187 | type: 'array', 188 | 'x-component': 'ArrayCollapse', 189 | maxItems: 3, 190 | 'x-decorator': 'FormItem', 191 | items: { 192 | type: 'void', 193 | 'x-component': 'ArrayCollapse.CollapsePanel', 194 | 'x-component-props': { 195 | title: 'String array', 196 | }, 197 | properties: { 198 | index: { 199 | type: 'void', 200 | 'x-component': 'ArrayCollapse.Index', 201 | }, 202 | input: { 203 | type: 'string', 204 | 'x-decorator': 'FormItem', 205 | title: 'Input', 206 | required: true, 207 | 'x-component': 'Input', 208 | }, 209 | remove: { 210 | type: 'void', 211 | 'x-component': 'ArrayCollapse.Remove', 212 | }, 213 | moveUp: { 214 | type: 'void', 215 | 'x-component': 'ArrayCollapse.MoveUp', 216 | }, 217 | moveDown: { 218 | type: 'void', 219 | 'x-component': 'ArrayCollapse.MoveDown', 220 | }, 221 | }, 222 | }, 223 | properties: { 224 | addition: { 225 | type: 'void', 226 | title: 'Add entry', 227 | 'x-component': 'ArrayCollapse.Addition', 228 | }, 229 | }, 230 | }, 231 | array: { 232 | type: 'array', 233 | 'x-component': 'ArrayCollapse', 234 | maxItems: 3, 235 | 'x-decorator': 'FormItem', 236 | items: { 237 | type: 'object', 238 | 'x-component': 'ArrayCollapse.CollapsePanel', 239 | 'x-component-props': { 240 | title: 'Object array', 241 | }, 242 | properties: { 243 | index: { 244 | type: 'void', 245 | 'x-component': 'ArrayCollapse.Index', 246 | }, 247 | input: { 248 | type: 'string', 249 | 'x-decorator': 'FormItem', 250 | title: 'Input', 251 | required: true, 252 | 'x-component': 'Input', 253 | }, 254 | remove: { 255 | type: 'void', 256 | 'x-component': 'ArrayCollapse.Remove', 257 | }, 258 | moveUp: { 259 | type: 'void', 260 | 'x-component': 'ArrayCollapse.MoveUp', 261 | }, 262 | moveDown: { 263 | type: 'void', 264 | 'x-component': 'ArrayCollapse.MoveDown', 265 | }, 266 | }, 267 | }, 268 | properties: { 269 | addition: { 270 | type: 'void', 271 | title: 'Add entry', 272 | 'x-component': 'ArrayCollapse.Addition', 273 | }, 274 | }, 275 | }, 276 | array_unshift: { 277 | type: 'array', 278 | 'x-component': 'ArrayCollapse', 279 | maxItems: 3, 280 | 'x-decorator': 'FormItem', 281 | items: { 282 | type: 'object', 283 | 'x-component': 'ArrayCollapse.CollapsePanel', 284 | 'x-component-props': { 285 | title: 'Object array', 286 | }, 287 | properties: { 288 | index: { 289 | type: 'void', 290 | 'x-component': 'ArrayCollapse.Index', 291 | }, 292 | input: { 293 | type: 'string', 294 | 'x-decorator': 'FormItem', 295 | title: 'Input', 296 | required: true, 297 | 'x-component': 'Input', 298 | }, 299 | remove: { 300 | type: 'void', 301 | 'x-component': 'ArrayCollapse.Remove', 302 | }, 303 | moveUp: { 304 | type: 'void', 305 | 'x-component': 'ArrayCollapse.MoveUp', 306 | }, 307 | moveDown: { 308 | type: 'void', 309 | 'x-component': 'ArrayCollapse.MoveDown', 310 | }, 311 | }, 312 | }, 313 | properties: { 314 | addition: { 315 | type: 'void', 316 | title: 'Add entry (unshift)', 317 | 'x-component': 'ArrayCollapse.Addition', 318 | 'x-component-props': { 319 | method: 'unshift', 320 | }, 321 | }, 322 | }, 323 | }, 324 | }, 325 | } 326 | 327 | export default () => { 328 | return ( 329 | <FormProvider form={form}> 330 | <SchemaField schema={schema} /> 331 | <FormButtonGroup> 332 | <Submit onSubmit={console.log}>Submit</Submit> 333 | </FormButtonGroup> 334 | </FormProvider> 335 | ) 336 | } 337 | ``` 338 | 339 | ## Effects linkage case 340 | 341 | ```tsx 342 | import React from 'react' 343 | import { 344 | FormItem, 345 | Input, 346 | ArrayCollapse, 347 | FormButtonGroup, 348 | Submit, 349 | } from '@formily/next' 350 | import { createForm, onFieldChange, onFieldReact } from '@formily/core' 351 | import { FormProvider, createSchemaField } from '@formily/react' 352 | 353 | const SchemaField = createSchemaField({ 354 | components: { 355 | FormItem, 356 | Input, 357 | ArrayCollapse, 358 | }, 359 | }) 360 | 361 | const form = createForm({ 362 | effects: () => { 363 | //Active linkage mode 364 | onFieldChange('array.*.aa', ['value'], (field, form) => { 365 | form.setFieldState(field.query('.bb'), (state) => { 366 | state.visible = field.value != '123' 367 | }) 368 | }) 369 | //Passive linkage mode 370 | onFieldReact('array.*.dd', (field) => { 371 | field.visible = field.query('.cc').get('value') != '123' 372 | }) 373 | }, 374 | }) 375 | 376 | export default () => { 377 | return ( 378 | <FormProvider form={form}> 379 | <SchemaField> 380 | <SchemaField.Array 381 | name="array" 382 | maxItems={3} 383 | x-component="ArrayCollapse" 384 | x-decorator="FormItem" 385 | x-component-props={{ 386 | title: 'Object array', 387 | }} 388 | > 389 | <SchemaField.Object 390 | x-component="ArrayCollapse.CollapsePanel" 391 | x-component-props={{ 392 | title: 'Object array', 393 | }} 394 | > 395 | <SchemaField.Void x-component="ArrayCollapse.Index" /> 396 | <SchemaField.String 397 | name="aa" 398 | x-decorator="FormItem" 399 | title="AA" 400 | required 401 | description="AA hide BB when entering 123" 402 | x-component="Input" 403 | /> 404 | <SchemaField.String 405 | name="bb" 406 | x-decorator="FormItem" 407 | title="BB" 408 | required 409 | x-component="Input" 410 | /> 411 | <SchemaField.String 412 | name="cc" 413 | x-decorator="FormItem" 414 | title="CC" 415 | required 416 | description="Hide DD when CC enters 123" 417 | x-component="Input" 418 | /> 419 | <SchemaField.String 420 | name="dd" 421 | x-decorator="FormItem" 422 | title="DD" 423 | required 424 | x-component="Input" 425 | /> 426 | <SchemaField.Void x-component="ArrayCollapse.Remove" /> 427 | <SchemaField.Void x-component="ArrayCollapse.MoveUp" /> 428 | <SchemaField.Void x-component="ArrayCollapse.MoveDown" /> 429 | </SchemaField.Object> 430 | <SchemaField.Void 431 | x-component="ArrayCollapse.Addition" 432 | title="Add entry" 433 | /> 434 | </SchemaField.Array> 435 | </SchemaField> 436 | <FormButtonGroup> 437 | <Submit onSubmit={console.log}>Submit</Submit> 438 | </FormButtonGroup> 439 | </FormProvider> 440 | ) 441 | } 442 | ``` 443 | 444 | ## JSON Schema linkage case 445 | 446 | ```tsx 447 | import React from 'react' 448 | import { 449 | FormItem, 450 | Input, 451 | ArrayCollapse, 452 | FormButtonGroup, 453 | Submit, 454 | } from '@formily/next' 455 | import { createForm } from '@formily/core' 456 | import { FormProvider, createSchemaField } from '@formily/react' 457 | 458 | const SchemaField = createSchemaField({ 459 | components: { 460 | FormItem, 461 | Input, 462 | ArrayCollapse, 463 | }, 464 | }) 465 | 466 | const form = createForm() 467 | 468 | const schema = { 469 | type: 'object', 470 | properties: { 471 | array: { 472 | type: 'array', 473 | 'x-component': 'ArrayCollapse', 474 | maxItems: 3, 475 | title: 'Object array', 476 | items: { 477 | type: 'object', 478 | 'x-component': 'ArrayCollapse.CollapsePanel', 479 | 'x-component-props': { 480 | title: 'Object array', 481 | }, 482 | properties: { 483 | index: { 484 | type: 'void', 485 | 'x-component': 'ArrayCollapse.Index', 486 | }, 487 | aa: { 488 | type: 'string', 489 | 'x-decorator': 'FormItem', 490 | title: 'AA', 491 | required: true, 492 | 'x-component': 'Input', 493 | description: 'Enter 123', 494 | }, 495 | bb: { 496 | type: 'string', 497 | title: 'BB', 498 | required: true, 499 | 'x-decorator': 'FormItem', 500 | 'x-component': 'Input', 501 | 'x-reactions': [ 502 | { 503 | dependencies: ['.aa'], 504 | when: "{{$deps[0] != '123'}}", 505 | fulfill: { 506 | schema: { 507 | title: 'BB', 508 | 'x-disabled': true, 509 | }, 510 | }, 511 | otherwise: { 512 | schema: { 513 | title: 'Changed', 514 | 'x-disabled': false, 515 | }, 516 | }, 517 | }, 518 | ], 519 | }, 520 | remove: { 521 | type: 'void', 522 | 'x-component': 'ArrayCollapse.Remove', 523 | }, 524 | moveUp: { 525 | type: 'void', 526 | 'x-component': 'ArrayCollapse.MoveUp', 527 | }, 528 | moveDown: { 529 | type: 'void', 530 | 'x-component': 'ArrayCollapse.MoveDown', 531 | }, 532 | }, 533 | }, 534 | properties: { 535 | addition: { 536 | type: 'void', 537 | title: 'Add entry', 538 | 'x-component': 'ArrayCollapse.Addition', 539 | }, 540 | }, 541 | }, 542 | }, 543 | } 544 | 545 | export default () => { 546 | return ( 547 | <FormProvider form={form}> 548 | <SchemaField schema={schema} /> 549 | <FormButtonGroup> 550 | <Submit onSubmit={console.log}>Submit</Submit> 551 | </FormButtonGroup> 552 | </FormProvider> 553 | ) 554 | } 555 | ``` 556 | 557 | ## API 558 | 559 | ### ArrayCollapse 560 | 561 | Reference https://fusion.design/pc/component/collapse 562 | 563 | Extended attributes 564 | 565 | | Property name | Type | Description | Default value | 566 | | --------------------- | ------ | ---------------------------- | ------------- | 567 | | defaultOpenPanelCount | number | Default expanded Panel count | 5 | 568 | 569 | ### ArrayCollapse.CollapsePanel 570 | 571 | Reference https://fusion.design/pc/component/collapse 572 | 573 | ### ArrayCollapse.Addition 574 | 575 | > Add button 576 | 577 | Extended attributes 578 | 579 | | Property name | Type | Description | Default value | 580 | | ------------- | -------------------- | ------------- | ------------- | 581 | | title | ReactText | Copywriting | | 582 | | method | `'push' \|'unshift'` | add method | `'push'` | 583 | | defaultValue | `any` | Default value | | 584 | 585 | Other references https://fusion.design/pc/component/basic/button 586 | 587 | Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective 588 | 589 | ### ArrayCollapse.Remove 590 | 591 | > Delete button 592 | 593 | | Property name | Type | Description | Default value | 594 | | ------------- | --------- | ----------- | ------------- | 595 | | title | ReactText | Copywriting | | 596 | 597 | Other references https://ant.design/components/icon-cn/ 598 | 599 | Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective 600 | 601 | ### ArrayCollapse.MoveDown 602 | 603 | > Move down button 604 | 605 | | Property name | Type | Description | Default value | 606 | | ------------- | --------- | ----------- | ------------- | 607 | | title | ReactText | Copywriting | | 608 | 609 | Other references https://ant.design/components/icon-cn/ 610 | 611 | Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective 612 | 613 | ### ArrayCollapse.MoveUp 614 | 615 | > Move up button 616 | 617 | | Property name | Type | Description | Default value | 618 | | ------------- | --------- | ----------- | ------------- | 619 | | title | ReactText | Copywriting | | 620 | 621 | Other references https://ant.design/components/icon-cn/ 622 | 623 | Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective 624 | 625 | ### ArrayCollapse.Index 626 | 627 | > Index Renderer 628 | 629 | No attributes 630 | 631 | ### ArrayCollapse.useIndex 632 | 633 | > Read the React Hook of the current rendering row index 634 | 635 | ### ArrayCollapse.useRecord 636 | 637 | > Read the React Hook of the current rendering row 638 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/ArrayCollapse.md: -------------------------------------------------------------------------------- ```markdown 1 | # ArrayCollapse 2 | 3 | > Folding panel, it is more suitable to use ArrayCollapse for scenes with more fields in each row and more linkage 4 | > 5 | > Note: This component is only applicable to Schema scenarios 6 | 7 | ## Markup Schema example 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | FormItem, 13 | Input, 14 | ArrayCollapse, 15 | FormButtonGroup, 16 | Submit, 17 | } from '@formily/antd' 18 | import { createForm } from '@formily/core' 19 | import { FormProvider, createSchemaField } from '@formily/react' 20 | import { Button } from 'antd' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | FormItem, 25 | Input, 26 | ArrayCollapse, 27 | }, 28 | }) 29 | 30 | const form = createForm() 31 | 32 | export default () => { 33 | return ( 34 | <FormProvider form={form}> 35 | <SchemaField> 36 | <SchemaField.Array 37 | name="string_array" 38 | maxItems={3} 39 | x-decorator="FormItem" 40 | x-component="ArrayCollapse" 41 | x-component-props={{ 42 | accordion: true, 43 | defaultOpenPanelCount: 3, 44 | }} 45 | > 46 | <SchemaField.Void 47 | x-component="ArrayCollapse.CollapsePanel" 48 | x-component-props={{ 49 | header: 'String array', 50 | }} 51 | > 52 | <SchemaField.Void x-component="ArrayCollapse.Index" /> 53 | <SchemaField.String 54 | name="input" 55 | x-decorator="FormItem" 56 | title="Input" 57 | required 58 | x-component="Input" 59 | /> 60 | <SchemaField.Void x-component="ArrayCollapse.Remove" /> 61 | <SchemaField.Void x-component="ArrayCollapse.MoveUp" /> 62 | <SchemaField.Void x-component="ArrayCollapse.MoveDown" /> 63 | </SchemaField.Void> 64 | <SchemaField.Void 65 | x-component="ArrayCollapse.Addition" 66 | title="Add entry" 67 | /> 68 | </SchemaField.Array> 69 | <SchemaField.Array 70 | name="array" 71 | maxItems={3} 72 | x-decorator="FormItem" 73 | x-component="ArrayCollapse" 74 | > 75 | <SchemaField.Object 76 | x-component="ArrayCollapse.CollapsePanel" 77 | x-component-props={{ 78 | header: 'Object array', 79 | }} 80 | > 81 | <SchemaField.Void x-component="ArrayCollapse.Index" /> 82 | <SchemaField.String 83 | name="input" 84 | x-decorator="FormItem" 85 | title="Input" 86 | required 87 | x-component="Input" 88 | /> 89 | <SchemaField.Void x-component="ArrayCollapse.Remove" /> 90 | <SchemaField.Void x-component="ArrayCollapse.MoveUp" /> 91 | <SchemaField.Void x-component="ArrayCollapse.MoveDown" /> 92 | </SchemaField.Object> 93 | <SchemaField.Void 94 | x-component="ArrayCollapse.Addition" 95 | title="Add entry" 96 | /> 97 | </SchemaField.Array> 98 | <SchemaField.Array 99 | name="string_array_unshift" 100 | maxItems={3} 101 | x-decorator="FormItem" 102 | x-component="ArrayCollapse" 103 | x-component-props={{ 104 | defaultOpenPanelCount: 8, 105 | }} 106 | > 107 | <SchemaField.Void 108 | x-component="ArrayCollapse.CollapsePanel" 109 | x-component-props={{ 110 | header: 'String array', 111 | }} 112 | > 113 | <SchemaField.Void x-component="ArrayCollapse.Index" /> 114 | <SchemaField.String 115 | name="input" 116 | x-decorator="FormItem" 117 | title="Input" 118 | required 119 | x-component="Input" 120 | /> 121 | <SchemaField.Void x-component="ArrayCollapse.Remove" /> 122 | <SchemaField.Void x-component="ArrayCollapse.MoveUp" /> 123 | <SchemaField.Void x-component="ArrayCollapse.MoveDown" /> 124 | </SchemaField.Void> 125 | <SchemaField.Void 126 | x-component="ArrayCollapse.Addition" 127 | title="Add entry (unshift)" 128 | x-component-props={{ 129 | method: 'unshift', 130 | }} 131 | /> 132 | </SchemaField.Array> 133 | </SchemaField> 134 | <FormButtonGroup> 135 | <Button 136 | onClick={() => { 137 | form.setInitialValues({ 138 | array: Array.from({ length: 10 }).map(() => ({ 139 | input: 'default value', 140 | })), 141 | string_array: Array.from({ length: 10 }).map( 142 | () => 'default value' 143 | ), 144 | string_array_unshift: Array.from({ length: 10 }).map( 145 | () => 'default value' 146 | ), 147 | }) 148 | }} 149 | > 150 | Load default data 151 | </Button> 152 | <Submit onSubmit={console.log}>Submit</Submit> 153 | </FormButtonGroup> 154 | </FormProvider> 155 | ) 156 | } 157 | ``` 158 | 159 | ## JSON Schema case 160 | 161 | ```tsx 162 | import React from 'react' 163 | import { 164 | FormItem, 165 | Input, 166 | ArrayCollapse, 167 | FormButtonGroup, 168 | Submit, 169 | } from '@formily/antd' 170 | import { createForm } from '@formily/core' 171 | import { FormProvider, createSchemaField } from '@formily/react' 172 | 173 | const SchemaField = createSchemaField({ 174 | components: { 175 | FormItem, 176 | Input, 177 | ArrayCollapse, 178 | }, 179 | }) 180 | 181 | const form = createForm() 182 | 183 | const schema = { 184 | type: 'object', 185 | properties: { 186 | string_array: { 187 | type: 'array', 188 | 'x-component': 'ArrayCollapse', 189 | maxItems: 3, 190 | 'x-decorator': 'FormItem', 191 | items: { 192 | type: 'void', 193 | 'x-component': 'ArrayCollapse.CollapsePanel', 194 | 'x-component-props': { 195 | header: 'String array', 196 | }, 197 | properties: { 198 | index: { 199 | type: 'void', 200 | 'x-component': 'ArrayCollapse.Index', 201 | }, 202 | input: { 203 | type: 'string', 204 | 'x-decorator': 'FormItem', 205 | title: 'Input', 206 | required: true, 207 | 'x-component': 'Input', 208 | }, 209 | remove: { 210 | type: 'void', 211 | 'x-component': 'ArrayCollapse.Remove', 212 | }, 213 | moveUp: { 214 | type: 'void', 215 | 'x-component': 'ArrayCollapse.MoveUp', 216 | }, 217 | moveDown: { 218 | type: 'void', 219 | 'x-component': 'ArrayCollapse.MoveDown', 220 | }, 221 | }, 222 | }, 223 | properties: { 224 | addition: { 225 | type: 'void', 226 | title: 'Add entry', 227 | 'x-component': 'ArrayCollapse.Addition', 228 | }, 229 | }, 230 | }, 231 | array: { 232 | type: 'array', 233 | 'x-component': 'ArrayCollapse', 234 | maxItems: 3, 235 | 'x-decorator': 'FormItem', 236 | items: { 237 | type: 'object', 238 | 'x-component': 'ArrayCollapse.CollapsePanel', 239 | 'x-component-props': { 240 | header: 'Object array', 241 | }, 242 | properties: { 243 | index: { 244 | type: 'void', 245 | 'x-component': 'ArrayCollapse.Index', 246 | }, 247 | input: { 248 | type: 'string', 249 | 'x-decorator': 'FormItem', 250 | title: 'Input', 251 | required: true, 252 | 'x-component': 'Input', 253 | }, 254 | remove: { 255 | type: 'void', 256 | 'x-component': 'ArrayCollapse.Remove', 257 | }, 258 | moveUp: { 259 | type: 'void', 260 | 'x-component': 'ArrayCollapse.MoveUp', 261 | }, 262 | moveDown: { 263 | type: 'void', 264 | 'x-component': 'ArrayCollapse.MoveDown', 265 | }, 266 | }, 267 | }, 268 | properties: { 269 | addition: { 270 | type: 'void', 271 | title: 'Add entry', 272 | 'x-component': 'ArrayCollapse.Addition', 273 | }, 274 | }, 275 | }, 276 | array_unshift: { 277 | type: 'array', 278 | 'x-component': 'ArrayCollapse', 279 | maxItems: 3, 280 | 'x-decorator': 'FormItem', 281 | items: { 282 | type: 'object', 283 | 'x-component': 'ArrayCollapse.CollapsePanel', 284 | 'x-component-props': { 285 | header: 'Object array', 286 | }, 287 | properties: { 288 | index: { 289 | type: 'void', 290 | 'x-component': 'ArrayCollapse.Index', 291 | }, 292 | input: { 293 | type: 'string', 294 | 'x-decorator': 'FormItem', 295 | title: 'Input', 296 | required: true, 297 | 'x-component': 'Input', 298 | }, 299 | remove: { 300 | type: 'void', 301 | 'x-component': 'ArrayCollapse.Remove', 302 | }, 303 | moveUp: { 304 | type: 'void', 305 | 'x-component': 'ArrayCollapse.MoveUp', 306 | }, 307 | moveDown: { 308 | type: 'void', 309 | 'x-component': 'ArrayCollapse.MoveDown', 310 | }, 311 | }, 312 | }, 313 | properties: { 314 | addition: { 315 | type: 'void', 316 | title: 'Add entry (unshift)', 317 | 'x-component': 'ArrayCollapse.Addition', 318 | 'x-component-props': { 319 | method: 'unshift', 320 | }, 321 | }, 322 | }, 323 | }, 324 | }, 325 | } 326 | 327 | export default () => { 328 | return ( 329 | <FormProvider form={form}> 330 | <SchemaField schema={schema} /> 331 | <FormButtonGroup> 332 | <Submit onSubmit={console.log}>Submit</Submit> 333 | </FormButtonGroup> 334 | </FormProvider> 335 | ) 336 | } 337 | ``` 338 | 339 | ## Effects linkage case 340 | 341 | ```tsx 342 | import React from 'react' 343 | import { 344 | FormItem, 345 | Input, 346 | ArrayCollapse, 347 | FormButtonGroup, 348 | Submit, 349 | } from '@formily/antd' 350 | import { createForm, onFieldChange, onFieldReact } from '@formily/core' 351 | import { FormProvider, createSchemaField } from '@formily/react' 352 | 353 | const SchemaField = createSchemaField({ 354 | components: { 355 | FormItem, 356 | Input, 357 | ArrayCollapse, 358 | }, 359 | }) 360 | 361 | const form = createForm({ 362 | effects: () => { 363 | //Active linkage mode 364 | onFieldChange('array.*.aa', ['value'], (field, form) => { 365 | form.setFieldState(field.query('.bb'), (state) => { 366 | state.visible = field.value != '123' 367 | }) 368 | }) 369 | //Passive linkage mode 370 | onFieldReact('array.*.dd', (field) => { 371 | field.visible = field.query('.cc').get('value') != '123' 372 | }) 373 | }, 374 | }) 375 | 376 | export default () => { 377 | return ( 378 | <FormProvider form={form}> 379 | <SchemaField> 380 | <SchemaField.Array 381 | name="array" 382 | maxItems={3} 383 | x-component="ArrayCollapse" 384 | x-decorator="FormItem" 385 | x-component-props={{ 386 | title: 'Object array', 387 | }} 388 | > 389 | <SchemaField.Object 390 | x-component="ArrayCollapse.CollapsePanel" 391 | x-component-props={{ 392 | header: 'Object array', 393 | }} 394 | > 395 | <SchemaField.Void x-component="ArrayCollapse.Index" /> 396 | <SchemaField.String 397 | name="aa" 398 | x-decorator="FormItem" 399 | title="AA" 400 | required 401 | description="AA hide BB when entering 123" 402 | x-component="Input" 403 | /> 404 | <SchemaField.String 405 | name="bb" 406 | x-decorator="FormItem" 407 | title="BB" 408 | required 409 | x-component="Input" 410 | /> 411 | <SchemaField.String 412 | name="cc" 413 | x-decorator="FormItem" 414 | title="CC" 415 | required 416 | description="Hide DD when CC enters 123" 417 | x-component="Input" 418 | /> 419 | <SchemaField.String 420 | name="dd" 421 | x-decorator="FormItem" 422 | title="DD" 423 | required 424 | x-component="Input" 425 | /> 426 | <SchemaField.Void x-component="ArrayCollapse.Remove" /> 427 | <SchemaField.Void x-component="ArrayCollapse.MoveUp" /> 428 | <SchemaField.Void x-component="ArrayCollapse.MoveDown" /> 429 | </SchemaField.Object> 430 | <SchemaField.Void 431 | x-component="ArrayCollapse.Addition" 432 | title="Add entry" 433 | /> 434 | </SchemaField.Array> 435 | </SchemaField> 436 | <FormButtonGroup> 437 | <Submit onSubmit={console.log}>Submit</Submit> 438 | </FormButtonGroup> 439 | </FormProvider> 440 | ) 441 | } 442 | ``` 443 | 444 | ## JSON Schema linkage case 445 | 446 | ```tsx 447 | import React from 'react' 448 | import { 449 | FormItem, 450 | Input, 451 | ArrayCollapse, 452 | FormButtonGroup, 453 | Submit, 454 | } from '@formily/antd' 455 | import { createForm } from '@formily/core' 456 | import { FormProvider, createSchemaField } from '@formily/react' 457 | 458 | const SchemaField = createSchemaField({ 459 | components: { 460 | FormItem, 461 | Input, 462 | ArrayCollapse, 463 | }, 464 | }) 465 | 466 | const form = createForm() 467 | 468 | const schema = { 469 | type: 'object', 470 | properties: { 471 | array: { 472 | type: 'array', 473 | 'x-component': 'ArrayCollapse', 474 | maxItems: 3, 475 | title: 'Object array', 476 | items: { 477 | type: 'object', 478 | 'x-component': 'ArrayCollapse.CollapsePanel', 479 | 'x-component-props': { 480 | header: 'Object array', 481 | }, 482 | properties: { 483 | index: { 484 | type: 'void', 485 | 'x-component': 'ArrayCollapse.Index', 486 | }, 487 | aa: { 488 | type: 'string', 489 | 'x-decorator': 'FormItem', 490 | title: 'AA', 491 | required: true, 492 | 'x-component': 'Input', 493 | description: 'Enter 123', 494 | }, 495 | bb: { 496 | type: 'string', 497 | title: 'BB', 498 | required: true, 499 | 'x-decorator': 'FormItem', 500 | 'x-component': 'Input', 501 | 'x-reactions': [ 502 | { 503 | dependencies: ['.aa'], 504 | when: "{{$deps[0] != '123'}}", 505 | fulfill: { 506 | schema: { 507 | title: 'BB', 508 | 'x-disabled': true, 509 | }, 510 | }, 511 | otherwise: { 512 | schema: { 513 | title: 'Changed', 514 | 'x-disabled': false, 515 | }, 516 | }, 517 | }, 518 | ], 519 | }, 520 | remove: { 521 | type: 'void', 522 | 'x-component': 'ArrayCollapse.Remove', 523 | }, 524 | moveUp: { 525 | type: 'void', 526 | 'x-component': 'ArrayCollapse.MoveUp', 527 | }, 528 | moveDown: { 529 | type: 'void', 530 | 'x-component': 'ArrayCollapse.MoveDown', 531 | }, 532 | }, 533 | }, 534 | properties: { 535 | addition: { 536 | type: 'void', 537 | title: 'Add entry', 538 | 'x-component': 'ArrayCollapse.Addition', 539 | }, 540 | }, 541 | }, 542 | }, 543 | } 544 | 545 | export default () => { 546 | return ( 547 | <FormProvider form={form}> 548 | <SchemaField schema={schema} /> 549 | <FormButtonGroup> 550 | <Submit onSubmit={console.log}>Submit</Submit> 551 | </FormButtonGroup> 552 | </FormProvider> 553 | ) 554 | } 555 | ``` 556 | 557 | ## API 558 | 559 | ### ArrayCollapse 560 | 561 | Reference https://ant.design/components/collapse-cn/ 562 | 563 | ### ArrayCollapse.CollapsePanel 564 | 565 | Reference https://ant.design/components/collapse-cn/ 566 | 567 | ### ArrayCollapse.Addition 568 | 569 | > Add button 570 | 571 | Extended attributes 572 | 573 | | Property name | Type | Description | Default value | 574 | | ------------- | -------------------- | ------------- | ------------- | 575 | | title | ReactText | Copywriting | | 576 | | method | `'push' \|'unshift'` | add method | `'push'` | 577 | | defaultValue | `any` | Default value | | 578 | 579 | Other references https://ant.design/components/button-cn/ 580 | 581 | Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective 582 | 583 | Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. 584 | 585 | ### ArrayCollapse.Remove 586 | 587 | > Delete button 588 | 589 | | Property name | Type | Description | Default value | 590 | | ------------- | --------- | ----------- | ------------- | 591 | | title | ReactText | Copywriting | | 592 | 593 | Other references https://ant.design/components/icon-cn/ 594 | 595 | Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective 596 | 597 | Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. 598 | 599 | ### ArrayCollapse.MoveDown 600 | 601 | > Move down button 602 | 603 | | Property name | Type | Description | Default value | 604 | | ------------- | --------- | ----------- | ------------- | 605 | | title | ReactText | Copywriting | | 606 | 607 | Other references https://ant.design/components/icon-cn/ 608 | 609 | Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective 610 | 611 | Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. 612 | 613 | ### ArrayCollapse.MoveUp 614 | 615 | > Move up button 616 | 617 | | Property name | Type | Description | Default value | 618 | | ------------- | --------- | ----------- | ------------- | 619 | | title | ReactText | Copywriting | | 620 | 621 | Other references https://ant.design/components/icon-cn/ 622 | 623 | Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective 624 | 625 | Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. 626 | 627 | ### ArrayCollapse.Index 628 | 629 | > Index Renderer 630 | 631 | No attributes 632 | 633 | ### ArrayCollapse.useIndex 634 | 635 | > Read the React Hook of the current rendering row index 636 | 637 | ### ArrayCollapse.useRecord 638 | 639 | > Read the React Hook of the current rendering row 640 | ``` -------------------------------------------------------------------------------- /packages/element/src/array-table/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | ArrayField, 3 | FieldDisplayTypes, 4 | GeneralField, 5 | IVoidFieldFactoryProps, 6 | } from '@formily/core' 7 | import type { Schema } from '@formily/json-schema' 8 | import { observer } from '@formily/reactive-vue' 9 | import { isArr, isBool, isFn } from '@formily/shared' 10 | import { 11 | Fragment, 12 | h, 13 | RecursionField as _RecursionField, 14 | useField, 15 | useFieldSchema, 16 | } from '@formily/vue' 17 | import type { 18 | Pagination as PaginationProps, 19 | Table as TableProps, 20 | TableColumn as ElColumnProps, 21 | } from 'element-ui' 22 | import { 23 | Badge, 24 | Option, 25 | Pagination, 26 | Select, 27 | Table as ElTable, 28 | TableColumn as ElTableColumn, 29 | } from 'element-ui' 30 | import type { Component, VNode } from 'vue' 31 | import { computed, defineComponent, inject, provide, ref, Ref } from 'vue-demi' 32 | import { ArrayBase } from '../array-base' 33 | import { Space } from '../space' 34 | import { stylePrefix } from '../__builtins__/configs' 35 | import { composeExport } from '../__builtins__/shared' 36 | 37 | const RecursionField = _RecursionField as unknown as Component 38 | 39 | interface IArrayTableProps extends TableProps { 40 | pagination?: PaginationProps | boolean 41 | } 42 | interface IArrayTablePaginationProps extends PaginationProps { 43 | dataSource?: any[] 44 | } 45 | 46 | interface ObservableColumnSource { 47 | field: GeneralField 48 | fieldProps: IVoidFieldFactoryProps<any, any> 49 | columnProps: ElColumnProps & { title: string; asterisk: boolean } 50 | schema: Schema 51 | display: FieldDisplayTypes 52 | required: boolean 53 | name: string 54 | } 55 | 56 | type ColumnProps = ElColumnProps & { 57 | key: string | number 58 | asterisk: boolean 59 | render?: ( 60 | startIndex?: Ref<number> 61 | ) => (props: { 62 | row: Record<string, any> 63 | column: ElColumnProps 64 | $index: number 65 | }) => VNode 66 | } 67 | 68 | interface PaginationAction { 69 | totalPage?: number 70 | pageSize?: number 71 | changePage?: (page: number) => void 72 | } 73 | 74 | const PaginationSymbol = Symbol('pagination') 75 | 76 | const isColumnComponent = (schema: Schema) => { 77 | return schema['x-component']?.indexOf('Column') > -1 78 | } 79 | 80 | const isOperationsComponent = (schema: Schema) => { 81 | return schema['x-component']?.indexOf('Operations') > -1 82 | } 83 | 84 | const isAdditionComponent = (schema: Schema) => { 85 | return schema['x-component']?.indexOf('Addition') > -1 86 | } 87 | 88 | const getArrayTableSources = ( 89 | arrayFieldRef: Ref<ArrayField>, 90 | schemaRef: Ref<Schema> 91 | ) => { 92 | const arrayField = arrayFieldRef.value 93 | const parseSources = (schema: Schema): ObservableColumnSource[] => { 94 | if ( 95 | isColumnComponent(schema) || 96 | isOperationsComponent(schema) || 97 | isAdditionComponent(schema) 98 | ) { 99 | if (!schema['x-component-props']?.['prop'] && !schema['name']) return [] 100 | const name = schema['x-component-props']?.['prop'] || schema['name'] 101 | const field = arrayField.query(arrayField.address.concat(name)).take() 102 | const fieldProps = field?.props || schema.toFieldProps() 103 | const columnProps = 104 | (field?.component as any[])?.[1] || schema['x-component-props'] || {} 105 | const display = field?.display || schema['x-display'] 106 | const required = schema.reduceProperties((required, property) => { 107 | if (required) { 108 | return required 109 | } 110 | return !!property.required 111 | }, false) 112 | 113 | return [ 114 | { 115 | name, 116 | display, 117 | required, 118 | field, 119 | fieldProps, 120 | schema, 121 | columnProps, 122 | }, 123 | ] 124 | } else if (schema.properties) { 125 | return schema.reduceProperties((buf: any[], schema) => { 126 | return buf.concat(parseSources(schema)) 127 | }, []) 128 | } else { 129 | return [] 130 | } 131 | } 132 | 133 | const parseArrayTable = (schema: Schema['items']) => { 134 | if (!schema) return [] 135 | const sources: ObservableColumnSource[] = [] 136 | const items = isArr(schema) ? schema : ([schema] as Schema[]) 137 | return items.reduce((columns, schema) => { 138 | const item = parseSources(schema) 139 | if (item) { 140 | return columns.concat(item) 141 | } 142 | return columns 143 | }, sources) 144 | } 145 | 146 | if (!schemaRef.value) throw new Error('can not found schema object') 147 | 148 | return parseArrayTable(schemaRef.value.items) 149 | } 150 | 151 | const getArrayTableColumns = ( 152 | sources: ObservableColumnSource[] 153 | ): ColumnProps[] => { 154 | return sources.reduce( 155 | ( 156 | buf: ColumnProps[], 157 | { name, columnProps, schema, display, required }, 158 | key 159 | ) => { 160 | const { title, asterisk, ...props } = columnProps 161 | if (display !== 'visible') return buf 162 | if (!isColumnComponent(schema)) return buf 163 | 164 | const render = (startIndex?: Ref<number>) => { 165 | return columnProps?.type && columnProps?.type !== 'default' 166 | ? undefined 167 | : (props: { 168 | row: Record<string, any> 169 | column: ElColumnProps 170 | $index: number 171 | }): VNode => { 172 | let index = (startIndex?.value ?? 0) + props.$index 173 | // const index = reactiveDataSource.value.indexOf(props.row) 174 | 175 | const children = h( 176 | ArrayBase.Item, 177 | { props: { index, record: props.row }, key: `${key}${index}` }, 178 | { 179 | default: () => 180 | h( 181 | RecursionField, 182 | { 183 | props: { 184 | schema, 185 | name: index, 186 | onlyRenderProperties: true, 187 | }, 188 | }, 189 | {} 190 | ), 191 | } 192 | ) 193 | return children 194 | } 195 | } 196 | 197 | return buf.concat({ 198 | label: title, 199 | ...props, 200 | key, 201 | prop: name, 202 | asterisk: asterisk ?? required, 203 | render, 204 | }) 205 | }, 206 | [] 207 | ) 208 | } 209 | 210 | const renderAddition = () => { 211 | const schema = useFieldSchema() 212 | return schema.value.reduceProperties((addition, schema) => { 213 | if (isAdditionComponent(schema)) { 214 | return h( 215 | RecursionField, 216 | { 217 | props: { 218 | schema, 219 | name: 'addition', 220 | }, 221 | }, 222 | {} 223 | ) 224 | } 225 | return addition 226 | }, null) 227 | } 228 | 229 | const schedulerRequest = { 230 | request: null, 231 | } 232 | 233 | const StatusSelect = observer( 234 | defineComponent({ 235 | props: { 236 | value: Number, 237 | onChange: Function, 238 | options: Array, 239 | pageSize: Number, 240 | }, 241 | setup(props) { 242 | const fieldRef = useField<ArrayField>() 243 | const prefixCls = `${stylePrefix}-array-table` 244 | 245 | return () => { 246 | const field = fieldRef.value 247 | const width = String(props.options?.length).length * 15 248 | const errors = field.errors 249 | const parseIndex = (address: string) => { 250 | return Number( 251 | address 252 | .slice(address.indexOf(field.address.toString()) + 1) 253 | .match(/(\d+)/)?.[1] 254 | ) 255 | } 256 | 257 | return h( 258 | Select, 259 | { 260 | style: { 261 | width: `${width < 60 ? 60 : width}px`, 262 | }, 263 | class: [ 264 | `${prefixCls}-status-select`, 265 | { 266 | 'has-error': errors?.length, 267 | }, 268 | ], 269 | props: { 270 | value: props.value, 271 | popperClass: `${prefixCls}-status-select-dropdown`, 272 | }, 273 | on: { 274 | input: props.onChange, 275 | }, 276 | }, 277 | { 278 | default: () => { 279 | return props.options?.map(({ label, value }) => { 280 | const hasError = errors.some(({ address }) => { 281 | const currentIndex = parseIndex(address) 282 | const startIndex = (value - 1) * props.pageSize 283 | const endIndex = value * props.pageSize 284 | return currentIndex >= startIndex && currentIndex <= endIndex 285 | }) 286 | 287 | return h( 288 | Option, 289 | { 290 | key: value, 291 | props: { 292 | label, 293 | value, 294 | }, 295 | }, 296 | { 297 | default: () => { 298 | if (hasError) { 299 | return h( 300 | Badge, 301 | { 302 | props: { 303 | isDot: true, 304 | }, 305 | }, 306 | { default: () => label } 307 | ) 308 | } 309 | 310 | return label 311 | }, 312 | } 313 | ) 314 | }) 315 | }, 316 | } 317 | ) 318 | } 319 | }, 320 | }), 321 | { 322 | scheduler: (update) => { 323 | clearTimeout(schedulerRequest.request) 324 | schedulerRequest.request = setTimeout(() => { 325 | update() 326 | }, 100) 327 | }, 328 | } 329 | ) 330 | 331 | const usePagination = () => { 332 | return inject<Ref<PaginationAction>>(PaginationSymbol, ref({})) 333 | } 334 | 335 | const ArrayTablePagination = defineComponent<IArrayTablePaginationProps>({ 336 | inheritAttrs: false, 337 | props: ['pageSize', 'dataSource'], 338 | setup(props, { attrs, slots }) { 339 | const prefixCls = `${stylePrefix}-array-table` 340 | const current = ref(1) 341 | const pageSize = computed(() => props.pageSize || 10) 342 | const dataSource = computed(() => props.dataSource || []) 343 | const startIndex = computed(() => (current.value - 1) * pageSize.value) 344 | const endIndex = computed(() => startIndex.value + pageSize.value - 1) 345 | const total = computed(() => dataSource.value?.length || 0) 346 | const totalPage = computed(() => Math.ceil(total.value / pageSize.value)) 347 | const pages = computed(() => { 348 | return Array.from(new Array(totalPage.value)).map((_, index) => { 349 | const page = index + 1 350 | return { 351 | label: page, 352 | value: page, 353 | } 354 | }) 355 | }) 356 | 357 | const renderPagination = function () { 358 | if (totalPage.value <= 1) return 359 | return h( 360 | 'div', 361 | { 362 | class: [`${prefixCls}-pagination`], 363 | }, 364 | { 365 | default: () => 366 | h( 367 | Space, 368 | {}, 369 | { 370 | default: () => [ 371 | h( 372 | StatusSelect, 373 | { 374 | props: { 375 | value: current.value, 376 | onChange: (val: number) => { 377 | current.value = val 378 | }, 379 | pageSize: pageSize.value, 380 | options: pages.value, 381 | }, 382 | }, 383 | {} 384 | ), 385 | h( 386 | Pagination, 387 | { 388 | props: { 389 | background: true, 390 | layout: 'prev, pager, next', 391 | ...attrs, 392 | pageSize: pageSize.value, 393 | pageCount: totalPage.value, 394 | currentPage: current.value, 395 | }, 396 | on: { 397 | 'current-change': (val: number) => { 398 | current.value = val 399 | }, 400 | }, 401 | }, 402 | {} 403 | ), 404 | ], 405 | } 406 | ), 407 | } 408 | ) 409 | } 410 | 411 | const paginationContext = computed<PaginationAction>(() => { 412 | return { 413 | totalPage: totalPage.value, 414 | pageSize: pageSize.value, 415 | changePage: (page: number) => (current.value = page), 416 | } 417 | }) 418 | provide(PaginationSymbol, paginationContext) 419 | 420 | return () => { 421 | return h( 422 | Fragment, 423 | {}, 424 | { 425 | default: () => 426 | slots?.default?.( 427 | dataSource.value?.slice(startIndex.value, endIndex.value + 1), 428 | renderPagination, 429 | startIndex 430 | ), 431 | } 432 | ) 433 | } 434 | }, 435 | }) 436 | 437 | const ArrayTableInner = observer( 438 | defineComponent<IArrayTableProps>({ 439 | name: 'FArrayTable', 440 | inheritAttrs: false, 441 | setup(props, { attrs, listeners, slots }) { 442 | const fieldRef = useField<ArrayField>() 443 | const schemaRef = useFieldSchema() 444 | const prefixCls = `${stylePrefix}-array-table` 445 | const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value) 446 | 447 | const defaultRowKey = (record: any) => { 448 | return getKey(record) 449 | } 450 | 451 | return () => { 452 | const props = attrs as unknown as IArrayTableProps 453 | const field = fieldRef.value 454 | const dataSource = Array.isArray(field.value) ? field.value.slice() : [] 455 | const pagination = props.pagination 456 | const sources = getArrayTableSources(fieldRef, schemaRef) 457 | const columns = getArrayTableColumns(sources) 458 | 459 | const renderColumns = (startIndex?: Ref<number>) => { 460 | return columns.map(({ key, render, asterisk, ...props }) => { 461 | const children = {} as Record<string, any> 462 | if (render) { 463 | children.default = render(startIndex) 464 | } 465 | if (asterisk) { 466 | children.header = ({ column }: { column: ElColumnProps }) => 467 | h( 468 | 'span', 469 | {}, 470 | { 471 | default: () => [ 472 | h( 473 | 'span', 474 | { class: `${prefixCls}-asterisk` }, 475 | { default: () => ['*'] } 476 | ), 477 | column.label, 478 | ], 479 | } 480 | ) 481 | } 482 | return h( 483 | ElTableColumn, 484 | { 485 | key, 486 | props, 487 | }, 488 | children 489 | ) 490 | }) 491 | } 492 | 493 | const renderStateManager = () => 494 | sources.map((column, key) => { 495 | //专门用来承接对Column的状态管理 496 | if (!isColumnComponent(column.schema)) return 497 | return h( 498 | RecursionField, 499 | { 500 | props: { 501 | name: column.name, 502 | schema: column.schema, 503 | onlyRenderSelf: true, 504 | }, 505 | key, 506 | }, 507 | {} 508 | ) 509 | }) 510 | 511 | const renderTable = ( 512 | dataSource?: any[], 513 | pager?: () => VNode, 514 | startIndex?: Ref<number> 515 | ) => { 516 | return h( 517 | 'div', 518 | { class: prefixCls }, 519 | { 520 | default: () => 521 | h( 522 | ArrayBase, 523 | { 524 | props: { 525 | keyMap, 526 | }, 527 | }, 528 | { 529 | default: () => [ 530 | h( 531 | ElTable, 532 | { 533 | props: { 534 | rowKey: defaultRowKey, 535 | ...attrs, 536 | data: dataSource, 537 | }, 538 | on: listeners, 539 | }, 540 | { 541 | ...slots, 542 | default: () => renderColumns(startIndex), 543 | } 544 | ), 545 | pager?.(), 546 | renderStateManager(), 547 | renderAddition(), 548 | ], 549 | } 550 | ), 551 | } 552 | ) 553 | } 554 | 555 | if (!pagination) { 556 | return renderTable(dataSource, null) 557 | } 558 | return h( 559 | ArrayTablePagination, 560 | { 561 | attrs: { 562 | ...(isBool(pagination) ? {} : pagination), 563 | dataSource, 564 | }, 565 | }, 566 | { default: renderTable } 567 | ) 568 | } 569 | }, 570 | }) 571 | ) 572 | 573 | const ArrayTableColumn: Component = { 574 | name: 'FArrayTableColumn', 575 | render(h) { 576 | return h() 577 | }, 578 | } 579 | 580 | const ArrayAddition = defineComponent({ 581 | name: 'ArrayAddition', 582 | setup(props, { attrs, listeners, slots }) { 583 | const array = ArrayBase.useArray() 584 | const paginationRef = usePagination() 585 | 586 | const onClick = listeners['click'] 587 | listeners['click'] = (e) => { 588 | const { totalPage = 0, pageSize = 10, changePage } = paginationRef.value 589 | // 如果添加数据后超过当前页,则自动切换到下一页 590 | const total = array?.field?.value?.value.length || 0 591 | if (total === (totalPage - 1) * pageSize + 1 && isFn(changePage)) { 592 | changePage(totalPage) 593 | } 594 | if (onClick) onClick(e) 595 | } 596 | return () => { 597 | return h( 598 | ArrayBase.Addition, 599 | { 600 | props, 601 | attrs, 602 | on: listeners, 603 | }, 604 | slots 605 | ) 606 | } 607 | }, 608 | }) 609 | 610 | export const ArrayTable = composeExport(ArrayTableInner, { 611 | Column: ArrayTableColumn, 612 | Index: ArrayBase.Index, 613 | SortHandle: ArrayBase.SortHandle, 614 | Addition: ArrayAddition, 615 | Remove: ArrayBase.Remove, 616 | MoveDown: ArrayBase.MoveDown, 617 | MoveUp: ArrayBase.MoveUp, 618 | useArray: ArrayBase.useArray, 619 | useIndex: ArrayBase.useIndex, 620 | useRecord: ArrayBase.useRecord, 621 | }) 622 | 623 | export default ArrayTable 624 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/TreeSelect.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # TreeSelect 2 | 3 | > 树选择器 4 | 5 | ## Markup Schema 同步数据源案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { TreeSelect, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | TreeSelect, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Number 26 | name="select" 27 | title="选择框" 28 | x-decorator="FormItem" 29 | x-component="TreeSelect" 30 | enum={[ 31 | { 32 | label: '选项1', 33 | value: 1, 34 | children: [ 35 | { 36 | title: 'Child Node1', 37 | value: '0-0-0', 38 | key: '0-0-0', 39 | }, 40 | { 41 | title: 'Child Node2', 42 | value: '0-0-1', 43 | key: '0-0-1', 44 | }, 45 | { 46 | title: 'Child Node3', 47 | value: '0-0-2', 48 | key: '0-0-2', 49 | }, 50 | ], 51 | }, 52 | { 53 | label: '选项2', 54 | value: 2, 55 | children: [ 56 | { 57 | title: 'Child Node3', 58 | value: '0-1-0', 59 | key: '0-1-0', 60 | }, 61 | { 62 | title: 'Child Node4', 63 | value: '0-1-1', 64 | key: '0-1-1', 65 | }, 66 | { 67 | title: 'Child Node5', 68 | value: '0-1-2', 69 | key: '0-1-2', 70 | }, 71 | ], 72 | }, 73 | ]} 74 | x-component-props={{ 75 | style: { 76 | width: 200, 77 | }, 78 | }} 79 | /> 80 | </SchemaField> 81 | <FormButtonGroup> 82 | <Submit onSubmit={console.log}>提交</Submit> 83 | </FormButtonGroup> 84 | </FormProvider> 85 | ) 86 | ``` 87 | 88 | ## Markup Schema 异步联动数据源案例 89 | 90 | ```tsx 91 | import React from 'react' 92 | import { 93 | TreeSelect, 94 | Select, 95 | FormItem, 96 | FormButtonGroup, 97 | Submit, 98 | } from '@formily/antd' 99 | import { createForm, onFieldReact, FormPathPattern, Field } from '@formily/core' 100 | import { FormProvider, createSchemaField } from '@formily/react' 101 | import { action } from '@formily/reactive' 102 | 103 | const SchemaField = createSchemaField({ 104 | components: { 105 | Select, 106 | TreeSelect, 107 | FormItem, 108 | }, 109 | }) 110 | 111 | const useAsyncDataSource = ( 112 | pattern: FormPathPattern, 113 | service: (field: Field) => Promise<{ label: string; value: any }[]> 114 | ) => { 115 | onFieldReact(pattern, (field) => { 116 | field.loading = true 117 | service(field).then( 118 | action.bound((data) => { 119 | field.dataSource = data 120 | field.loading = false 121 | }) 122 | ) 123 | }) 124 | } 125 | 126 | const form = createForm({ 127 | effects: () => { 128 | useAsyncDataSource('select', async (field) => { 129 | const linkage = field.query('linkage').get('value') 130 | if (!linkage) return [] 131 | return new Promise((resolve) => { 132 | setTimeout(() => { 133 | if (linkage === 1) { 134 | resolve([ 135 | { 136 | label: 'AAA', 137 | value: 'aaa', 138 | children: [ 139 | { 140 | title: 'Child Node1', 141 | value: '0-0-0', 142 | key: '0-0-0', 143 | }, 144 | { 145 | title: 'Child Node2', 146 | value: '0-0-1', 147 | key: '0-0-1', 148 | }, 149 | { 150 | title: 'Child Node3', 151 | value: '0-0-2', 152 | key: '0-0-2', 153 | }, 154 | ], 155 | }, 156 | { 157 | label: 'BBB', 158 | value: 'ccc', 159 | children: [ 160 | { 161 | title: 'Child Node1', 162 | value: '0-1-0', 163 | key: '0-1-0', 164 | }, 165 | { 166 | title: 'Child Node2', 167 | value: '0-1-1', 168 | key: '0-1-1', 169 | }, 170 | { 171 | title: 'Child Node3', 172 | value: '0-1-2', 173 | key: '0-1-2', 174 | }, 175 | ], 176 | }, 177 | ]) 178 | } else if (linkage === 2) { 179 | resolve([ 180 | { 181 | label: 'CCC', 182 | value: 'ccc', 183 | children: [ 184 | { 185 | title: 'Child Node1', 186 | value: '0-0-0', 187 | key: '0-0-0', 188 | }, 189 | { 190 | title: 'Child Node2', 191 | value: '0-0-1', 192 | key: '0-0-1', 193 | }, 194 | { 195 | title: 'Child Node3', 196 | value: '0-0-2', 197 | key: '0-0-2', 198 | }, 199 | ], 200 | }, 201 | { 202 | label: 'DDD', 203 | value: 'ddd', 204 | children: [ 205 | { 206 | title: 'Child Node1', 207 | value: '0-1-0', 208 | key: '0-1-0', 209 | }, 210 | { 211 | title: 'Child Node2', 212 | value: '0-1-1', 213 | key: '0-1-1', 214 | }, 215 | { 216 | title: 'Child Node3', 217 | value: '0-1-2', 218 | key: '0-1-2', 219 | }, 220 | ], 221 | }, 222 | ]) 223 | } 224 | }, 1500) 225 | }) 226 | }) 227 | }, 228 | }) 229 | 230 | export default () => ( 231 | <FormProvider form={form}> 232 | <SchemaField> 233 | <SchemaField.Number 234 | name="linkage" 235 | title="联动选择框" 236 | x-decorator="FormItem" 237 | x-component="Select" 238 | enum={[ 239 | { label: '发请求1', value: 1 }, 240 | { label: '发请求2', value: 2 }, 241 | ]} 242 | x-component-props={{ 243 | style: { 244 | width: 200, 245 | }, 246 | }} 247 | /> 248 | <SchemaField.String 249 | name="select" 250 | title="异步选择框" 251 | x-decorator="FormItem" 252 | x-component="TreeSelect" 253 | x-component-props={{ 254 | style: { 255 | width: 200, 256 | }, 257 | }} 258 | /> 259 | </SchemaField> 260 | <FormButtonGroup> 261 | <Submit onSubmit={console.log}>提交</Submit> 262 | </FormButtonGroup> 263 | </FormProvider> 264 | ) 265 | ``` 266 | 267 | ## JSON Schema 同步数据源案例 268 | 269 | ```tsx 270 | import React from 'react' 271 | import { TreeSelect, FormItem, FormButtonGroup, Submit } from '@formily/antd' 272 | import { createForm } from '@formily/core' 273 | import { FormProvider, createSchemaField } from '@formily/react' 274 | 275 | const SchemaField = createSchemaField({ 276 | components: { 277 | TreeSelect, 278 | FormItem, 279 | }, 280 | }) 281 | 282 | const form = createForm() 283 | 284 | const schema = { 285 | type: 'object', 286 | properties: { 287 | select: { 288 | type: 'string', 289 | title: '选择框', 290 | 'x-decorator': 'FormItem', 291 | 'x-component': 'TreeSelect', 292 | enum: [ 293 | { 294 | label: '选项1', 295 | value: 1, 296 | children: [ 297 | { 298 | title: 'Child Node1', 299 | value: '0-0-0', 300 | key: '0-0-0', 301 | }, 302 | { 303 | title: 'Child Node2', 304 | value: '0-0-1', 305 | key: '0-0-1', 306 | }, 307 | { 308 | title: 'Child Node3', 309 | value: '0-0-2', 310 | key: '0-0-2', 311 | }, 312 | ], 313 | }, 314 | { 315 | label: '选项2', 316 | value: 2, 317 | children: [ 318 | { 319 | title: 'Child Node1', 320 | value: '0-1-0', 321 | key: '0-1-0', 322 | }, 323 | { 324 | title: 'Child Node2', 325 | value: '0-1-1', 326 | key: '0-1-1', 327 | }, 328 | { 329 | title: 'Child Node3', 330 | value: '0-1-2', 331 | key: '0-1-2', 332 | }, 333 | ], 334 | }, 335 | ], 336 | 'x-component-props': { 337 | style: { 338 | width: 200, 339 | }, 340 | }, 341 | }, 342 | }, 343 | } 344 | 345 | export default () => ( 346 | <FormProvider form={form}> 347 | <SchemaField schema={schema} /> 348 | <FormButtonGroup> 349 | <Submit onSubmit={console.log}>提交</Submit> 350 | </FormButtonGroup> 351 | </FormProvider> 352 | ) 353 | ``` 354 | 355 | ## JSON Schema 异步联动数据源案例 356 | 357 | ```tsx 358 | import React from 'react' 359 | import { 360 | TreeSelect, 361 | Select, 362 | FormItem, 363 | FormButtonGroup, 364 | Submit, 365 | } from '@formily/antd' 366 | import { createForm } from '@formily/core' 367 | import { FormProvider, createSchemaField } from '@formily/react' 368 | import { action } from '@formily/reactive' 369 | 370 | const SchemaField = createSchemaField({ 371 | components: { 372 | Select, 373 | TreeSelect, 374 | FormItem, 375 | }, 376 | }) 377 | 378 | const loadData = async (field) => { 379 | const linkage = field.query('linkage').get('value') 380 | if (!linkage) return [] 381 | return new Promise((resolve) => { 382 | setTimeout(() => { 383 | if (linkage === 1) { 384 | resolve([ 385 | { 386 | label: 'AAA', 387 | value: 'aaa', 388 | children: [ 389 | { 390 | title: 'Child Node1', 391 | value: '0-0-0', 392 | key: '0-0-0', 393 | }, 394 | { 395 | title: 'Child Node2', 396 | value: '0-0-1', 397 | key: '0-0-1', 398 | }, 399 | { 400 | title: 'Child Node3', 401 | value: '0-0-2', 402 | key: '0-0-2', 403 | }, 404 | ], 405 | }, 406 | { 407 | label: 'BBB', 408 | value: 'ccc', 409 | children: [ 410 | { 411 | title: 'Child Node1', 412 | value: '0-1-0', 413 | key: '0-1-0', 414 | }, 415 | { 416 | title: 'Child Node2', 417 | value: '0-1-1', 418 | key: '0-1-1', 419 | }, 420 | { 421 | title: 'Child Node3', 422 | value: '0-1-2', 423 | key: '0-1-2', 424 | }, 425 | ], 426 | }, 427 | ]) 428 | } else if (linkage === 2) { 429 | resolve([ 430 | { 431 | label: 'CCC', 432 | value: 'ccc', 433 | children: [ 434 | { 435 | title: 'Child Node1', 436 | value: '0-0-0', 437 | key: '0-0-0', 438 | }, 439 | { 440 | title: 'Child Node2', 441 | value: '0-0-1', 442 | key: '0-0-1', 443 | }, 444 | { 445 | title: 'Child Node3', 446 | value: '0-0-2', 447 | key: '0-0-2', 448 | }, 449 | ], 450 | }, 451 | { 452 | label: 'DDD', 453 | value: 'ddd', 454 | children: [ 455 | { 456 | title: 'Child Node1', 457 | value: '0-1-0', 458 | key: '0-1-0', 459 | }, 460 | { 461 | title: 'Child Node2', 462 | value: '0-1-1', 463 | key: '0-1-1', 464 | }, 465 | { 466 | title: 'Child Node3', 467 | value: '0-1-2', 468 | key: '0-1-2', 469 | }, 470 | ], 471 | }, 472 | ]) 473 | } 474 | }, 1500) 475 | }) 476 | } 477 | 478 | const useAsyncDataSource = (service) => (field) => { 479 | field.loading = true 480 | service(field).then( 481 | action.bound((data) => { 482 | field.dataSource = data 483 | field.loading = false 484 | }) 485 | ) 486 | } 487 | 488 | const form = createForm() 489 | 490 | const schema = { 491 | type: 'object', 492 | properties: { 493 | linkage: { 494 | type: 'string', 495 | title: '联动选择框', 496 | enum: [ 497 | { label: '发请求1', value: 1 }, 498 | { label: '发请求2', value: 2 }, 499 | ], 500 | 'x-decorator': 'FormItem', 501 | 'x-component': 'Select', 502 | 'x-component-props': { 503 | style: { 504 | width: 200, 505 | }, 506 | }, 507 | }, 508 | select: { 509 | type: 'string', 510 | title: '异步选择框', 511 | 'x-decorator': 'FormItem', 512 | 'x-component': 'TreeSelect', 513 | 'x-component-props': { 514 | style: { 515 | width: 200, 516 | }, 517 | }, 518 | 'x-reactions': ['{{useAsyncDataSource(loadData)}}'], 519 | }, 520 | }, 521 | } 522 | 523 | export default () => ( 524 | <FormProvider form={form}> 525 | <SchemaField schema={schema} scope={{ useAsyncDataSource, loadData }} /> 526 | <FormButtonGroup> 527 | <Submit onSubmit={console.log}>提交</Submit> 528 | </FormButtonGroup> 529 | </FormProvider> 530 | ) 531 | ``` 532 | 533 | ## 纯 JSX 同步数据源案例 534 | 535 | ```tsx 536 | import React from 'react' 537 | import { TreeSelect, FormItem, FormButtonGroup, Submit } from '@formily/antd' 538 | import { createForm } from '@formily/core' 539 | import { FormProvider, Field } from '@formily/react' 540 | 541 | const form = createForm() 542 | 543 | export default () => ( 544 | <FormProvider form={form}> 545 | <Field 546 | name="select" 547 | title="选择框" 548 | dataSource={[ 549 | { 550 | label: '选项1', 551 | value: 1, 552 | children: [ 553 | { 554 | title: 'Child Node1', 555 | value: '0-0-0', 556 | key: '0-0-0', 557 | }, 558 | { 559 | title: 'Child Node2', 560 | value: '0-0-1', 561 | key: '0-0-1', 562 | }, 563 | { 564 | title: 'Child Node3', 565 | value: '0-0-2', 566 | key: '0-0-2', 567 | }, 568 | ], 569 | }, 570 | { 571 | label: '选项2', 572 | value: 2, 573 | children: [ 574 | { 575 | title: 'Child Node3', 576 | value: '0-1-0', 577 | key: '0-1-0', 578 | }, 579 | { 580 | title: 'Child Node4', 581 | value: '0-1-1', 582 | key: '0-1-1', 583 | }, 584 | { 585 | title: 'Child Node5', 586 | value: '0-1-2', 587 | key: '0-1-2', 588 | }, 589 | ], 590 | }, 591 | ]} 592 | decorator={[FormItem]} 593 | component={[TreeSelect]} 594 | /> 595 | <FormButtonGroup> 596 | <Submit onSubmit={console.log}>提交</Submit> 597 | </FormButtonGroup> 598 | </FormProvider> 599 | ) 600 | ``` 601 | 602 | ## 纯 JSX 异步联动数据源案例 603 | 604 | ```tsx 605 | import React from 'react' 606 | import { 607 | TreeSelect, 608 | Select, 609 | FormItem, 610 | FormButtonGroup, 611 | Submit, 612 | } from '@formily/antd' 613 | import { 614 | createForm, 615 | onFieldReact, 616 | FormPathPattern, 617 | Field as FieldType, 618 | } from '@formily/core' 619 | import { FormProvider, Field } from '@formily/react' 620 | import { action } from '@formily/reactive' 621 | 622 | const useAsyncDataSource = ( 623 | pattern: FormPathPattern, 624 | service: (field: FieldType) => Promise<{ label: string; value: any }[]> 625 | ) => { 626 | onFieldReact(pattern, (field) => { 627 | field.loading = true 628 | service(field).then( 629 | action.bound((data) => { 630 | field.dataSource = data 631 | field.loading = false 632 | }) 633 | ) 634 | }) 635 | } 636 | 637 | const form = createForm({ 638 | effects: () => { 639 | useAsyncDataSource('select', async (field) => { 640 | const linkage = field.query('linkage').get('value') 641 | if (!linkage) return [] 642 | return new Promise((resolve) => { 643 | setTimeout(() => { 644 | if (linkage === 1) { 645 | resolve([ 646 | { 647 | label: 'AAA', 648 | value: 'aaa', 649 | children: [ 650 | { 651 | title: 'Child Node1', 652 | value: '0-0-0', 653 | key: '0-0-0', 654 | }, 655 | { 656 | title: 'Child Node2', 657 | value: '0-0-1', 658 | key: '0-0-1', 659 | }, 660 | { 661 | title: 'Child Node3', 662 | value: '0-0-2', 663 | key: '0-0-2', 664 | }, 665 | ], 666 | }, 667 | { 668 | label: 'BBB', 669 | value: 'ccc', 670 | children: [ 671 | { 672 | title: 'Child Node1', 673 | value: '0-1-0', 674 | key: '0-1-0', 675 | }, 676 | { 677 | title: 'Child Node2', 678 | value: '0-1-1', 679 | key: '0-1-1', 680 | }, 681 | { 682 | title: 'Child Node3', 683 | value: '0-1-2', 684 | key: '0-1-2', 685 | }, 686 | ], 687 | }, 688 | ]) 689 | } else if (linkage === 2) { 690 | resolve([ 691 | { 692 | label: 'CCC', 693 | value: 'ccc', 694 | children: [ 695 | { 696 | title: 'Child Node1', 697 | value: '0-0-0', 698 | key: '0-0-0', 699 | }, 700 | { 701 | title: 'Child Node2', 702 | value: '0-0-1', 703 | key: '0-0-1', 704 | }, 705 | { 706 | title: 'Child Node3', 707 | value: '0-0-2', 708 | key: '0-0-2', 709 | }, 710 | ], 711 | }, 712 | { 713 | label: 'DDD', 714 | value: 'ddd', 715 | children: [ 716 | { 717 | title: 'Child Node1', 718 | value: '0-1-0', 719 | key: '0-1-0', 720 | }, 721 | { 722 | title: 'Child Node2', 723 | value: '0-1-1', 724 | key: '0-1-1', 725 | }, 726 | { 727 | title: 'Child Node3', 728 | value: '0-1-2', 729 | key: '0-1-2', 730 | }, 731 | ], 732 | }, 733 | ]) 734 | } 735 | }, 1500) 736 | }) 737 | }) 738 | }, 739 | }) 740 | 741 | export default () => ( 742 | <FormProvider form={form}> 743 | <Field 744 | name="linkage" 745 | title="联动选择框" 746 | dataSource={[ 747 | { label: '发请求1', value: 1 }, 748 | { label: '发请求2', value: 2 }, 749 | ]} 750 | decorator={[FormItem]} 751 | component={[ 752 | Select, 753 | { 754 | style: { 755 | width: 200, 756 | }, 757 | }, 758 | ]} 759 | /> 760 | <Field 761 | name="select" 762 | title="异步选择框" 763 | decorator={[FormItem]} 764 | component={[ 765 | TreeSelect, 766 | { 767 | style: { 768 | width: 200, 769 | }, 770 | }, 771 | ]} 772 | /> 773 | <FormButtonGroup> 774 | <Submit onSubmit={console.log}>提交</Submit> 775 | </FormButtonGroup> 776 | </FormProvider> 777 | ) 778 | ``` 779 | 780 | ## API 781 | 782 | 参考 https://ant.design/components/tree-select-cn/ 783 | ```