This is page 25 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/antd/docs/components/FormDialog.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormDialog 2 | 3 | > Pop-up form, mainly used in simple event to open the form scene 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd' 10 | import { createSchemaField } from '@formily/react' 11 | import { Button } from 'antd' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | Input, 17 | }, 18 | }) 19 | 20 | export default () => { 21 | return ( 22 | <Button 23 | onClick={() => { 24 | const dialog = FormDialog('Pop-up form', () => { 25 | return ( 26 | <FormLayout labelCol={6} wrapperCol={10}> 27 | <SchemaField> 28 | <SchemaField.String 29 | name="aaa" 30 | required 31 | title="input box 1" 32 | x-decorator="FormItem" 33 | x-component="Input" 34 | /> 35 | <SchemaField.String 36 | name="bbb" 37 | required 38 | title="input box 2" 39 | x-decorator="FormItem" 40 | x-component="Input" 41 | /> 42 | <SchemaField.String 43 | name="ccc" 44 | required 45 | title="input box 3" 46 | x-decorator="FormItem" 47 | x-component="Input" 48 | /> 49 | <SchemaField.String 50 | name="ddd" 51 | required 52 | title="input box 4" 53 | x-decorator="FormItem" 54 | x-component="Input" 55 | /> 56 | </SchemaField> 57 | <FormDialog.Footer> 58 | <span 59 | onClick={() => { 60 | dialog.close() 61 | }} 62 | style={{ marginLeft: 4 }} 63 | > 64 | Extended copywriting(Click me to close the form) 65 | </span> 66 | </FormDialog.Footer> 67 | </FormLayout> 68 | ) 69 | }) 70 | dialog 71 | .forOpen((payload, next) => { 72 | setTimeout(() => { 73 | next({ 74 | initialValues: { 75 | aaa: '123', 76 | }, 77 | }) 78 | }, 1000) 79 | }) 80 | .forConfirm((payload, next) => { 81 | setTimeout(() => { 82 | console.log(payload) 83 | next(payload) 84 | }, 1000) 85 | }) 86 | .forCancel((payload, next) => { 87 | setTimeout(() => { 88 | console.log(payload) 89 | next(payload) 90 | }, 1000) 91 | }) 92 | .open({ 93 | initialValues: { 94 | aaa: '123', 95 | }, 96 | }) 97 | .then(console.log) 98 | }} 99 | > 100 | Click me to open the form 101 | </Button> 102 | ) 103 | } 104 | ``` 105 | 106 | ## JSON Schema case 107 | 108 | ```tsx 109 | import React from 'react' 110 | import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd' 111 | import { createSchemaField } from '@formily/react' 112 | import { Button } from 'antd' 113 | 114 | const SchemaField = createSchemaField({ 115 | components: { 116 | FormItem, 117 | Input, 118 | }, 119 | }) 120 | 121 | const schema = { 122 | type: 'object', 123 | properties: { 124 | aaa: { 125 | type: 'string', 126 | title: 'input box 1', 127 | required: true, 128 | 'x-decorator': 'FormItem', 129 | 'x-component': 'Input', 130 | }, 131 | bbb: { 132 | type: 'string', 133 | title: 'input box 2', 134 | required: true, 135 | 'x-decorator': 'FormItem', 136 | 'x-component': 'Input', 137 | }, 138 | ccc: { 139 | type: 'string', 140 | title: 'input box 3', 141 | required: true, 142 | 'x-decorator': 'FormItem', 143 | 'x-component': 'Input', 144 | }, 145 | ddd: { 146 | type: 'string', 147 | title: 'input box 4', 148 | required: true, 149 | 'x-decorator': 'FormItem', 150 | 'x-component': 'Input', 151 | }, 152 | }, 153 | } 154 | 155 | export default () => { 156 | return ( 157 | <Button 158 | onClick={() => { 159 | const dialog = FormDialog('Pop-up form', () => { 160 | return ( 161 | <FormLayout labelCol={6} wrapperCol={10}> 162 | <SchemaField schema={schema} /> 163 | <FormDialog.Footer> 164 | <span 165 | onClick={() => { 166 | dialog.close() 167 | }} 168 | style={{ marginLeft: 4 }} 169 | > 170 | Extended copywriting(Click me to close the form) 171 | </span> 172 | </FormDialog.Footer> 173 | </FormLayout> 174 | ) 175 | }) 176 | dialog 177 | .forOpen((payload, next) => { 178 | setTimeout(() => { 179 | next({ 180 | initialValues: { 181 | aaa: '123', 182 | }, 183 | }) 184 | }, 1000) 185 | }) 186 | .forConfirm((payload, next) => { 187 | setTimeout(() => { 188 | console.log(payload) 189 | next(payload) 190 | }, 1000) 191 | }) 192 | .forCancel((payload, next) => { 193 | setTimeout(() => { 194 | console.log(payload) 195 | next(payload) 196 | }, 1000) 197 | }) 198 | .open({ 199 | initialValues: { 200 | aaa: '123', 201 | }, 202 | }) 203 | .then(console.log) 204 | }} 205 | > 206 | Click me to open the form 207 | </Button> 208 | ) 209 | } 210 | ``` 211 | 212 | ## Pure JSX case 213 | 214 | ```tsx 215 | import React from 'react' 216 | import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd' 217 | import { Field } from '@formily/react' 218 | import { Button } from 'antd' 219 | 220 | export default () => { 221 | return ( 222 | <Button 223 | onClick={() => { 224 | const dialog = FormDialog('Pop-up form', () => { 225 | return ( 226 | <FormLayout labelCol={6} wrapperCol={10}> 227 | <Field 228 | name="aaa" 229 | required 230 | title="input box 1" 231 | decorator={[FormItem]} 232 | component={[Input]} 233 | /> 234 | <Field 235 | name="bbb" 236 | required 237 | title="input box 2" 238 | decorator={[FormItem]} 239 | component={[Input]} 240 | /> 241 | <Field 242 | name="ccc" 243 | required 244 | title="input box 3" 245 | decorator={[FormItem]} 246 | component={[Input]} 247 | /> 248 | <Field 249 | name="ddd" 250 | required 251 | title="input box 4" 252 | decorator={[FormItem]} 253 | component={[Input]} 254 | /> 255 | <FormDialog.Footer> 256 | <span 257 | onClick={() => { 258 | dialog.close() 259 | }} 260 | style={{ marginLeft: 4 }} 261 | > 262 | Extended copywriting(Click me to close the form) 263 | </span> 264 | </FormDialog.Footer> 265 | </FormLayout> 266 | ) 267 | }) 268 | dialog 269 | .forOpen((payload, next) => { 270 | setTimeout(() => { 271 | next({ 272 | initialValues: { 273 | aaa: '123', 274 | }, 275 | }) 276 | }, 1000) 277 | }) 278 | .forConfirm((payload, next) => { 279 | setTimeout(() => { 280 | console.log(payload) 281 | next(payload) 282 | }, 1000) 283 | }) 284 | .forCancel((payload, next) => { 285 | setTimeout(() => { 286 | console.log(payload) 287 | next(payload) 288 | }, 1000) 289 | }) 290 | .open({ 291 | initialValues: { 292 | aaa: '123', 293 | }, 294 | }) 295 | .then(console.log) 296 | }} 297 | > 298 | Click me to open the form 299 | </Button> 300 | ) 301 | } 302 | ``` 303 | 304 | ## API 305 | 306 | ### FormDialog 307 | 308 | ```ts pure 309 | import { IFormProps, Form } from '@formily/core' 310 | 311 | type FormDialogRenderer = 312 | | React.ReactElement 313 | | ((form: Form) => React.ReactElement) 314 | 315 | type ModalTitle = string | number | React.ReactElement 316 | 317 | interface IFormDialog { 318 | forOpen( 319 | middleware: ( 320 | props: IFormProps, 321 | next: (props?: IFormProps) => Promise<any> 322 | ) => any 323 | ): any //Middleware interceptor, can intercept Dialog to open 324 | forConfirm( 325 | middleware: (props: Form, next: (props?: Form) => Promise<any>) => any 326 | ): any //Middleware interceptor, which can intercept Dialog confirmation 327 | forCancel( 328 | middleware: (props: Form, next: (props?: Form) => Promise<any>) => any 329 | ): any //Middleware interceptor, can intercept Dialog to cancel 330 | //Open the pop-up window to receive form attributes, you can pass in initialValues/values/effects etc. 331 | open(props: IFormProps): Promise<any> //return form data 332 | //Close the pop-up window 333 | close(): void 334 | } 335 | 336 | interface IModalProps extends ModalProps { 337 | onOk?: (event: React.MouseEvent<HTMLElement>) => void | boolean // return false can prevent onOk 338 | onCancel?: (event: React.MouseEvent<HTMLElement>) => void | boolean // return false can prevent onCancel 339 | loadingText?: React.ReactNode 340 | } 341 | 342 | interface FormDialog { 343 | (title: IModalProps, id: string, renderer: FormDialogRenderer): IFormDialog 344 | (title: IModalProps, renderer: FormDialogRenderer): IFormDialog 345 | (title: ModalTitle, id: string, renderer: FormDialogRenderer): IFormDialog 346 | (title: ModalTitle, renderer: FormDialogRenderer): IFormDialog 347 | } 348 | ``` 349 | 350 | `ModalProps` type definition reference ant design [Modal API](https://ant.design/components/modal-cn/#API) 351 | 352 | ### FormDialog.Footer 353 | 354 | No attributes, only child nodes are received 355 | 356 | ### FormDialog.Portal 357 | 358 | Receive the optional id attribute, the default value is `form-dialog`, if there are multiple prefixCls in an application, and the prefixCls in the pop-up window of different regions are different, then it is recommended to specify the id as the region-level id 359 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormDialog.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormDialog 2 | 3 | > 弹窗表单,主要用在简单的事件打开表单场景 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React, { createContext, useContext } from 'react' 9 | import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' 10 | import { createSchemaField } from '@formily/react' 11 | import { Button } from '@alifd/next' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | Input, 17 | }, 18 | }) 19 | 20 | const Context = createContext() 21 | 22 | const PortalId = '可以传,也可以不传的ID,默认是form-dialog' 23 | 24 | export default () => { 25 | return ( 26 | <Context.Provider value="自定义上下文可以直接传到弹窗内部,只需要ID一致即可"> 27 | <FormDialog.Portal id={PortalId}> 28 | <Button 29 | onClick={() => { 30 | FormDialog('弹窗表单', PortalId, (form) => { 31 | console.log(useContext(Context)) 32 | return ( 33 | <FormLayout labelCol={6} wrapperCol={10}> 34 | <SchemaField> 35 | <SchemaField.String 36 | name="aaa" 37 | required 38 | title="输入框1" 39 | x-decorator="FormItem" 40 | x-component="Input" 41 | /> 42 | <SchemaField.String 43 | name="bbb" 44 | required 45 | title="输入框2" 46 | x-decorator="FormItem" 47 | x-component="Input" 48 | /> 49 | <SchemaField.String 50 | name="ccc" 51 | required 52 | title="输入框3" 53 | x-decorator="FormItem" 54 | x-component="Input" 55 | /> 56 | <SchemaField.String 57 | name="ddd" 58 | required 59 | title="输入框4" 60 | x-decorator="FormItem" 61 | x-component="Input" 62 | /> 63 | </SchemaField> 64 | <FormDialog.Footer> 65 | <span style={{ marginLeft: 4 }}> 66 | 扩展文案:{form.values.aaa} 67 | </span> 68 | </FormDialog.Footer> 69 | </FormLayout> 70 | ) 71 | }) 72 | .forOpen((payload, next) => { 73 | setTimeout(() => { 74 | next({ 75 | initialValues: { 76 | aaa: '123', 77 | }, 78 | }) 79 | }, 1000) 80 | }) 81 | .forConfirm((payload, next) => { 82 | setTimeout(() => { 83 | console.log(payload) 84 | next(payload) 85 | }, 1000) 86 | }) 87 | .forCancel((payload, next) => { 88 | setTimeout(() => { 89 | console.log(payload) 90 | next(payload) 91 | }, 1000) 92 | }) 93 | .open() 94 | .then(console.log) 95 | .catch(console.error) 96 | }} 97 | > 98 | 点我打开表单 99 | </Button> 100 | </FormDialog.Portal> 101 | </Context.Provider> 102 | ) 103 | } 104 | ``` 105 | 106 | ## JSON Schema 案例 107 | 108 | ```tsx 109 | import React from 'react' 110 | import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' 111 | import { createSchemaField } from '@formily/react' 112 | import { Button } from '@alifd/next' 113 | 114 | const SchemaField = createSchemaField({ 115 | components: { 116 | FormItem, 117 | Input, 118 | }, 119 | }) 120 | 121 | const schema = { 122 | type: 'object', 123 | properties: { 124 | aaa: { 125 | type: 'string', 126 | title: '输入框1', 127 | required: true, 128 | 'x-decorator': 'FormItem', 129 | 'x-component': 'Input', 130 | }, 131 | bbb: { 132 | type: 'string', 133 | title: '输入框2', 134 | required: true, 135 | 'x-decorator': 'FormItem', 136 | 'x-component': 'Input', 137 | }, 138 | ccc: { 139 | type: 'string', 140 | title: '输入框3', 141 | required: true, 142 | 'x-decorator': 'FormItem', 143 | 'x-component': 'Input', 144 | }, 145 | ddd: { 146 | type: 'string', 147 | title: '输入框4', 148 | required: true, 149 | 'x-decorator': 'FormItem', 150 | 'x-component': 'Input', 151 | }, 152 | }, 153 | } 154 | 155 | export default () => { 156 | return ( 157 | <Button 158 | onClick={() => { 159 | FormDialog('弹窗表单', () => { 160 | return ( 161 | <FormLayout labelCol={6} wrapperCol={14}> 162 | <SchemaField schema={schema} /> 163 | <FormDialog.Footer> 164 | <span style={{ marginLeft: 4 }}>扩展文案</span> 165 | </FormDialog.Footer> 166 | </FormLayout> 167 | ) 168 | }) 169 | .open({ 170 | initialValues: { 171 | aaa: '123', 172 | }, 173 | }) 174 | .then(console.log) 175 | }} 176 | > 177 | 点我打开表单 178 | </Button> 179 | ) 180 | } 181 | ``` 182 | 183 | ## 纯 JSX 案例 184 | 185 | ```tsx 186 | import React from 'react' 187 | import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' 188 | import { Field } from '@formily/react' 189 | import { Button } from '@alifd/next' 190 | 191 | export default () => { 192 | return ( 193 | <Button 194 | onClick={() => { 195 | FormDialog('弹窗表单', () => { 196 | return ( 197 | <FormLayout labelCol={6} wrapperCol={14}> 198 | <Field 199 | name="aaa" 200 | required 201 | title="输入框1" 202 | decorator={[FormItem]} 203 | component={[Input]} 204 | /> 205 | <Field 206 | name="bbb" 207 | required 208 | title="输入框2" 209 | decorator={[FormItem]} 210 | component={[Input]} 211 | /> 212 | <Field 213 | name="ccc" 214 | required 215 | title="输入框3" 216 | decorator={[FormItem]} 217 | component={[Input]} 218 | /> 219 | <Field 220 | name="ddd" 221 | required 222 | title="输入框4" 223 | decorator={[FormItem]} 224 | component={[Input]} 225 | /> 226 | <FormDialog.Footer> 227 | <span style={{ marginLeft: 4 }}>扩展文案</span> 228 | </FormDialog.Footer> 229 | </FormLayout> 230 | ) 231 | }) 232 | .open({ 233 | initialValues: { 234 | aaa: '123', 235 | }, 236 | }) 237 | .then(console.log) 238 | }} 239 | > 240 | 点我打开表单 241 | </Button> 242 | ) 243 | } 244 | ``` 245 | 246 | ## 使用 Fusion Context 247 | 248 | ```tsx 249 | import React from 'react' 250 | import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' 251 | import { Field } from '@formily/react' 252 | import { Button, ConfigProvider } from '@alifd/next' 253 | 254 | export default () => { 255 | return ( 256 | <ConfigProvider 257 | locale={{ 258 | Dialog: { 259 | ok: 'OK', 260 | cancel: 'Cancel', 261 | }, 262 | }} 263 | defaultPropsConfig={{ 264 | Dialog: { 265 | isFullScreen: true, 266 | footerActions: ['cancel', 'ok'], 267 | }, 268 | }} 269 | > 270 | <Button 271 | onClick={() => { 272 | FormDialog('弹窗表单', () => { 273 | return ( 274 | <FormLayout labelCol={6} wrapperCol={14}> 275 | <Field 276 | name="aaa" 277 | required 278 | title="输入框1" 279 | decorator={[FormItem]} 280 | component={[Input]} 281 | /> 282 | <Field 283 | name="bbb" 284 | required 285 | title="输入框2" 286 | decorator={[FormItem]} 287 | component={[Input]} 288 | /> 289 | <Field 290 | name="ccc" 291 | required 292 | title="输入框3" 293 | decorator={[FormItem]} 294 | component={[Input]} 295 | /> 296 | <Field 297 | name="ddd" 298 | required 299 | title="输入框4" 300 | decorator={[FormItem]} 301 | component={[Input]} 302 | /> 303 | <FormDialog.Footer> 304 | <span style={{ marginLeft: 4 }}>扩展文案</span> 305 | </FormDialog.Footer> 306 | </FormLayout> 307 | ) 308 | }) 309 | .open({ 310 | initialValues: { 311 | aaa: '123', 312 | }, 313 | }) 314 | .then(console.log) 315 | }} 316 | > 317 | 点我打开表单 318 | </Button> 319 | </ConfigProvider> 320 | ) 321 | } 322 | ``` 323 | 324 | ## API 325 | 326 | ### FormDialog 327 | 328 | ```ts pure 329 | import { IFormProps, Form } from '@formily/core' 330 | 331 | type FormDialogRenderer = 332 | | React.ReactElement 333 | | ((form: Form) => React.ReactElement) 334 | 335 | interface IFormDialog { 336 | forOpen( 337 | middleware: ( 338 | props: IFormProps, 339 | next: (props?: IFormProps) => Promise<any> 340 | ) => any 341 | ): any //中间件拦截器,可以拦截Dialog打开 342 | forConfirm( 343 | middleware: (props: Form, next: (props?: Form) => Promise<any>) => any 344 | ): any //中间件拦截器,可以拦截Dialog确认 345 | forCancel( 346 | middleware: (props: Form, next: (props?: Form) => Promise<any>) => any 347 | ): any //中间件拦截器,可以拦截Dialog取消 348 | //打开弹窗,接收表单属性,可以传入initialValues/values/effects etc. 349 | open(props: IFormProps): Promise<any> //返回表单数据 350 | //关闭弹窗 351 | close(): void 352 | } 353 | 354 | interface IDialogProps extends DialogProps { 355 | onOk?: (event: React.MouseEvent) => void | boolean // return false can prevent onOk 356 | onCancel?: (event: React.MouseEvent) => void | boolean // return false can prevent onCancel 357 | loadingText?: React.ReactText 358 | } 359 | 360 | interface FormDialog { 361 | (title: IDialogProps, id: string, renderer: FormDialogRenderer): IFormDialog 362 | (title: IDialogProps, renderer: FormDialogRenderer): IFormDialog 363 | (title: ModalTitle, id: string, renderer: FormDialogRenderer): IFormDialog 364 | (title: ModalTitle, renderer: FormDialogRenderer): IFormDialog 365 | } 366 | ``` 367 | 368 | `DialogProps` 类型定义参考 fusion [Dialog API](https://fusion.design/pc/component/dialog?themeid=2#API) 369 | 370 | ### FormDialog.Footer 371 | 372 | 无属性,只接收子节点 373 | 374 | ### FormDialog.Portal 375 | 376 | 接收可选的 id 属性,默认值为`form-dialog`,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id 377 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormDrawer.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormDrawer 2 | 3 | > 抽屉表单,主要用在简单的事件打开表单场景 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | FormDrawer, 11 | FormItem, 12 | Input, 13 | Submit, 14 | Reset, 15 | FormButtonGroup, 16 | FormLayout, 17 | } from '@formily/next' 18 | import { createSchemaField } from '@formily/react' 19 | import { Button } from '@alifd/next' 20 | 21 | const SchemaField = createSchemaField({ 22 | components: { 23 | FormItem, 24 | Input, 25 | }, 26 | }) 27 | 28 | export default () => { 29 | return ( 30 | <Button 31 | onClick={() => { 32 | FormDrawer('抽屉表单', () => { 33 | return ( 34 | <FormLayout labelCol={6} wrapperCol={10}> 35 | <SchemaField> 36 | <SchemaField.String 37 | name="aaa" 38 | required 39 | title="输入框1" 40 | x-decorator="FormItem" 41 | x-component="Input" 42 | /> 43 | <SchemaField.String 44 | name="bbb" 45 | required 46 | title="输入框2" 47 | x-decorator="FormItem" 48 | x-component="Input" 49 | /> 50 | <SchemaField.String 51 | name="ccc" 52 | required 53 | title="输入框3" 54 | x-decorator="FormItem" 55 | x-component="Input" 56 | /> 57 | <SchemaField.String 58 | name="ddd" 59 | required 60 | title="输入框4" 61 | x-decorator="FormItem" 62 | x-component="Input" 63 | /> 64 | </SchemaField> 65 | <FormDrawer.Footer> 66 | <FormButtonGroup align="right"> 67 | <Submit 68 | onSubmit={() => { 69 | return new Promise((resolve) => { 70 | setTimeout(resolve, 1000) 71 | }) 72 | }} 73 | > 74 | 提交 75 | </Submit> 76 | <Reset>重置</Reset> 77 | </FormButtonGroup> 78 | </FormDrawer.Footer> 79 | </FormLayout> 80 | ) 81 | }) 82 | .forOpen((props, next) => { 83 | setTimeout(() => { 84 | next() 85 | }, 1000) 86 | }) 87 | .open({ 88 | initialValues: { 89 | aaa: '123', 90 | }, 91 | }) 92 | .then(console.log) 93 | }} 94 | > 95 | 点我打开表单 96 | </Button> 97 | ) 98 | } 99 | ``` 100 | 101 | ## JSON Schema 案例 102 | 103 | ```tsx 104 | import React from 'react' 105 | import { 106 | FormDrawer, 107 | FormItem, 108 | Input, 109 | Submit, 110 | Reset, 111 | FormButtonGroup, 112 | FormLayout, 113 | } from '@formily/next' 114 | import { createSchemaField } from '@formily/react' 115 | import { Button } from '@alifd/next' 116 | 117 | const SchemaField = createSchemaField({ 118 | components: { 119 | FormItem, 120 | Input, 121 | }, 122 | }) 123 | 124 | const schema = { 125 | type: 'object', 126 | properties: { 127 | aaa: { 128 | type: 'string', 129 | title: '输入框1', 130 | required: true, 131 | 'x-decorator': 'FormItem', 132 | 'x-component': 'Input', 133 | }, 134 | bbb: { 135 | type: 'string', 136 | title: '输入框2', 137 | required: true, 138 | 'x-decorator': 'FormItem', 139 | 'x-component': 'Input', 140 | }, 141 | ccc: { 142 | type: 'string', 143 | title: '输入框3', 144 | required: true, 145 | 'x-decorator': 'FormItem', 146 | 'x-component': 'Input', 147 | }, 148 | ddd: { 149 | type: 'string', 150 | title: '输入框4', 151 | required: true, 152 | 'x-decorator': 'FormItem', 153 | 'x-component': 'Input', 154 | }, 155 | }, 156 | } 157 | 158 | export default () => { 159 | return ( 160 | <Button 161 | onClick={() => { 162 | FormDrawer('弹窗表单', () => { 163 | return ( 164 | <FormLayout labelCol={6} wrapperCol={14}> 165 | <SchemaField schema={schema} /> 166 | <FormDrawer.Footer> 167 | <FormButtonGroup align="right"> 168 | <Submit 169 | onSubmit={() => { 170 | return new Promise((resolve) => { 171 | setTimeout(resolve, 1000) 172 | }) 173 | }} 174 | > 175 | 提交 176 | </Submit> 177 | <Reset>重置</Reset> 178 | </FormButtonGroup> 179 | </FormDrawer.Footer> 180 | </FormLayout> 181 | ) 182 | }) 183 | .open({ 184 | initialValues: { 185 | aaa: '123', 186 | }, 187 | }) 188 | .then(console.log) 189 | }} 190 | > 191 | 点我打开表单 192 | </Button> 193 | ) 194 | } 195 | ``` 196 | 197 | ## 纯 JSX 案例 198 | 199 | ```tsx 200 | import React from 'react' 201 | import { 202 | FormDrawer, 203 | FormItem, 204 | Input, 205 | Submit, 206 | Reset, 207 | FormButtonGroup, 208 | FormLayout, 209 | } from '@formily/next' 210 | import { Field } from '@formily/react' 211 | import { Button } from '@alifd/next' 212 | 213 | export default () => { 214 | return ( 215 | <Button 216 | onClick={() => { 217 | FormDrawer('弹窗表单', () => { 218 | return ( 219 | <FormLayout labelCol={6} wrapperCol={14}> 220 | <Field 221 | name="aaa" 222 | required 223 | title="输入框1" 224 | decorator={[FormItem]} 225 | component={[Input]} 226 | /> 227 | <Field 228 | name="bbb" 229 | required 230 | title="输入框2" 231 | decorator={[FormItem]} 232 | component={[Input]} 233 | /> 234 | <Field 235 | name="ccc" 236 | required 237 | title="输入框3" 238 | decorator={[FormItem]} 239 | component={[Input]} 240 | /> 241 | <Field 242 | name="ddd" 243 | required 244 | title="输入框4" 245 | decorator={[FormItem]} 246 | component={[Input]} 247 | /> 248 | <FormDrawer.Footer> 249 | <FormButtonGroup align="right"> 250 | <Submit 251 | onSubmit={() => { 252 | return new Promise((resolve) => { 253 | setTimeout(resolve, 1000) 254 | }) 255 | }} 256 | > 257 | 提交 258 | </Submit> 259 | <Reset>重置</Reset> 260 | </FormButtonGroup> 261 | </FormDrawer.Footer> 262 | </FormLayout> 263 | ) 264 | }) 265 | .open({ 266 | initialValues: { 267 | aaa: '123', 268 | }, 269 | }) 270 | .then(console.log) 271 | }} 272 | > 273 | 点我打开表单 274 | </Button> 275 | ) 276 | } 277 | ``` 278 | 279 | ## 使用 Fusion Context 280 | 281 | ```tsx 282 | import React from 'react' 283 | import { 284 | FormDrawer, 285 | FormItem, 286 | Input, 287 | Submit, 288 | Reset, 289 | FormButtonGroup, 290 | FormLayout, 291 | } from '@formily/next' 292 | import { Field } from '@formily/react' 293 | import { Button, ConfigProvider } from '@alifd/next' 294 | 295 | export default () => { 296 | return ( 297 | <ConfigProvider 298 | defaultPropsConfig={{ 299 | Drawer: {}, 300 | }} 301 | > 302 | <Button 303 | onClick={() => { 304 | FormDrawer('弹窗表单', () => { 305 | return ( 306 | <FormLayout labelCol={6} wrapperCol={14}> 307 | <Field 308 | name="aaa" 309 | required 310 | title="输入框1" 311 | decorator={[FormItem]} 312 | component={[Input]} 313 | /> 314 | <Field 315 | name="bbb" 316 | required 317 | title="输入框2" 318 | decorator={[FormItem]} 319 | component={[Input]} 320 | /> 321 | <Field 322 | name="ccc" 323 | required 324 | title="输入框3" 325 | decorator={[FormItem]} 326 | component={[Input]} 327 | /> 328 | <Field 329 | name="ddd" 330 | required 331 | title="输入框4" 332 | decorator={[FormItem]} 333 | component={[Input]} 334 | /> 335 | <FormDrawer.Footer> 336 | <FormButtonGroup align="right"> 337 | <Submit 338 | onSubmit={() => { 339 | return new Promise((resolve) => { 340 | setTimeout(resolve, 1000) 341 | }) 342 | }} 343 | > 344 | 提交 345 | </Submit> 346 | <Reset>重置</Reset> 347 | </FormButtonGroup> 348 | </FormDrawer.Footer> 349 | </FormLayout> 350 | ) 351 | }) 352 | .open({ 353 | initialValues: { 354 | aaa: '123', 355 | }, 356 | }) 357 | .then(console.log) 358 | }} 359 | > 360 | 点我打开表单 361 | </Button> 362 | </ConfigProvider> 363 | ) 364 | } 365 | ``` 366 | 367 | ## API 368 | 369 | ### FormDrawer 370 | 371 | ## API 372 | 373 | ### FormDrawer 374 | 375 | ```ts pure 376 | import { IFormProps, Form } from '@formily/core' 377 | 378 | type FormDrawerRenderer = 379 | | React.ReactElement 380 | | ((form: Form) => React.ReactElement) 381 | 382 | interface IFormDrawer { 383 | forOpen( 384 | middleware: ( 385 | props: IFormProps, 386 | next: (props?: IFormProps) => Promise<any> 387 | ) => any 388 | ): any //中间件拦截器,可以拦截Drawer打开 389 | //打开弹窗,接收表单属性,可以传入initialValues/values/effects etc. 390 | open(props: IFormProps): Promise<any> //返回表单数据 391 | //关闭弹窗 392 | close(): void 393 | } 394 | 395 | interface IDrawerProps extends DrawerProps { 396 | onClose?: (reason: string, e: React.MouseEvent) => void | boolean // return false can prevent onClose 397 | loadingText?: React.ReactNode 398 | } 399 | 400 | interface FormDrawer { 401 | (title: IDrawerProps, id: string, renderer: FormDrawerRenderer): IFormDrawer 402 | (title: IDrawerProps, renderer: FormDrawerRenderer): IFormDrawer 403 | (title: ModalTitle, id: string, renderer: FormDrawerRenderer): IFormDrawer 404 | (title: ModalTitle, renderer: FormDrawerRenderer): IFormDrawer 405 | } 406 | ``` 407 | 408 | `DrawerProps` 类型定义参考 fusion [Drawer API](https://fusion.design/pc/component/drawer?themeid=2#API) 409 | 410 | ### FormDrawer.Footer 411 | 412 | 无属性,只接收子节点 413 | 414 | ### FormDrawer.Portal 415 | 416 | 接收可选的 id 属性,默认值为`form-drawer`,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id 417 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Select.md: -------------------------------------------------------------------------------- ```markdown 1 | # Select 2 | 3 | > Drop-down box components 4 | 5 | ## Markup Schema synchronization data source case 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Select, 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="select box" 28 | x-decorator="FormItem" 29 | x-component="Select" 30 | enum={[ 31 | { label: 'Option 1', value: 1 }, 32 | { label: 'Option 2', value: 2 }, 33 | ]} 34 | x-component-props={{ 35 | style: { 36 | width: 120, 37 | }, 38 | }} 39 | /> 40 | </SchemaField> 41 | <FormButtonGroup> 42 | <Submit onSubmit={console.log}>Submit</Submit> 43 | </FormButtonGroup> 44 | </FormProvider> 45 | ) 46 | ``` 47 | 48 | ## Markup Schema Asynchronous Linkage Data Source Case 49 | 50 | ```tsx 51 | import React from 'react' 52 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' 53 | import { createForm, onFieldReact, FormPathPattern, Field } from '@formily/core' 54 | import { FormProvider, createSchemaField } from '@formily/react' 55 | import { action } from '@formily/reactive' 56 | 57 | const SchemaField = createSchemaField({ 58 | components: { 59 | Select, 60 | FormItem, 61 | }, 62 | }) 63 | 64 | const useAsyncDataSource = ( 65 | pattern: FormPathPattern, 66 | service: (field: Field) => Promise<{ label: string; value: any }[]> 67 | ) => { 68 | onFieldReact(pattern, (field) => { 69 | field.loading = true 70 | service(field).then( 71 | action.bound((data) => { 72 | field.dataSource = data 73 | field.loading = false 74 | }) 75 | ) 76 | }) 77 | } 78 | 79 | const form = createForm({ 80 | effects: () => { 81 | useAsyncDataSource('select', async (field) => { 82 | const linkage = field.query('linkage').get('value') 83 | if (!linkage) return [] 84 | return new Promise((resolve) => { 85 | setTimeout(() => { 86 | if (linkage === 1) { 87 | resolve([ 88 | { 89 | label: 'AAA', 90 | value: 'aaa', 91 | }, 92 | { 93 | label: 'BBB', 94 | value: 'ccc', 95 | }, 96 | ]) 97 | } else if (linkage === 2) { 98 | resolve([ 99 | { 100 | label: 'CCC', 101 | value: 'ccc', 102 | }, 103 | { 104 | label: 'DDD', 105 | value: 'ddd', 106 | }, 107 | ]) 108 | } 109 | }, 1500) 110 | }) 111 | }) 112 | }, 113 | }) 114 | 115 | export default () => ( 116 | <FormProvider form={form}> 117 | <SchemaField> 118 | <SchemaField.Number 119 | name="linkage" 120 | title="Linkage selection box" 121 | x-decorator="FormItem" 122 | x-component="Select" 123 | enum={[ 124 | { label: 'Request 1', value: 1 }, 125 | { label: 'Request 2', value: 2 }, 126 | ]} 127 | x-component-props={{ 128 | style: { 129 | width: 120, 130 | }, 131 | }} 132 | /> 133 | <SchemaField.String 134 | name="select" 135 | title="Asynchronous select box" 136 | x-decorator="FormItem" 137 | x-component="Select" 138 | x-component-props={{ 139 | style: { 140 | width: 120, 141 | }, 142 | }} 143 | /> 144 | </SchemaField> 145 | <FormButtonGroup> 146 | <Submit onSubmit={console.log}>Submit</Submit> 147 | </FormButtonGroup> 148 | </FormProvider> 149 | ) 150 | ``` 151 | 152 | ## JSON Schema synchronization data source case 153 | 154 | ```tsx 155 | import React from 'react' 156 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' 157 | import { createForm } from '@formily/core' 158 | import { FormProvider, createSchemaField } from '@formily/react' 159 | 160 | const SchemaField = createSchemaField({ 161 | components: { 162 | Select, 163 | FormItem, 164 | }, 165 | }) 166 | 167 | const form = createForm() 168 | 169 | const schema = { 170 | type: 'object', 171 | properties: { 172 | select: { 173 | type: 'string', 174 | title: 'Select box', 175 | 'x-decorator': 'FormItem', 176 | 'x-component': 'Select', 177 | enum: [ 178 | { label: 'Option 1', value: 1 }, 179 | { label: 'Option 2', value: 2 }, 180 | ], 181 | 'x-component-props': { 182 | style: { 183 | width: 120, 184 | }, 185 | }, 186 | }, 187 | }, 188 | } 189 | 190 | export default () => ( 191 | <FormProvider form={form}> 192 | <SchemaField schema={schema} /> 193 | <FormButtonGroup> 194 | <Submit onSubmit={console.log}>Submit</Submit> 195 | </FormButtonGroup> 196 | </FormProvider> 197 | ) 198 | ``` 199 | 200 | ## JSON Schema asynchronous linkage data source case 201 | 202 | ```tsx 203 | import React from 'react' 204 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' 205 | import { createForm } from '@formily/core' 206 | import { FormProvider, createSchemaField } from '@formily/react' 207 | import { action } from '@formily/reactive' 208 | 209 | const SchemaField = createSchemaField({ 210 | components: { 211 | Select, 212 | FormItem, 213 | }, 214 | }) 215 | 216 | const loadData = async (field) => { 217 | const linkage = field.query('linkage').get('value') 218 | if (!linkage) return [] 219 | return new Promise((resolve) => { 220 | setTimeout(() => { 221 | if (linkage === 1) { 222 | resolve([ 223 | { 224 | label: 'AAA', 225 | value: 'aaa', 226 | }, 227 | { 228 | label: 'BBB', 229 | value: 'ccc', 230 | }, 231 | ]) 232 | } else if (linkage === 2) { 233 | resolve([ 234 | { 235 | label: 'CCC', 236 | value: 'ccc', 237 | }, 238 | { 239 | label: 'DDD', 240 | value: 'ddd', 241 | }, 242 | ]) 243 | } 244 | }, 1500) 245 | }) 246 | } 247 | 248 | const useAsyncDataSource = (service) => (field) => { 249 | field.loading = true 250 | service(field).then( 251 | action.bound((data) => { 252 | field.dataSource = data 253 | field.loading = false 254 | }) 255 | ) 256 | } 257 | 258 | const form = createForm() 259 | 260 | const schema = { 261 | type: 'object', 262 | properties: { 263 | linkage: { 264 | type: 'string', 265 | title: 'Linkage selection box', 266 | enum: [ 267 | { label: 'Request 1', value: 1 }, 268 | { label: 'Request 2', value: 2 }, 269 | ], 270 | 'x-decorator': 'FormItem', 271 | 'x-component': 'Select', 272 | 'x-component-props': { 273 | style: { 274 | width: 120, 275 | }, 276 | }, 277 | }, 278 | select: { 279 | type: 'string', 280 | title: 'Asynchronous selection box', 281 | 'x-decorator': 'FormItem', 282 | 'x-component': 'Select', 283 | 'x-component-props': { 284 | style: { 285 | width: 120, 286 | }, 287 | }, 288 | 'x-reactions': ['{{useAsyncDataSource(loadData)}}'], 289 | }, 290 | }, 291 | } 292 | 293 | export default () => ( 294 | <FormProvider form={form}> 295 | <SchemaField schema={schema} scope={{ useAsyncDataSource, loadData }} /> 296 | <FormButtonGroup> 297 | <Submit onSubmit={console.log}>Submit</Submit> 298 | </FormButtonGroup> 299 | </FormProvider> 300 | ) 301 | ``` 302 | 303 | ## Pure JSX synchronization data source case 304 | 305 | ```tsx 306 | import React from 'react' 307 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' 308 | import { createForm } from '@formily/core' 309 | import { FormProvider, Field } from '@formily/react' 310 | 311 | const form = createForm() 312 | 313 | export default () => ( 314 | <FormProvider form={form}> 315 | <Field 316 | name="select" 317 | title="select box" 318 | dataSource={[ 319 | { label: 'Option 1', value: 1 }, 320 | { label: 'Option 2', value: 2 }, 321 | ]} 322 | decorator={[FormItem]} 323 | component={[ 324 | Select, 325 | { 326 | style: { 327 | width: 120, 328 | }, 329 | }, 330 | ]} 331 | /> 332 | <FormButtonGroup> 333 | <Submit onSubmit={console.log}>Submit</Submit> 334 | </FormButtonGroup> 335 | </FormProvider> 336 | ) 337 | ``` 338 | 339 | ## Pure JSX asynchronous linkage data source case 340 | 341 | ```tsx 342 | import React from 'react' 343 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' 344 | import { 345 | createForm, 346 | onFieldReact, 347 | FormPathPattern, 348 | FieldType, 349 | } from '@formily/core' 350 | import { FormProvider, Field } from '@formily/react' 351 | import { action } from '@formily/reactive' 352 | 353 | const useAsyncDataSource = ( 354 | pattern: FormPathPattern, 355 | service: (field: FieldType) => Promise<{ label: string; value: any }[]> 356 | ) => { 357 | onFieldReact(pattern, (field) => { 358 | field.loading = true 359 | service(field).then( 360 | action.bound((data) => { 361 | field.dataSource = data 362 | field.loading = false 363 | }) 364 | ) 365 | }) 366 | } 367 | 368 | const form = createForm({ 369 | effects: () => { 370 | useAsyncDataSource('select', async (field) => { 371 | const linkage = field.query('linkage').get('value') 372 | if (!linkage) return [] 373 | return new Promise((resolve) => { 374 | setTimeout(() => { 375 | if (linkage === 1) { 376 | resolve([ 377 | { 378 | label: 'AAA', 379 | value: 'aaa', 380 | }, 381 | { 382 | label: 'BBB', 383 | value: 'ccc', 384 | }, 385 | ]) 386 | } else if (linkage === 2) { 387 | resolve([ 388 | { 389 | label: 'CCC', 390 | value: 'ccc', 391 | }, 392 | { 393 | label: 'DDD', 394 | value: 'ddd', 395 | }, 396 | ]) 397 | } 398 | }, 1500) 399 | }) 400 | }) 401 | }, 402 | }) 403 | 404 | export default () => ( 405 | <FormProvider form={form}> 406 | <Field 407 | name="linkage" 408 | title="Linkage selection box" 409 | dataSource={[ 410 | { label: 'Request 1', value: 1 }, 411 | { label: 'Request 2', value: 2 }, 412 | ]} 413 | decorator={[FormItem]} 414 | component={[ 415 | Select, 416 | { 417 | style: { 418 | width: 120, 419 | }, 420 | }, 421 | ]} 422 | /> 423 | <Field 424 | name="select" 425 | title="Asynchronous select box" 426 | decorator={[FormItem]} 427 | component={[ 428 | Select, 429 | { 430 | style: { 431 | width: 120, 432 | }, 433 | }, 434 | ]} 435 | /> 436 | <FormButtonGroup> 437 | <Submit onSubmit={console.log}>Submit</Submit> 438 | </FormButtonGroup> 439 | </FormProvider> 440 | ) 441 | ``` 442 | 443 | ## API 444 | 445 | Reference https://fusion.design/pc/component/basic/select 446 | ``` -------------------------------------------------------------------------------- /packages/antd/src/preview-text/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { createContext, useContext } from 'react' 2 | import { isArr, toArr, isValid } from '@formily/shared' 3 | import { Field } from '@formily/core' 4 | import { observer, useField } from '@formily/react' 5 | import { InputProps } from 'antd/lib/input' 6 | import { InputNumberProps } from 'antd/lib/input-number' 7 | import { SelectProps } from 'antd/lib/select' 8 | import { TreeSelectProps } from 'antd/lib/tree-select' 9 | import { CascaderProps, DefaultOptionType } from 'antd/lib/cascader' 10 | import { 11 | DatePickerProps, 12 | RangePickerProps as DateRangePickerProps, 13 | } from 'antd/lib/date-picker' 14 | import { TimePickerProps, TimeRangePickerProps } from 'antd/lib/time-picker' 15 | import { Tag, Space } from 'antd' 16 | import cls from 'classnames' 17 | import { formatMomentValue, usePrefixCls } from '../__builtins__' 18 | 19 | const PlaceholderContext = createContext<React.ReactNode>('N/A') 20 | 21 | const Placeholder = PlaceholderContext.Provider 22 | 23 | const usePlaceholder = (value?: any) => { 24 | const placeholder = useContext(PlaceholderContext) || 'N/A' 25 | return isValid(value) && value !== '' ? value : placeholder 26 | } 27 | 28 | const Input: React.FC<React.PropsWithChildren<InputProps>> = (props) => { 29 | const prefixCls = usePrefixCls('form-text', props) 30 | return ( 31 | <Space className={cls(prefixCls, props.className)} style={props.style}> 32 | {props.addonBefore} 33 | {props.prefix} 34 | {usePlaceholder(props.value)} 35 | {props.suffix} 36 | {props.addonAfter} 37 | </Space> 38 | ) 39 | } 40 | 41 | const NumberPicker: React.FC<React.PropsWithChildren<InputNumberProps>> = ( 42 | props 43 | ) => { 44 | const prefixCls = usePrefixCls('form-text', props) 45 | return ( 46 | <Space className={cls(prefixCls, props.className)} style={props.style}> 47 | {props.addonBefore} 48 | {props.prefix} 49 | {usePlaceholder( 50 | props.formatter 51 | ? props.formatter(String(props.value), { 52 | userTyping: false, 53 | input: '', 54 | }) 55 | : props.value 56 | )} 57 | {props['suffix']} 58 | {props.addonAfter} 59 | </Space> 60 | ) 61 | } 62 | 63 | const Select: React.FC<React.PropsWithChildren<SelectProps<any>>> = observer( 64 | (props) => { 65 | const field = useField<Field>() 66 | const prefixCls = usePrefixCls('form-text', props) 67 | const dataSource: any[] = field?.dataSource?.length 68 | ? field.dataSource 69 | : props?.options?.length 70 | ? props.options 71 | : [] 72 | const placeholder = usePlaceholder() 73 | const getSelected = () => { 74 | const value = props.value 75 | if (props.mode === 'multiple' || props.mode === 'tags') { 76 | if (props.labelInValue) { 77 | return isArr(value) ? value : [] 78 | } else { 79 | return isArr(value) 80 | ? value.map((val) => ({ label: val, value: val })) 81 | : [] 82 | } 83 | } else { 84 | if (props.labelInValue) { 85 | return isValid(value) ? [value] : [] 86 | } else { 87 | return isValid(value) ? [{ label: value, value }] : [] 88 | } 89 | } 90 | } 91 | 92 | const getLabel = (target: any) => { 93 | const labelKey = props.fieldNames?.label || 'label' 94 | return ( 95 | dataSource?.find((item) => { 96 | const valueKey = props.fieldNames?.value || 'value' 97 | return item[valueKey] == target?.value 98 | })?.[labelKey] || 99 | target.label || 100 | placeholder 101 | ) 102 | } 103 | 104 | const getLabels = () => { 105 | const selected = getSelected() 106 | if (!selected.length) return placeholder 107 | if (selected.length === 1) return getLabel(selected[0]) 108 | return selected.map((item, key) => { 109 | return <Tag key={key}>{getLabel(item)}</Tag> 110 | }) 111 | } 112 | return ( 113 | <div className={cls(prefixCls, props.className)} style={props.style}> 114 | {getLabels()} 115 | </div> 116 | ) 117 | } 118 | ) 119 | 120 | const TreeSelect: React.FC<React.PropsWithChildren<TreeSelectProps<any>>> = 121 | observer((props) => { 122 | const field = useField<Field>() 123 | const placeholder = usePlaceholder() 124 | const prefixCls = usePrefixCls('form-text', props) 125 | const dataSource = field?.dataSource?.length 126 | ? field.dataSource 127 | : props?.treeData?.length 128 | ? props.treeData 129 | : [] 130 | const getSelected = () => { 131 | const value = props.value 132 | if (props.multiple) { 133 | if (props.labelInValue) { 134 | return isArr(value) ? value : [] 135 | } else { 136 | return isArr(value) 137 | ? value.map((val) => ({ label: val, value: val })) 138 | : [] 139 | } 140 | } else { 141 | if (props.labelInValue) { 142 | return value ? [value] : [] 143 | } else { 144 | return value ? [{ label: value, value }] : [] 145 | } 146 | } 147 | } 148 | 149 | const findLabel = ( 150 | value: any, 151 | dataSource: any[], 152 | treeNodeLabelProp?: string 153 | ) => { 154 | for (let i = 0; i < dataSource?.length; i++) { 155 | const item = dataSource[i] 156 | if (item?.value === value) { 157 | return item?.label ?? item[treeNodeLabelProp] 158 | } else { 159 | const childLabel = findLabel(value, item?.children, treeNodeLabelProp) 160 | if (childLabel) return childLabel 161 | } 162 | } 163 | } 164 | 165 | const getLabels = () => { 166 | const selected = getSelected() 167 | if (!selected?.length) return <Tag>{placeholder}</Tag> 168 | return selected.map(({ value, label }, key) => { 169 | return ( 170 | <Tag key={key}> 171 | {findLabel(value, dataSource, props.treeNodeLabelProp) || 172 | label || 173 | placeholder} 174 | </Tag> 175 | ) 176 | }) 177 | } 178 | return ( 179 | <div className={cls(prefixCls, props.className)} style={props.style}> 180 | {getLabels()} 181 | </div> 182 | ) 183 | }) 184 | 185 | const Cascader: React.FC<React.PropsWithChildren<CascaderProps<any>>> = 186 | observer((props) => { 187 | const field = useField<Field>() 188 | const placeholder = usePlaceholder() 189 | const prefixCls = usePrefixCls('form-text', props) 190 | const dataSource: any[] = field?.dataSource?.length 191 | ? field.dataSource 192 | : props?.options?.length 193 | ? props.options 194 | : [] 195 | const findSelectedItem = ( 196 | items: DefaultOptionType[], 197 | val: string | number 198 | ) => { 199 | return items.find((item) => item.value == val) 200 | } 201 | const findSelectedItems = ( 202 | sources: DefaultOptionType[], 203 | selectedValues: Array<string[] | number[]> 204 | ): Array<any[]> => { 205 | return selectedValues.map((value) => { 206 | const result: Array<DefaultOptionType> = [] 207 | let items = sources 208 | value.forEach((val) => { 209 | const selectedItem = findSelectedItem(items, val) 210 | result.push({ 211 | label: selectedItem?.label ?? '', 212 | value: selectedItem?.value, 213 | }) 214 | items = selectedItem?.children ?? [] 215 | }) 216 | return result 217 | }) 218 | } 219 | const getSelected = () => { 220 | const val = toArr(props.value) 221 | // unified conversion to multi selection mode 222 | return props.multiple ? val : [val] 223 | } 224 | const getLabels = () => { 225 | const selected = getSelected() 226 | const values = findSelectedItems(dataSource, selected) 227 | const labels = values 228 | .map((val: Array<DefaultOptionType>) => { 229 | return val.map((item) => item.label).join('/') 230 | }) 231 | .join(' ') 232 | return labels || placeholder 233 | } 234 | return ( 235 | <div className={cls(prefixCls, props.className)} style={props.style}> 236 | {getLabels()} 237 | </div> 238 | ) 239 | }) 240 | 241 | const DatePicker: React.FC<React.PropsWithChildren<DatePickerProps>> = ( 242 | props 243 | ) => { 244 | const placeholder = usePlaceholder() 245 | const prefixCls = usePrefixCls('form-text', props) 246 | const getLabels = () => { 247 | const labels = formatMomentValue(props.value, props.format, placeholder) 248 | return isArr(labels) ? labels.join('~') : labels 249 | } 250 | return <div className={cls(prefixCls, props.className)}>{getLabels()}</div> 251 | } 252 | 253 | const DateRangePicker: React.FC< 254 | React.PropsWithChildren<DateRangePickerProps> 255 | > = (props) => { 256 | const placeholder = usePlaceholder() 257 | const prefixCls = usePrefixCls('form-text', props) 258 | const getLabels = () => { 259 | const labels = formatMomentValue(props.value, props.format, placeholder) 260 | return isArr(labels) ? labels.join('~') : labels 261 | } 262 | return ( 263 | <div className={cls(prefixCls, props.className)} style={props.style}> 264 | {getLabels()} 265 | </div> 266 | ) 267 | } 268 | 269 | const TimePicker: React.FC<React.PropsWithChildren<TimePickerProps>> = ( 270 | props 271 | ) => { 272 | const placeholder = usePlaceholder() 273 | const prefixCls = usePrefixCls('form-text', props) 274 | const getLabels = () => { 275 | const labels = formatMomentValue(props.value, props.format, placeholder) 276 | return isArr(labels) ? labels.join('~') : labels 277 | } 278 | return ( 279 | <div className={cls(prefixCls, props.className)} style={props.style}> 280 | {getLabels()} 281 | </div> 282 | ) 283 | } 284 | 285 | const TimeRangePicker: React.FC< 286 | React.PropsWithChildren<TimeRangePickerProps> 287 | > = (props) => { 288 | const placeholder = usePlaceholder() 289 | const prefixCls = usePrefixCls('form-text', props) 290 | const getLabels = () => { 291 | const labels = formatMomentValue(props.value, props.format, placeholder) 292 | return isArr(labels) ? labels.join('~') : labels 293 | } 294 | return ( 295 | <div className={cls(prefixCls, props.className)} style={props.style}> 296 | {getLabels()} 297 | </div> 298 | ) 299 | } 300 | 301 | const Text = (props: React.PropsWithChildren<any>) => { 302 | const prefixCls = usePrefixCls('form-text', props) 303 | 304 | return ( 305 | <div className={cls(prefixCls, props.className)} style={props.style}> 306 | {usePlaceholder(props.value)} 307 | </div> 308 | ) 309 | } 310 | 311 | Text.Input = Input 312 | Text.Select = Select 313 | Text.TreeSelect = TreeSelect 314 | Text.Cascader = Cascader 315 | Text.DatePicker = DatePicker 316 | Text.DateRangePicker = DateRangePicker 317 | Text.TimePicker = TimePicker 318 | Text.TimeRangePicker = TimeRangePicker 319 | Text.Placeholder = Placeholder 320 | Text.usePlaceholder = usePlaceholder 321 | Text.NumberPicker = NumberPicker 322 | 323 | export const PreviewText = Text 324 | 325 | export default PreviewText 326 | ``` -------------------------------------------------------------------------------- /docs/guide/advanced/calculator.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # 实现联动计算器 2 | 3 | 联动计算器,主要用于在填写表单的过程中做求值汇总,在 Formily1.x 中实现这类需求的成本非常非常高,在 2.x 中,我们可以借助 reactions 轻松实现 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Form, 11 | FormItem, 12 | NumberPicker, 13 | ArrayTable, 14 | Editable, 15 | Input, 16 | FormButtonGroup, 17 | Submit, 18 | } from '@formily/antd' 19 | import { createForm } from '@formily/core' 20 | import { createSchemaField } from '@formily/react' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | FormItem, 25 | Editable, 26 | Input, 27 | NumberPicker, 28 | ArrayTable, 29 | }, 30 | }) 31 | 32 | const form = createForm() 33 | 34 | export default () => { 35 | return ( 36 | <Form form={form} layout="vertical"> 37 | <SchemaField> 38 | <SchemaField.Array 39 | name="projects" 40 | title="Projects" 41 | x-decorator="FormItem" 42 | x-component="ArrayTable" 43 | > 44 | <SchemaField.Object> 45 | <SchemaField.Void 46 | x-component="ArrayTable.Column" 47 | x-component-props={{ width: 50, title: 'Sort', align: 'center' }} 48 | > 49 | <SchemaField.Void 50 | x-decorator="FormItem" 51 | x-component="ArrayTable.SortHandle" 52 | /> 53 | </SchemaField.Void> 54 | <SchemaField.Void 55 | x-component="ArrayTable.Column" 56 | x-component-props={{ width: 80, title: 'Index', align: 'center' }} 57 | > 58 | <SchemaField.String 59 | x-decorator="FormItem" 60 | x-component="ArrayTable.Index" 61 | /> 62 | </SchemaField.Void> 63 | <SchemaField.Void 64 | x-component="ArrayTable.Column" 65 | x-component-props={{ title: 'Price' }} 66 | > 67 | <SchemaField.Number 68 | name="price" 69 | x-decorator="Editable" 70 | required 71 | x-component="NumberPicker" 72 | x-component-props={{ 73 | addonAfter: '$', 74 | }} 75 | default={0} 76 | /> 77 | </SchemaField.Void> 78 | <SchemaField.Void 79 | x-component="ArrayTable.Column" 80 | x-component-props={{ title: 'Count' }} 81 | > 82 | <SchemaField.Number 83 | name="count" 84 | x-decorator="Editable" 85 | required 86 | x-component="NumberPicker" 87 | default={0} 88 | /> 89 | </SchemaField.Void> 90 | <SchemaField.Void 91 | x-component="ArrayTable.Column" 92 | x-component-props={{ title: 'Total' }} 93 | > 94 | <SchemaField.Number 95 | x-decorator="FormItem" 96 | name="total" 97 | x-component="NumberPicker" 98 | x-pattern="readPretty" 99 | x-component-props={{ 100 | addonAfter: '$', 101 | }} 102 | x-reactions={{ 103 | dependencies: ['.price', '.count'], 104 | when: '{{$deps[0] && $deps[1]}}', 105 | fulfill: { 106 | state: { 107 | value: '{{$deps[0] * $deps[1]}}', 108 | }, 109 | }, 110 | }} 111 | /> 112 | </SchemaField.Void> 113 | <SchemaField.Void 114 | x-component="ArrayTable.Column" 115 | x-component-props={{ 116 | title: 'Operations', 117 | dataIndex: 'operations', 118 | width: 200, 119 | fixed: 'right', 120 | }} 121 | > 122 | <SchemaField.Void x-component="FormItem"> 123 | <SchemaField.Void x-component="ArrayTable.Remove" /> 124 | <SchemaField.Void x-component="ArrayTable.MoveDown" /> 125 | <SchemaField.Void x-component="ArrayTable.MoveUp" /> 126 | </SchemaField.Void> 127 | </SchemaField.Void> 128 | </SchemaField.Object> 129 | <SchemaField.Void x-component="ArrayTable.Addition" title="Add" /> 130 | </SchemaField.Array> 131 | <SchemaField.Number 132 | name="total" 133 | title="Total" 134 | x-decorator="FormItem" 135 | x-component="NumberPicker" 136 | x-component-props={{ 137 | addonAfter: '$', 138 | }} 139 | x-pattern="readPretty" 140 | x-reactions={{ 141 | dependencies: ['.projects'], 142 | when: '{{$deps[0].length > 0}}', 143 | fulfill: { 144 | state: { 145 | value: 146 | '{{$deps[0].reduce((total,item)=>item.total ? total+item.total : total,0)}}', 147 | }, 148 | }, 149 | }} 150 | /> 151 | </SchemaField> 152 | <FormButtonGroup> 153 | <Submit onSubmit={console.log}>提交</Submit> 154 | </FormButtonGroup> 155 | </Form> 156 | ) 157 | } 158 | ``` 159 | 160 | ## JSON Schema 案例 161 | 162 | ```tsx 163 | import React from 'react' 164 | import { 165 | Form, 166 | FormItem, 167 | NumberPicker, 168 | ArrayTable, 169 | Editable, 170 | Input, 171 | FormButtonGroup, 172 | Submit, 173 | } from '@formily/antd' 174 | import { createForm } from '@formily/core' 175 | import { createSchemaField } from '@formily/react' 176 | 177 | const SchemaField = createSchemaField({ 178 | components: { 179 | FormItem, 180 | Editable, 181 | Input, 182 | NumberPicker, 183 | ArrayTable, 184 | }, 185 | }) 186 | 187 | const form = createForm() 188 | 189 | const schema = { 190 | type: 'object', 191 | properties: { 192 | projects: { 193 | type: 'array', 194 | title: 'Projects', 195 | 'x-decorator': 'FormItem', 196 | 'x-component': 'ArrayTable', 197 | items: { 198 | type: 'object', 199 | properties: { 200 | column_1: { 201 | type: 'void', 202 | 'x-component': 'ArrayTable.Column', 203 | 'x-component-props': { 204 | width: 50, 205 | title: 'Sort', 206 | align: 'center', 207 | }, 208 | properties: { 209 | sortable: { 210 | type: 'void', 211 | 'x-component': 'ArrayTable.SortHandle', 212 | }, 213 | }, 214 | }, 215 | column_2: { 216 | type: 'void', 217 | 'x-component': 'ArrayTable.Column', 218 | 'x-component-props': { 219 | width: 50, 220 | title: 'Index', 221 | align: 'center', 222 | }, 223 | properties: { 224 | index: { 225 | type: 'void', 226 | 'x-component': 'ArrayTable.Index', 227 | }, 228 | }, 229 | }, 230 | column_3: { 231 | type: 'void', 232 | 'x-component': 'ArrayTable.Column', 233 | 'x-component-props': { 234 | title: 'Price', 235 | }, 236 | properties: { 237 | price: { 238 | type: 'number', 239 | default: 0, 240 | 'x-decorator': 'Editable', 241 | 'x-component': 'NumberPicker', 242 | 'x-component-props': { 243 | addonAfter: '$', 244 | }, 245 | }, 246 | }, 247 | }, 248 | column_4: { 249 | type: 'void', 250 | 'x-component': 'ArrayTable.Column', 251 | 'x-component-props': { 252 | title: 'Count', 253 | }, 254 | properties: { 255 | count: { 256 | type: 'number', 257 | default: 0, 258 | 'x-decorator': 'Editable', 259 | 'x-component': 'NumberPicker', 260 | 'x-component-props': { 261 | addonAfter: '$', 262 | }, 263 | }, 264 | }, 265 | }, 266 | column_5: { 267 | type: 'void', 268 | 'x-component': 'ArrayTable.Column', 269 | 'x-component-props': { 270 | title: 'Total', 271 | }, 272 | properties: { 273 | total: { 274 | type: 'number', 275 | 'x-read-pretty': true, 276 | 'x-decorator': 'FormItem', 277 | 'x-component': 'NumberPicker', 278 | 'x-component-props': { 279 | addonAfter: '$', 280 | }, 281 | 'x-reactions': { 282 | dependencies: ['.price', '.count'], 283 | when: '{{$deps[0] && $deps[1]}}', 284 | fulfill: { 285 | state: { 286 | value: '{{$deps[0] * $deps[1]}}', 287 | }, 288 | }, 289 | }, 290 | }, 291 | }, 292 | }, 293 | column_6: { 294 | type: 'void', 295 | 'x-component': 'ArrayTable.Column', 296 | 'x-component-props': { 297 | title: 'Operations', 298 | }, 299 | properties: { 300 | item: { 301 | type: 'void', 302 | 'x-component': 'FormItem', 303 | properties: { 304 | remove: { 305 | type: 'void', 306 | 'x-component': 'ArrayTable.Remove', 307 | }, 308 | moveDown: { 309 | type: 'void', 310 | 'x-component': 'ArrayTable.MoveDown', 311 | }, 312 | moveUp: { 313 | type: 'void', 314 | 'x-component': 'ArrayTable.MoveUp', 315 | }, 316 | }, 317 | }, 318 | }, 319 | }, 320 | }, 321 | }, 322 | properties: { 323 | add: { 324 | type: 'void', 325 | title: 'Add', 326 | 'x-component': 'ArrayTable.Addition', 327 | }, 328 | }, 329 | }, 330 | total: { 331 | type: 'number', 332 | title: 'Total', 333 | 'x-decorator': 'FormItem', 334 | 'x-component': 'NumberPicker', 335 | 'x-component-props': { 336 | addonAfter: '$', 337 | }, 338 | 'x-pattern': 'readPretty', 339 | 'x-reactions': { 340 | dependencies: ['.projects'], 341 | when: '{{$deps[0].length > 0}}', 342 | fulfill: { 343 | state: { 344 | value: 345 | '{{$deps[0].reduce((total,item)=>item.total ? total+item.total : total,0)}}', 346 | }, 347 | }, 348 | }, 349 | }, 350 | }, 351 | } 352 | 353 | export default () => { 354 | return ( 355 | <Form form={form} layout="vertical"> 356 | <SchemaField schema={schema} /> 357 | <FormButtonGroup> 358 | <Submit onSubmit={console.log}>提交</Submit> 359 | </FormButtonGroup> 360 | </Form> 361 | ) 362 | } 363 | ``` 364 | ``` -------------------------------------------------------------------------------- /docs/guide/advanced/calculator.md: -------------------------------------------------------------------------------- ```markdown 1 | # Calculator 2 | 3 | Linkage calculator is mainly used for evaluation and summarization in the process of filling in the form. In Formily 1.x, the cost of realizing this kind of demand is very high. In 2.x, we can easily implement it with the help of reactions. 4 | 5 | ## Markup Schema Case 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Form, 11 | FormItem, 12 | NumberPicker, 13 | ArrayTable, 14 | Editable, 15 | Input, 16 | FormButtonGroup, 17 | Submit, 18 | } from '@formily/antd' 19 | import { createForm } from '@formily/core' 20 | import { createSchemaField } from '@formily/react' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | FormItem, 25 | Editable, 26 | Input, 27 | NumberPicker, 28 | ArrayTable, 29 | }, 30 | }) 31 | 32 | const form = createForm() 33 | 34 | export default () => { 35 | return ( 36 | <Form form={form} layout="vertical"> 37 | <SchemaField> 38 | <SchemaField.Array 39 | name="projects" 40 | title="Projects" 41 | x-decorator="FormItem" 42 | x-component="ArrayTable" 43 | > 44 | <SchemaField.Object> 45 | <SchemaField.Void 46 | x-component="ArrayTable.Column" 47 | x-component-props={{ width: 50, title: 'Sort', align: 'center' }} 48 | > 49 | <SchemaField.Void 50 | x-decorator="FormItem" 51 | x-component="ArrayTable.SortHandle" 52 | /> 53 | </SchemaField.Void> 54 | <SchemaField.Void 55 | x-component="ArrayTable.Column" 56 | x-component-props={{ width: 80, title: 'Index', align: 'center' }} 57 | > 58 | <SchemaField.String 59 | x-decorator="FormItem" 60 | x-component="ArrayTable.Index" 61 | /> 62 | </SchemaField.Void> 63 | <SchemaField.Void 64 | x-component="ArrayTable.Column" 65 | x-component-props={{ title: 'Price' }} 66 | > 67 | <SchemaField.Number 68 | name="price" 69 | x-decorator="Editable" 70 | required 71 | x-component="NumberPicker" 72 | x-component-props={{ 73 | addonAfter: '$', 74 | }} 75 | default={0} 76 | /> 77 | </SchemaField.Void> 78 | <SchemaField.Void 79 | x-component="ArrayTable.Column" 80 | x-component-props={{ title: 'Count' }} 81 | > 82 | <SchemaField.Number 83 | name="count" 84 | x-decorator="Editable" 85 | required 86 | x-component="NumberPicker" 87 | default={0} 88 | /> 89 | </SchemaField.Void> 90 | <SchemaField.Void 91 | x-component="ArrayTable.Column" 92 | x-component-props={{ title: 'Total' }} 93 | > 94 | <SchemaField.Number 95 | x-decorator="FormItem" 96 | name="total" 97 | x-component="NumberPicker" 98 | x-pattern="readPretty" 99 | x-component-props={{ 100 | addonAfter: '$', 101 | }} 102 | x-reactions={{ 103 | dependencies: ['.price', '.count'], 104 | when: '{{$deps[0] && $deps[1]}}', 105 | fulfill: { 106 | state: { 107 | value: '{{$deps[0] * $deps[1]}}', 108 | }, 109 | }, 110 | }} 111 | /> 112 | </SchemaField.Void> 113 | <SchemaField.Void 114 | x-component="ArrayTable.Column" 115 | x-component-props={{ 116 | title: 'Operations', 117 | dataIndex: 'operations', 118 | width: 200, 119 | fixed: 'right', 120 | }} 121 | > 122 | <SchemaField.Void x-component="FormItem"> 123 | <SchemaField.Void x-component="ArrayTable.Remove" /> 124 | <SchemaField.Void x-component="ArrayTable.MoveDown" /> 125 | <SchemaField.Void x-component="ArrayTable.MoveUp" /> 126 | </SchemaField.Void> 127 | </SchemaField.Void> 128 | </SchemaField.Object> 129 | <SchemaField.Void x-component="ArrayTable.Addition" title="Add" /> 130 | </SchemaField.Array> 131 | <SchemaField.Number 132 | name="total" 133 | title="Total" 134 | x-decorator="FormItem" 135 | x-component="NumberPicker" 136 | x-component-props={{ 137 | addonAfter: '$', 138 | }} 139 | x-pattern="readPretty" 140 | x-reactions={{ 141 | dependencies: ['.projects'], 142 | when: '{{$deps[0].length > 0}}', 143 | fulfill: { 144 | state: { 145 | value: 146 | '{{$deps[0].reduce((total,item)=>item.total ? total+item.total : total,0)}}', 147 | }, 148 | }, 149 | }} 150 | /> 151 | </SchemaField> 152 | <FormButtonGroup> 153 | <Submit onSubmit={console.log}>提交</Submit> 154 | </FormButtonGroup> 155 | </Form> 156 | ) 157 | } 158 | ``` 159 | 160 | ## JSON Schema Case 161 | 162 | ```tsx 163 | import React from 'react' 164 | import { 165 | Form, 166 | FormItem, 167 | NumberPicker, 168 | ArrayTable, 169 | Editable, 170 | Input, 171 | FormButtonGroup, 172 | Submit, 173 | } from '@formily/antd' 174 | import { createForm } from '@formily/core' 175 | import { createSchemaField } from '@formily/react' 176 | 177 | const SchemaField = createSchemaField({ 178 | components: { 179 | FormItem, 180 | Editable, 181 | Input, 182 | NumberPicker, 183 | ArrayTable, 184 | }, 185 | }) 186 | 187 | const form = createForm() 188 | 189 | const schema = { 190 | type: 'object', 191 | properties: { 192 | projects: { 193 | type: 'array', 194 | title: 'Projects', 195 | 'x-decorator': 'FormItem', 196 | 'x-component': 'ArrayTable', 197 | items: { 198 | type: 'object', 199 | properties: { 200 | column_1: { 201 | type: 'void', 202 | 'x-component': 'ArrayTable.Column', 203 | 'x-component-props': { 204 | width: 50, 205 | title: 'Sort', 206 | align: 'center', 207 | }, 208 | properties: { 209 | sortable: { 210 | type: 'void', 211 | 'x-component': 'ArrayTable.SortHandle', 212 | }, 213 | }, 214 | }, 215 | column_2: { 216 | type: 'void', 217 | 'x-component': 'ArrayTable.Column', 218 | 'x-component-props': { 219 | width: 50, 220 | title: 'Index', 221 | align: 'center', 222 | }, 223 | properties: { 224 | index: { 225 | type: 'void', 226 | 'x-component': 'ArrayTable.Index', 227 | }, 228 | }, 229 | }, 230 | column_3: { 231 | type: 'void', 232 | 'x-component': 'ArrayTable.Column', 233 | 'x-component-props': { 234 | title: 'Price', 235 | }, 236 | properties: { 237 | price: { 238 | type: 'number', 239 | default: 0, 240 | 'x-decorator': 'Editable', 241 | 'x-component': 'NumberPicker', 242 | 'x-component-props': { 243 | addonAfter: '$', 244 | }, 245 | }, 246 | }, 247 | }, 248 | column_4: { 249 | type: 'void', 250 | 'x-component': 'ArrayTable.Column', 251 | 'x-component-props': { 252 | title: 'Count', 253 | }, 254 | properties: { 255 | count: { 256 | type: 'number', 257 | default: 0, 258 | 'x-decorator': 'Editable', 259 | 'x-component': 'NumberPicker', 260 | 'x-component-props': { 261 | addonAfter: '$', 262 | }, 263 | }, 264 | }, 265 | }, 266 | column_5: { 267 | type: 'void', 268 | 'x-component': 'ArrayTable.Column', 269 | 'x-component-props': { 270 | title: 'Total', 271 | }, 272 | properties: { 273 | total: { 274 | type: 'number', 275 | 'x-read-pretty': true, 276 | 'x-decorator': 'FormItem', 277 | 'x-component': 'NumberPicker', 278 | 'x-component-props': { 279 | addonAfter: '$', 280 | }, 281 | 'x-reactions': { 282 | dependencies: ['.price', '.count'], 283 | when: '{{$deps[0] && $deps[1]}}', 284 | fulfill: { 285 | state: { 286 | value: '{{$deps[0] * $deps[1]}}', 287 | }, 288 | }, 289 | }, 290 | }, 291 | }, 292 | }, 293 | column_6: { 294 | type: 'void', 295 | 'x-component': 'ArrayTable.Column', 296 | 'x-component-props': { 297 | title: 'Operations', 298 | }, 299 | properties: { 300 | item: { 301 | type: 'void', 302 | 'x-component': 'FormItem', 303 | properties: { 304 | remove: { 305 | type: 'void', 306 | 'x-component': 'ArrayTable.Remove', 307 | }, 308 | moveDown: { 309 | type: 'void', 310 | 'x-component': 'ArrayTable.MoveDown', 311 | }, 312 | moveUp: { 313 | type: 'void', 314 | 'x-component': 'ArrayTable.MoveUp', 315 | }, 316 | }, 317 | }, 318 | }, 319 | }, 320 | }, 321 | }, 322 | properties: { 323 | add: { 324 | type: 'void', 325 | title: 'Add', 326 | 'x-component': 'ArrayTable.Addition', 327 | }, 328 | }, 329 | }, 330 | total: { 331 | type: 'number', 332 | title: 'Total', 333 | 'x-decorator': 'FormItem', 334 | 'x-component': 'NumberPicker', 335 | 'x-component-props': { 336 | addonAfter: '$', 337 | }, 338 | 'x-pattern': 'readPretty', 339 | 'x-reactions': { 340 | dependencies: ['.projects'], 341 | when: '{{$deps[0].length > 0}}', 342 | fulfill: { 343 | state: { 344 | value: 345 | '{{$deps[0].reduce((total,item)=>item.total ? total+item.total : total,0)}}', 346 | }, 347 | }, 348 | }, 349 | }, 350 | }, 351 | } 352 | 353 | export default () => { 354 | return ( 355 | <Form form={form} layout="vertical"> 356 | <SchemaField schema={schema} /> 357 | <FormButtonGroup> 358 | <Submit onSubmit={console.log}>submit</Submit> 359 | </FormButtonGroup> 360 | </Form> 361 | ) 362 | } 363 | ``` 364 | ``` -------------------------------------------------------------------------------- /packages/next/src/array-base/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { createContext, useContext } from 'react' 2 | import { Button } from '@alifd/next' 3 | import { isValid, isUndef, clone } from '@formily/shared' 4 | import { ButtonProps } from '@alifd/next/lib/button' 5 | import { ArrayField } from '@formily/core' 6 | import { useField, useFieldSchema, Schema, JSXComponent } from '@formily/react' 7 | import { SortableHandle } from 'react-sortable-hoc' 8 | import { 9 | usePrefixCls, 10 | PlusOutlinedIcon, 11 | DeleteOutlinedIcon, 12 | DownOutlinedIcon, 13 | UpOutlinedIcon, 14 | MenuOutlinedIcon, 15 | CopyOutlinedIcon, 16 | } from '../__builtins__' 17 | import cls from 'classnames' 18 | 19 | export interface IArrayBaseAdditionProps extends ButtonProps { 20 | title?: string 21 | method?: 'push' | 'unshift' 22 | defaultValue?: any 23 | icon?: React.ReactNode 24 | } 25 | 26 | export interface IArrayBaseOperationProps extends ButtonProps { 27 | title?: string 28 | index?: number 29 | ref?: React.Ref<Button> 30 | icon?: React.ReactNode 31 | } 32 | 33 | export interface IArrayBaseContext { 34 | props: IArrayBaseProps 35 | field: ArrayField 36 | schema: Schema 37 | } 38 | 39 | export interface IArrayBaseItemProps { 40 | index: number 41 | record: ((index: number) => Record<string, any>) | Record<string, any> 42 | } 43 | 44 | export type ArrayBaseMixins = { 45 | Addition?: React.FC<React.PropsWithChildren<IArrayBaseAdditionProps>> 46 | Remove?: React.FC< 47 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 48 | > 49 | Copy?: React.FC< 50 | React.PropsWithChildren< 51 | IArrayBaseAdditionProps & IArrayBaseOperationProps & { index?: number } 52 | > 53 | > 54 | MoveUp?: React.FC< 55 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 56 | > 57 | MoveDown?: React.FC< 58 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 59 | > 60 | SortHandle?: React.FC< 61 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 62 | > 63 | Index?: React.FC 64 | useArray?: () => IArrayBaseContext 65 | useIndex?: (index?: number) => number 66 | useRecord?: (record?: number) => any 67 | } 68 | 69 | export interface IArrayBaseProps { 70 | disabled?: boolean 71 | onCopy?: (index: number) => void 72 | onAdd?: (index: number) => void 73 | onRemove?: (index: number) => void 74 | onMoveDown?: (index: number) => void 75 | onMoveUp?: (index: number) => void 76 | } 77 | 78 | type ComposedArrayBase = React.FC<React.PropsWithChildren<IArrayBaseProps>> & 79 | ArrayBaseMixins & { 80 | Item?: React.FC<React.PropsWithChildren<IArrayBaseItemProps>> 81 | mixin?: <T extends JSXComponent>(target: T) => T & ArrayBaseMixins 82 | } 83 | 84 | const ArrayBaseContext = createContext<IArrayBaseContext>(null) 85 | 86 | const ItemContext = createContext<IArrayBaseItemProps>(null) 87 | 88 | const takeRecord = (val: any, index?: number) => 89 | typeof val === 'function' ? val(index) : val 90 | 91 | const useArray = () => { 92 | return useContext(ArrayBaseContext) 93 | } 94 | 95 | const useIndex = (index?: number) => { 96 | const ctx = useContext(ItemContext) 97 | return ctx ? ctx.index : index 98 | } 99 | 100 | const useRecord = (record?: number) => { 101 | const ctx = useContext(ItemContext) 102 | return takeRecord(ctx ? ctx.record : record, ctx?.index) 103 | } 104 | 105 | const getSchemaDefaultValue = (schema: Schema) => { 106 | if (schema?.type === 'array') return [] 107 | if (schema?.type === 'object') return {} 108 | if (schema?.type === 'void') { 109 | for (let key in schema.properties) { 110 | const value = getSchemaDefaultValue(schema.properties[key]) 111 | if (isValid(value)) return value 112 | } 113 | } 114 | } 115 | 116 | const getDefaultValue = (defaultValue: any, schema: Schema) => { 117 | if (isValid(defaultValue)) return clone(defaultValue) 118 | if (Array.isArray(schema?.items)) 119 | return getSchemaDefaultValue(schema?.items[0]) 120 | return getSchemaDefaultValue(schema?.items) 121 | } 122 | 123 | export const ArrayBase: ComposedArrayBase = (props) => { 124 | const field = useField<ArrayField>() 125 | const schema = useFieldSchema() 126 | return ( 127 | <ArrayBaseContext.Provider value={{ field, schema, props }}> 128 | {props.children} 129 | </ArrayBaseContext.Provider> 130 | ) 131 | } 132 | 133 | ArrayBase.Item = ({ children, ...props }) => { 134 | return <ItemContext.Provider value={props}>{children}</ItemContext.Provider> 135 | } 136 | 137 | const SortHandle = SortableHandle((props: any) => { 138 | const prefixCls = usePrefixCls('formily-array-base') 139 | return ( 140 | <MenuOutlinedIcon 141 | {...props} 142 | className={cls(`${prefixCls}-sort-handle`, props.className)} 143 | style={{ ...props.style }} 144 | /> 145 | ) 146 | }) as any 147 | 148 | ArrayBase.SortHandle = () => { 149 | const array = useArray() 150 | if (!array) return null 151 | if (array.field?.pattern !== 'editable') return null 152 | return <SortHandle /> 153 | } 154 | 155 | ArrayBase.Index = (props) => { 156 | const index = useIndex() 157 | const prefixCls = usePrefixCls('formily-array-base') 158 | return ( 159 | <span {...props} className={`${prefixCls}-index`}> 160 | #{index + 1}. 161 | </span> 162 | ) 163 | } 164 | 165 | ArrayBase.Addition = (props) => { 166 | const self = useField() 167 | const array = useArray() 168 | const prefixCls = usePrefixCls('formily-array-base') 169 | if (!array) return null 170 | if ( 171 | array.field?.pattern !== 'editable' && 172 | array.field?.pattern !== 'disabled' 173 | ) 174 | return null 175 | return ( 176 | <Button 177 | {...props} 178 | disabled={self?.disabled} 179 | className={cls(`${prefixCls}-addition`, props.className)} 180 | style={{ display: 'block', width: '100%', ...props.style }} 181 | onClick={(e) => { 182 | if (array.props?.disabled) return 183 | e.stopPropagation() 184 | const defaultValue = getDefaultValue(props.defaultValue, array.schema) 185 | if (props.method === 'unshift') { 186 | array.field?.unshift?.(defaultValue) 187 | array.props?.onAdd?.(0) 188 | } else { 189 | array.field?.push?.(defaultValue) 190 | array.props?.onAdd?.(array?.field?.value?.length - 1) 191 | } 192 | if (props.onClick) { 193 | props.onClick(e) 194 | } 195 | }} 196 | > 197 | {isUndef(props.icon) ? <PlusOutlinedIcon /> : props.icon} 198 | {props.title || self.title} 199 | </Button> 200 | ) 201 | } 202 | 203 | ArrayBase.Remove = React.forwardRef((props, ref) => { 204 | const index = useIndex(props.index) 205 | const self = useField() 206 | const array = useArray() 207 | const prefixCls = usePrefixCls('formily-array-base') 208 | if (!array) return null 209 | if (array.field?.pattern !== 'editable') return null 210 | return ( 211 | <Button 212 | text 213 | {...props} 214 | disabled={self?.disabled} 215 | className={cls( 216 | `${prefixCls}-remove`, 217 | self?.disabled ? `${prefixCls}-remove-disabled` : '', 218 | props.className 219 | )} 220 | ref={ref} 221 | onClick={(e) => { 222 | if (self?.disabled) return 223 | e.stopPropagation() 224 | array.field?.remove?.(index) 225 | array.props?.onRemove?.(index) 226 | if (props.onClick) { 227 | props.onClick(e) 228 | } 229 | }} 230 | > 231 | {isUndef(props.icon) ? <DeleteOutlinedIcon /> : props.icon} 232 | {props.title || self.title} 233 | </Button> 234 | ) 235 | }) 236 | 237 | ArrayBase.Copy = React.forwardRef((props, ref) => { 238 | const index = useIndex(props.index) 239 | const self = useField() 240 | const array = useArray() 241 | const prefixCls = usePrefixCls('formily-array-base') 242 | if (!array) return null 243 | if (array.field?.pattern !== 'editable') return null 244 | return ( 245 | <Button 246 | text 247 | {...props} 248 | disabled={self?.disabled} 249 | className={cls( 250 | `${prefixCls}-copy`, 251 | self?.disabled ? `${prefixCls}-copy-disabled` : '', 252 | props.className 253 | )} 254 | ref={ref} 255 | onClick={(e) => { 256 | if (self?.disabled) return 257 | e.stopPropagation() 258 | if (array.props?.disabled) return 259 | const value = clone(array?.field?.value[index]) 260 | const distIndex = index + 1 261 | array.field?.insert?.(distIndex, value) 262 | array.props?.onCopy?.(distIndex) 263 | if (props.onClick) { 264 | props.onClick(e) 265 | } 266 | }} 267 | > 268 | {isUndef(props.icon) ? <CopyOutlinedIcon /> : props.icon} 269 | {props.title || self.title} 270 | </Button> 271 | ) 272 | }) 273 | 274 | ArrayBase.MoveDown = React.forwardRef((props, ref) => { 275 | const index = useIndex(props.index) 276 | const self = useField() 277 | const array = useArray() 278 | const prefixCls = usePrefixCls('formily-array-base') 279 | if (!array) return null 280 | if (array.field?.pattern !== 'editable') return null 281 | return ( 282 | <Button 283 | text 284 | {...props} 285 | disabled={self?.disabled} 286 | className={cls( 287 | `${prefixCls}-move-down`, 288 | self?.disabled ? `${prefixCls}-move-down-disabled` : '', 289 | props.className 290 | )} 291 | ref={ref} 292 | onClick={(e) => { 293 | if (self?.disabled) return 294 | e.stopPropagation() 295 | array.field?.moveDown?.(index) 296 | array.props?.onMoveDown?.(index) 297 | if (props.onClick) { 298 | props.onClick(e) 299 | } 300 | }} 301 | > 302 | {isUndef(props.icon) ? <DownOutlinedIcon /> : props.icon} 303 | {props.title || self.title} 304 | </Button> 305 | ) 306 | }) 307 | 308 | ArrayBase.MoveUp = React.forwardRef((props, ref) => { 309 | const index = useIndex(props.index) 310 | const self = useField() 311 | const array = useArray() 312 | const prefixCls = usePrefixCls('formily-array-base') 313 | if (!array) return null 314 | if (array.field?.pattern !== 'editable') return null 315 | return ( 316 | <Button 317 | text 318 | {...props} 319 | disabled={self?.disabled} 320 | className={cls( 321 | `${prefixCls}-move-up`, 322 | self?.disabled ? `${prefixCls}-move-up-disabled` : '', 323 | props.className 324 | )} 325 | ref={ref} 326 | onClick={(e) => { 327 | if (self?.disabled) return 328 | e.stopPropagation() 329 | array?.field?.moveUp(index) 330 | array?.props?.onMoveUp?.(index) 331 | if (props.onClick) { 332 | props.onClick(e) 333 | } 334 | }} 335 | > 336 | {isUndef(props.icon) ? <UpOutlinedIcon /> : props.icon} 337 | {props.title || self.title} 338 | </Button> 339 | ) 340 | }) 341 | 342 | ArrayBase.useArray = useArray 343 | ArrayBase.useIndex = useIndex 344 | ArrayBase.useRecord = useRecord 345 | ArrayBase.mixin = (target: any) => { 346 | target.Index = ArrayBase.Index 347 | target.SortHandle = ArrayBase.SortHandle 348 | target.Addition = ArrayBase.Addition 349 | target.Copy = ArrayBase.Copy 350 | target.Remove = ArrayBase.Remove 351 | target.MoveDown = ArrayBase.MoveDown 352 | target.MoveUp = ArrayBase.MoveUp 353 | target.useArray = ArrayBase.useArray 354 | target.useIndex = ArrayBase.useIndex 355 | target.useRecord = ArrayBase.useRecord 356 | return target 357 | } 358 | 359 | export default ArrayBase 360 | ``` -------------------------------------------------------------------------------- /packages/antd/src/array-base/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | CopyOutlined, 3 | DeleteOutlined, 4 | DownOutlined, 5 | MenuOutlined, 6 | PlusOutlined, 7 | UpOutlined, 8 | } from '@ant-design/icons' 9 | import { ArrayField } from '@formily/core' 10 | import { JSXComponent, Schema, useField, useFieldSchema } from '@formily/react' 11 | import { clone, isUndef, isValid } from '@formily/shared' 12 | import { Button } from 'antd' 13 | import { ButtonProps } from 'antd/lib/button' 14 | import cls from 'classnames' 15 | import React, { createContext, useContext } from 'react' 16 | import { SortableHandle, usePrefixCls } from '../__builtins__' 17 | 18 | export interface IArrayBaseAdditionProps extends ButtonProps { 19 | title?: string 20 | method?: 'push' | 'unshift' 21 | defaultValue?: any 22 | } 23 | export interface IArrayBaseOperationProps extends ButtonProps { 24 | title?: string 25 | index?: number 26 | ref?: React.Ref<HTMLElement> 27 | } 28 | 29 | export interface IArrayBaseContext { 30 | props: IArrayBaseProps 31 | field: ArrayField 32 | schema: Schema 33 | } 34 | 35 | export interface IArrayBaseItemProps { 36 | index: number 37 | record: ((index: number) => Record<string, any>) | Record<string, any> 38 | } 39 | 40 | export type ArrayBaseMixins = { 41 | Addition?: React.FC<React.PropsWithChildren<IArrayBaseAdditionProps>> 42 | Copy?: React.FC< 43 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 44 | > 45 | Remove?: React.FC< 46 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 47 | > 48 | MoveUp?: React.FC< 49 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 50 | > 51 | MoveDown?: React.FC< 52 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 53 | > 54 | SortHandle?: React.FC< 55 | React.PropsWithChildren<IArrayBaseOperationProps & { index?: number }> 56 | > 57 | Index?: React.FC 58 | useArray?: () => IArrayBaseContext 59 | useIndex?: (index?: number) => number 60 | useRecord?: (record?: number) => any 61 | } 62 | 63 | export interface IArrayBaseProps { 64 | disabled?: boolean 65 | onAdd?: (index: number) => void 66 | onCopy?: (index: number) => void 67 | onRemove?: (index: number) => void 68 | onMoveDown?: (index: number) => void 69 | onMoveUp?: (index: number) => void 70 | } 71 | 72 | type ComposedArrayBase = React.FC<React.PropsWithChildren<IArrayBaseProps>> & 73 | ArrayBaseMixins & { 74 | Item?: React.FC<React.PropsWithChildren<IArrayBaseItemProps>> 75 | mixin?: <T extends JSXComponent>(target: T) => T & ArrayBaseMixins 76 | } 77 | 78 | const ArrayBaseContext = createContext<IArrayBaseContext>(null) 79 | 80 | const ItemContext = createContext<IArrayBaseItemProps>(null) 81 | 82 | const takeRecord = (val: any, index?: number) => 83 | typeof val === 'function' ? val(index) : val 84 | 85 | const useArray = () => { 86 | return useContext(ArrayBaseContext) 87 | } 88 | 89 | const useIndex = (index?: number) => { 90 | const ctx = useContext(ItemContext) 91 | return ctx ? ctx.index : index 92 | } 93 | 94 | const useRecord = (record?: number) => { 95 | const ctx = useContext(ItemContext) 96 | return takeRecord(ctx ? ctx.record : record, ctx?.index) 97 | } 98 | 99 | const getSchemaDefaultValue = (schema: Schema) => { 100 | if (schema?.type === 'array') return [] 101 | if (schema?.type === 'object') return {} 102 | if (schema?.type === 'void') { 103 | for (let key in schema.properties) { 104 | const value = getSchemaDefaultValue(schema.properties[key]) 105 | if (isValid(value)) return value 106 | } 107 | } 108 | } 109 | 110 | const getDefaultValue = (defaultValue: any, schema: Schema) => { 111 | if (isValid(defaultValue)) return clone(defaultValue) 112 | if (Array.isArray(schema?.items)) 113 | return getSchemaDefaultValue(schema?.items[0]) 114 | return getSchemaDefaultValue(schema?.items) 115 | } 116 | 117 | export const ArrayBase: ComposedArrayBase = (props) => { 118 | const field = useField<ArrayField>() 119 | const schema = useFieldSchema() 120 | return ( 121 | <ArrayBaseContext.Provider value={{ field, schema, props }}> 122 | {props.children} 123 | </ArrayBaseContext.Provider> 124 | ) 125 | } 126 | 127 | ArrayBase.Item = ({ children, ...props }) => { 128 | return <ItemContext.Provider value={props}>{children}</ItemContext.Provider> 129 | } 130 | 131 | const SortHandle = SortableHandle((props: any) => { 132 | const prefixCls = usePrefixCls('formily-array-base') 133 | return ( 134 | <MenuOutlined 135 | {...props} 136 | className={cls(`${prefixCls}-sort-handle`, props.className)} 137 | style={{ ...props.style }} 138 | /> 139 | ) 140 | }) as any 141 | 142 | ArrayBase.SortHandle = (props) => { 143 | const array = useArray() 144 | if (!array) return null 145 | if (array.field?.pattern !== 'editable') return null 146 | return <SortHandle {...props} /> 147 | } 148 | 149 | ArrayBase.Index = (props) => { 150 | const index = useIndex() 151 | const prefixCls = usePrefixCls('formily-array-base') 152 | return ( 153 | <span {...props} className={`${prefixCls}-index`}> 154 | #{index + 1}. 155 | </span> 156 | ) 157 | } 158 | 159 | ArrayBase.Addition = (props) => { 160 | const self = useField() 161 | const array = useArray() 162 | const prefixCls = usePrefixCls('formily-array-base') 163 | if (!array) return null 164 | if ( 165 | array.field?.pattern !== 'editable' && 166 | array.field?.pattern !== 'disabled' 167 | ) 168 | return null 169 | return ( 170 | <Button 171 | type="dashed" 172 | block 173 | {...props} 174 | disabled={self?.disabled} 175 | className={cls(`${prefixCls}-addition`, props.className)} 176 | onClick={(e) => { 177 | if (array.props?.disabled) return 178 | if (props.onClick) { 179 | props.onClick(e) 180 | if (e.defaultPrevented) return 181 | } 182 | const defaultValue = getDefaultValue(props.defaultValue, array.schema) 183 | if (props.method === 'unshift') { 184 | array.field?.unshift?.(defaultValue) 185 | array.props?.onAdd?.(0) 186 | } else { 187 | array.field?.push?.(defaultValue) 188 | array.props?.onAdd?.(array?.field?.value?.length - 1) 189 | } 190 | }} 191 | icon={isUndef(props.icon) ? <PlusOutlined /> : props.icon} 192 | > 193 | {props.title || self.title} 194 | </Button> 195 | ) 196 | } 197 | 198 | ArrayBase.Copy = React.forwardRef((props, ref) => { 199 | const self = useField() 200 | const array = useArray() 201 | const index = useIndex(props.index) 202 | const prefixCls = usePrefixCls('formily-array-base') 203 | if (!array) return null 204 | if (array.field?.pattern !== 'editable') return null 205 | return ( 206 | <Button 207 | type="text" 208 | {...props} 209 | disabled={self?.disabled} 210 | className={cls( 211 | `${prefixCls}-copy`, 212 | self?.disabled ? `${prefixCls}-copy-disabled` : '', 213 | props.className 214 | )} 215 | ref={ref} 216 | onClick={(e) => { 217 | if (self?.disabled) return 218 | e.stopPropagation() 219 | if (array.props?.disabled) return 220 | if (props.onClick) { 221 | props.onClick(e) 222 | if (e.defaultPrevented) return 223 | } 224 | const value = clone(array?.field?.value[index]) 225 | const distIndex = index + 1 226 | array.field?.insert?.(distIndex, value) 227 | array.props?.onCopy?.(distIndex) 228 | }} 229 | icon={isUndef(props.icon) ? <CopyOutlined /> : props.icon} 230 | > 231 | {props.title || self.title} 232 | </Button> 233 | ) 234 | }) 235 | 236 | ArrayBase.Remove = React.forwardRef((props, ref) => { 237 | const index = useIndex(props.index) 238 | const self = useField() 239 | const array = useArray() 240 | const prefixCls = usePrefixCls('formily-array-base') 241 | if (!array) return null 242 | if (array.field?.pattern !== 'editable') return null 243 | return ( 244 | <Button 245 | type="text" 246 | {...props} 247 | disabled={self?.disabled} 248 | className={cls( 249 | `${prefixCls}-remove`, 250 | self?.disabled ? `${prefixCls}-remove-disabled` : '', 251 | props.className 252 | )} 253 | ref={ref} 254 | onClick={(e) => { 255 | if (self?.disabled) return 256 | e.stopPropagation() 257 | if (props.onClick) { 258 | props.onClick(e) 259 | if (e.defaultPrevented) return 260 | } 261 | array.field?.remove?.(index) 262 | array.props?.onRemove?.(index) 263 | }} 264 | icon={isUndef(props.icon) ? <DeleteOutlined /> : props.icon} 265 | > 266 | {props.title || self.title} 267 | </Button> 268 | ) 269 | }) 270 | 271 | ArrayBase.MoveDown = React.forwardRef((props, ref) => { 272 | const index = useIndex(props.index) 273 | const self = useField() 274 | const array = useArray() 275 | const prefixCls = usePrefixCls('formily-array-base') 276 | if (!array) return null 277 | if (array.field?.pattern !== 'editable') return null 278 | return ( 279 | <Button 280 | type="text" 281 | {...props} 282 | disabled={self?.disabled} 283 | className={cls( 284 | `${prefixCls}-move-down`, 285 | self?.disabled ? `${prefixCls}-move-down-disabled` : '', 286 | props.className 287 | )} 288 | ref={ref} 289 | onClick={(e) => { 290 | if (self?.disabled) return 291 | e.stopPropagation() 292 | if (props.onClick) { 293 | props.onClick(e) 294 | if (e.defaultPrevented) return 295 | } 296 | array.field?.moveDown?.(index) 297 | array.props?.onMoveDown?.(index) 298 | }} 299 | icon={isUndef(props.icon) ? <DownOutlined /> : props.icon} 300 | > 301 | {props.title || self.title} 302 | </Button> 303 | ) 304 | }) 305 | 306 | ArrayBase.MoveUp = React.forwardRef((props, ref) => { 307 | const index = useIndex(props.index) 308 | const self = useField() 309 | const array = useArray() 310 | const prefixCls = usePrefixCls('formily-array-base') 311 | if (!array) return null 312 | if (array.field?.pattern !== 'editable') return null 313 | return ( 314 | <Button 315 | type="text" 316 | {...props} 317 | disabled={self?.disabled} 318 | className={cls( 319 | `${prefixCls}-move-up`, 320 | self?.disabled ? `${prefixCls}-move-up-disabled` : '', 321 | props.className 322 | )} 323 | ref={ref} 324 | onClick={(e) => { 325 | if (self?.disabled) return 326 | e.stopPropagation() 327 | if (props.onClick) { 328 | props.onClick(e) 329 | if (e.defaultPrevented) return 330 | } 331 | array?.field?.moveUp(index) 332 | array?.props?.onMoveUp?.(index) 333 | }} 334 | icon={isUndef(props.icon) ? <UpOutlined /> : props.icon} 335 | > 336 | {props.title || self.title} 337 | </Button> 338 | ) 339 | }) 340 | 341 | ArrayBase.useArray = useArray 342 | ArrayBase.useIndex = useIndex 343 | ArrayBase.useRecord = useRecord 344 | ArrayBase.mixin = (target: any) => { 345 | target.Index = ArrayBase.Index 346 | target.SortHandle = ArrayBase.SortHandle 347 | target.Addition = ArrayBase.Addition 348 | target.Copy = ArrayBase.Copy 349 | target.Remove = ArrayBase.Remove 350 | target.MoveDown = ArrayBase.MoveDown 351 | target.MoveUp = ArrayBase.MoveUp 352 | target.useArray = ArrayBase.useArray 353 | target.useIndex = ArrayBase.useIndex 354 | target.useRecord = ArrayBase.useRecord 355 | return target 356 | } 357 | 358 | export default ArrayBase 359 | ```