This is page 18 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/core/docs/index.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: Formily-Alibaba unified front-end form solution 3 | order: 10 4 | hero: 5 | title: Core Library 6 | desc: Alibaba Unified Form Solution 7 | actions: 8 | - text: Home Site 9 | link: //formilyjs.org 10 | - text: Document 11 | link: /guide 12 | features: 13 | - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg 14 | title: High Performance 15 | desc: Efficient update, Demand rendering 16 | - icon: https://img.alicdn.com/imgextra/i3/O1CN0194OqFF1ui6mMT4g7O_!!6000000006070-55-tps-800-800.svg 17 | title: Excellent Reusability 18 | desc: Independent side effects, Pluggable 19 | - icon: https://img.alicdn.com/imgextra/i2/O1CN01QnfYS71E44I1ZpxU9_!!6000000000297-55-tps-800-800.svg 20 | title: Elegant Linkage Writing 21 | desc: Flexible, Complete, Elegant 22 | - icon: https://img.alicdn.com/imgextra/i2/O1CN01YqmcpN1tDalwgyHBH_!!6000000005868-55-tps-800-800.svg 23 | title: Complete domain model 24 | desc: Pure Core, No UI, No Framework 25 | - icon: https://img.alicdn.com/imgextra/i4/O1CN018vDmpl2186xdLu6KI_!!6000000006939-55-tps-800-800.svg 26 | title: Friendly debugging 27 | desc: Natural docking with Formily DevTools 28 | - icon: https://img.alicdn.com/imgextra/i4/O1CN01u6jHgs1ZMwXpjAYnh_!!6000000003181-55-tps-800-800.svg 29 | title: Smart Tips 30 | desc: Embrace Typescript 31 | footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by self 32 | --- 33 | 34 | ## Installation 35 | 36 | ```bash 37 | $ npm install --save @formily/core 38 | 39 | ``` 40 | 41 | ## Quick start 42 | 43 | > The following case is to teach you step by step to implement a form from scratch 44 | > 45 | > @formily/core brings you the following capabilities: 46 | > 47 | > 1. Responsive computing capabilities 48 | > 2. Verification capability, verification internationalization capability 49 | > 3. Value Management Ability 50 | > 4. Linkage management capabilities 51 | > 5. Development tool debugging capabilities, [download Formily Devtools](https://chrome.google.com/webstore/detail/formily-devtools/kkocalmbfnplecdmbadaapgapdioecfm?hl=zh-CN) 52 | 53 | ```tsx 54 | /** 55 | * defaultShowCode: true 56 | */ 57 | import React, { createContext, useMemo, useContext, useEffect } from 'react' 58 | import { createForm, setValidateLanguage } from '@formily/core' 59 | import { observer } from '@formily/reactive-react' 60 | 61 | //Create a context to facilitate Field consumption 62 | const FormContext = createContext() 63 | //Create a context to facilitate the consumption of FormItem 64 | const FieldContext = createContext() 65 | 66 | //State bridge component 67 | const Field = observer((props) => { 68 | const form = useContext(FormContext) 69 | //Create a field 70 | const field = form.createField(props) 71 | useEffect(() => { 72 | //Mount field 73 | field.onMount() 74 | return () => { 75 | //Unload field 76 | field.onUnmount() 77 | } 78 | }) 79 | if (!field.visible || field.hidden) return null 80 | //Render the field, associate the field state with the UI component 81 | const component = React.createElement(field.component[0], { 82 | ...field.component[1], 83 | value: field.value, 84 | onChange: field.onInput, 85 | }) 86 | 87 | //Render field wrapper 88 | const decorator = React.createElement( 89 | field.decorator[0], 90 | field.decorator[1], 91 | component 92 | ) 93 | 94 | return ( 95 | <FieldContext.Provider value={field}>{decorator}</FieldContext.Provider> 96 | ) 97 | }) 98 | 99 | // FormItem UI component 100 | const FormItem = observer(({ children }) => { 101 | const field = useContext(FieldContext) 102 | return ( 103 | <div> 104 | <div style={{ height: 20 }}>{field.title}:</div> 105 | {children} 106 | <div style={{ height: 20, fontSize: 12, color: 'red' }}> 107 | {field.selfErrors.join(',')} 108 | </div> 109 | </div> 110 | ) 111 | }) 112 | 113 | // Input UI component 114 | const Input = (props) => { 115 | return ( 116 | <input 117 | {...props} 118 | value={props.value || ''} 119 | style={{ 120 | ...props.style, 121 | border: '2px solid rgb(186 203 255)', 122 | borderRadius: 6, 123 | width: '100%', 124 | height: 28, 125 | padding: '0 5px', 126 | }} 127 | /> 128 | ) 129 | } 130 | 131 | //Form management entrance 132 | const FormProvider = (props) => { 133 | useEffect(() => { 134 | //Mount form 135 | props.form?.onMount() 136 | return () => { 137 | //Uninstall the form 138 | props.form?.onUnmount() 139 | } 140 | }) 141 | return ( 142 | <FormContext.Provider value={props.form}> 143 | {props.children} 144 | </FormContext.Provider> 145 | ) 146 | } 147 | 148 | //Form response monitor 149 | const FormConsumer = observer((props) => { 150 | const form = useContext(FormContext) 151 | return <div>{props.children(form)}</div> 152 | }) 153 | 154 | /* 155 | * The above logic has been implemented in @formily/react or @formily/vue, and there is no need to rewrite it in actual use 156 | */ 157 | 158 | //Switch the built-in check internationalization copy to English 159 | setValidateLanguage('en') 160 | 161 | export default () => { 162 | const form = useMemo(() => createForm({ validateFirst: true })) 163 | 164 | const createPasswordEqualValidate = (equalName) => (field) => { 165 | if ( 166 | form.values.confirm_password && 167 | field.value && 168 | form.values[equalName] !== field.value 169 | ) { 170 | field.selfErrors = ['Password does not match Confirm Password.'] 171 | } else { 172 | field.selfErrors = [] 173 | } 174 | } 175 | 176 | return ( 177 | <FormProvider form={form}> 178 | <Field 179 | name="name" 180 | title="Name" 181 | required 182 | decorator={[FormItem]} 183 | component={[Input, { placeholder: 'Please Input' }]} 184 | /> 185 | <Field 186 | name="password" 187 | title="Password" 188 | required 189 | decorator={[FormItem]} 190 | component={[Input, { type: 'password', placeholder: 'Please Input' }]} 191 | reactions={createPasswordEqualValidate('confirm_password')} 192 | /> 193 | <Field 194 | name="confirm_password" 195 | title="Confirm Password" 196 | required 197 | decorator={[FormItem]} 198 | component={[Input, { type: 'password', placeholder: 'Please Input' }]} 199 | reactions={createPasswordEqualValidate('password')} 200 | /> 201 | <code> 202 | <pre> 203 | <FormConsumer> 204 | {(form) => JSON.stringify(form.values, null, 2)} 205 | </FormConsumer> 206 | </pre> 207 | </code> 208 | </FormProvider> 209 | ) 210 | } 211 | ``` 212 | ``` -------------------------------------------------------------------------------- /packages/element/src/form-collapse/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Collapse, CollapseItem, Badge } from 'element-ui' 2 | import { model } from '@formily/reactive' 3 | import type { 4 | Collapse as CollapseProps, 5 | CollapseItem as CollapseItemProps, 6 | } from 'element-ui' 7 | import { 8 | useField, 9 | useFieldSchema, 10 | RecursionField, 11 | h, 12 | Fragment, 13 | } from '@formily/vue' 14 | import { observer } from '@formily/reactive-vue' 15 | import { Schema, SchemaKey } from '@formily/json-schema' 16 | import { composeExport, stylePrefix } from '../__builtins__' 17 | import { toArr } from '@formily/shared' 18 | import { computed, defineComponent, PropType } from 'vue-demi' 19 | import { GeneralField } from '@formily/core' 20 | 21 | type ActiveKeys = string | number | Array<string | number> 22 | 23 | type ActiveKey = string | number 24 | 25 | type Panels = { name: SchemaKey; props: any; schema: Schema }[] 26 | 27 | export interface IFormCollapse { 28 | activeKeys: ActiveKeys 29 | hasActiveKey(key: ActiveKey): boolean 30 | setActiveKeys(key: ActiveKeys): void 31 | addActiveKey(key: ActiveKey): void 32 | removeActiveKey(key: ActiveKey): void 33 | toggleActiveKey(key: ActiveKey): void 34 | } 35 | 36 | export interface IFormCollapseProps extends CollapseProps { 37 | formCollapse?: IFormCollapse 38 | activeKey?: ActiveKey 39 | } 40 | 41 | const usePanels = (collapseField: GeneralField, schema: Schema) => { 42 | const panels: Panels = [] 43 | schema.mapProperties((schema, name) => { 44 | const field = collapseField.query(collapseField.address.concat(name)).take() 45 | if (field?.display === 'none' || field?.display === 'hidden') return 46 | if (schema['x-component']?.indexOf('FormCollapse.Item') > -1) { 47 | panels.push({ 48 | name, 49 | props: { 50 | ...schema?.['x-component-props'], 51 | key: schema?.['x-component-props']?.key || name, 52 | }, 53 | schema, 54 | }) 55 | } 56 | }) 57 | return panels 58 | } 59 | 60 | const createFormCollapse = (defaultActiveKeys?: ActiveKeys) => { 61 | const formCollapse = model({ 62 | activeKeys: defaultActiveKeys, 63 | setActiveKeys(keys: ActiveKeys) { 64 | formCollapse.activeKeys = keys 65 | }, 66 | hasActiveKey(key: ActiveKey) { 67 | if (Array.isArray(formCollapse.activeKeys)) { 68 | if (formCollapse.activeKeys.includes(key)) { 69 | return true 70 | } 71 | } else if (formCollapse.activeKeys == key) { 72 | return true 73 | } 74 | return false 75 | }, 76 | addActiveKey(key: ActiveKey) { 77 | if (formCollapse.hasActiveKey(key)) return 78 | formCollapse.activeKeys = toArr(formCollapse.activeKeys).concat(key) 79 | }, 80 | removeActiveKey(key: ActiveKey) { 81 | if (Array.isArray(formCollapse.activeKeys)) { 82 | formCollapse.activeKeys = formCollapse.activeKeys.filter( 83 | (item) => item != key 84 | ) 85 | } else { 86 | formCollapse.activeKeys = '' 87 | } 88 | }, 89 | toggleActiveKey(key: ActiveKey) { 90 | if (formCollapse.hasActiveKey(key)) { 91 | formCollapse.removeActiveKey(key) 92 | } else { 93 | formCollapse.addActiveKey(key) 94 | } 95 | }, 96 | }) 97 | return formCollapse 98 | } 99 | 100 | const FormCollapse = observer( 101 | defineComponent({ 102 | inheritAttrs: false, 103 | props: { 104 | formCollapse: { type: Object as PropType<IFormCollapse> }, 105 | activeKey: { 106 | type: [String, Number], 107 | }, 108 | }, 109 | setup(props, { attrs, emit }) { 110 | const field = useField() 111 | const schema = useFieldSchema() 112 | const prefixCls = `${stylePrefix}-form-collapse` 113 | const _formCollapse = computed( 114 | () => props.formCollapse ?? createFormCollapse() 115 | ) 116 | 117 | const takeActiveKeys = (panels: Panels) => { 118 | if (props.activeKey) return props.activeKey 119 | if (_formCollapse.value?.activeKeys) 120 | return _formCollapse.value?.activeKeys 121 | if (attrs.accordion) return panels[0]?.name 122 | return panels.map((item) => item.name) 123 | } 124 | 125 | const badgedHeader = (key: SchemaKey, props: any) => { 126 | const errors = field.value.form.queryFeedbacks({ 127 | type: 'error', 128 | address: `${field.value.address.concat(key)}.*`, 129 | }) 130 | if (errors.length) { 131 | return h( 132 | Badge, 133 | { 134 | class: [`${prefixCls}-errors-badge`], 135 | props: { 136 | value: errors.length, 137 | }, 138 | }, 139 | { default: () => props.title } 140 | ) 141 | } 142 | return props.title 143 | } 144 | 145 | return () => { 146 | const panels = usePanels(field.value, schema.value) 147 | const activeKey = takeActiveKeys(panels) 148 | return h( 149 | Collapse, 150 | { 151 | class: prefixCls, 152 | props: { 153 | value: activeKey, 154 | }, 155 | on: { 156 | change: (key: string | string[]) => { 157 | emit('input', key) 158 | _formCollapse.value.setActiveKeys(key) 159 | }, 160 | }, 161 | }, 162 | { 163 | default: () => { 164 | return panels.map(({ props, schema, name }, index) => { 165 | return h( 166 | CollapseItem, 167 | { 168 | key: index, 169 | props: { 170 | ...props, 171 | name, 172 | }, 173 | }, 174 | { 175 | default: () => [ 176 | h(RecursionField, { props: { schema, name } }, {}), 177 | ], 178 | title: () => 179 | h( 180 | 'span', 181 | {}, 182 | { default: () => badgedHeader(name, props) } 183 | ), 184 | } 185 | ) 186 | }) 187 | }, 188 | } 189 | ) 190 | } 191 | }, 192 | }) 193 | ) 194 | 195 | export const FormCollapseItem = defineComponent<CollapseItemProps>({ 196 | name: 'FFormCollapseItem', 197 | setup(_props, { slots }) { 198 | return () => h(Fragment, {}, slots) 199 | }, 200 | }) 201 | 202 | const composeFormCollapse = composeExport(FormCollapse, { 203 | Item: FormCollapseItem, 204 | createFormCollapse, 205 | }) 206 | 207 | export { composeFormCollapse as FormCollapse } 208 | export default composeFormCollapse 209 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Editable.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Editable 2 | 3 | > 局部编辑器,对于一些空间要求较高的表单区域可以使用该组件 4 | > 5 | > Editable 组件相当于是 FormItem 组件的变体,所以通常放在 decorator 中 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | Input, 13 | DatePicker, 14 | Editable, 15 | FormItem, 16 | FormButtonGroup, 17 | Submit, 18 | } from '@formily/antd' 19 | import { createForm } from '@formily/core' 20 | import { FormProvider, createSchemaField } from '@formily/react' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | DatePicker, 25 | Editable, 26 | Input, 27 | FormItem, 28 | }, 29 | }) 30 | 31 | const form = createForm() 32 | 33 | export default () => ( 34 | <FormProvider form={form}> 35 | <SchemaField> 36 | <SchemaField.String 37 | name="date" 38 | title="日期" 39 | x-decorator="Editable" 40 | x-component="DatePicker" 41 | /> 42 | <SchemaField.String 43 | name="input" 44 | title="输入框" 45 | x-decorator="Editable" 46 | x-component="Input" 47 | /> 48 | <SchemaField.Void 49 | name="void" 50 | title="虚拟节点容器" 51 | x-component="Editable.Popover" 52 | x-reactions={(field) => { 53 | field.title = field.query('.void.date2').get('value') || field.title 54 | }} 55 | > 56 | <SchemaField.String 57 | name="date2" 58 | title="日期" 59 | x-decorator="FormItem" 60 | x-component="DatePicker" 61 | /> 62 | <SchemaField.String 63 | name="input2" 64 | title="输入框" 65 | x-decorator="FormItem" 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | <SchemaField.Object 70 | name="iobject" 71 | title="对象节点容器" 72 | x-component="Editable.Popover" 73 | x-reactions={(field) => { 74 | field.title = field.value?.date || field.title 75 | }} 76 | > 77 | <SchemaField.String 78 | name="date" 79 | title="日期" 80 | x-decorator="FormItem" 81 | x-component="DatePicker" 82 | /> 83 | <SchemaField.String 84 | name="input" 85 | title="输入框" 86 | x-decorator="FormItem" 87 | x-component="Input" 88 | /> 89 | </SchemaField.Object> 90 | </SchemaField> 91 | <FormButtonGroup> 92 | <Submit onSubmit={console.log}>提交</Submit> 93 | </FormButtonGroup> 94 | </FormProvider> 95 | ) 96 | ``` 97 | 98 | ## JSON Schema 案例 99 | 100 | ```tsx 101 | import React from 'react' 102 | import { 103 | Input, 104 | DatePicker, 105 | Editable, 106 | FormItem, 107 | FormButtonGroup, 108 | Submit, 109 | } from '@formily/antd' 110 | import { createForm } from '@formily/core' 111 | import { FormProvider, createSchemaField } from '@formily/react' 112 | 113 | const SchemaField = createSchemaField({ 114 | components: { 115 | DatePicker, 116 | Editable, 117 | Input, 118 | FormItem, 119 | }, 120 | }) 121 | 122 | const form = createForm() 123 | 124 | const schema = { 125 | type: 'object', 126 | properties: { 127 | date: { 128 | type: 'string', 129 | title: '日期', 130 | 'x-decorator': 'Editable', 131 | 'x-component': 'DatePicker', 132 | }, 133 | input: { 134 | type: 'string', 135 | title: '输入框', 136 | 'x-decorator': 'Editable', 137 | 'x-component': 'Input', 138 | }, 139 | void: { 140 | type: 'void', 141 | title: '虚拟节点容器', 142 | 'x-component': 'Editable.Popover', 143 | 'x-reactions': 144 | "{{(field) => field.title = field.query('.void.date2').get('value') || field.title}}", 145 | properties: { 146 | date2: { 147 | type: 'string', 148 | title: '日期', 149 | 'x-decorator': 'FormItem', 150 | 'x-component': 'DatePicker', 151 | }, 152 | input2: { 153 | type: 'string', 154 | title: '输入框', 155 | 'x-decorator': 'FormItem', 156 | 'x-component': 'Input', 157 | }, 158 | }, 159 | }, 160 | iobject: { 161 | type: 'object', 162 | title: '对象节点容器', 163 | 'x-component': 'Editable.Popover', 164 | 'x-reactions': 165 | '{{(field) => field.title = field.value && field.value.date || field.title}}', 166 | properties: { 167 | date: { 168 | type: 'string', 169 | title: '日期', 170 | 'x-decorator': 'FormItem', 171 | 'x-component': 'DatePicker', 172 | }, 173 | input: { 174 | type: 'string', 175 | title: '输入框', 176 | 'x-decorator': 'FormItem', 177 | 'x-component': 'Input', 178 | }, 179 | }, 180 | }, 181 | }, 182 | } 183 | 184 | export default () => ( 185 | <FormProvider form={form}> 186 | <SchemaField schema={schema} /> 187 | <FormButtonGroup> 188 | <Submit onSubmit={console.log}>提交</Submit> 189 | </FormButtonGroup> 190 | </FormProvider> 191 | ) 192 | ``` 193 | 194 | ## 纯 JSX 案例 195 | 196 | ```tsx 197 | import React from 'react' 198 | import { 199 | Input, 200 | DatePicker, 201 | Editable, 202 | FormItem, 203 | FormButtonGroup, 204 | Submit, 205 | } from '@formily/antd' 206 | import { createForm } from '@formily/core' 207 | import { FormProvider, Field, VoidField, ObjectField } from '@formily/react' 208 | 209 | const form = createForm() 210 | 211 | export default () => ( 212 | <FormProvider form={form}> 213 | <Field 214 | name="date" 215 | title="日期" 216 | decorator={[Editable]} 217 | component={[DatePicker]} 218 | /> 219 | <Field 220 | name="input" 221 | title="输入框" 222 | decorator={[Editable]} 223 | component={[Input]} 224 | /> 225 | <VoidField 226 | name="void" 227 | title="虚拟节点容器" 228 | reactions={(field) => { 229 | field.title = field.query('.void.date2').get('value') || field.title 230 | }} 231 | component={[Editable.Popover]} 232 | > 233 | <Field 234 | name="date2" 235 | title="日期" 236 | decorator={[FormItem]} 237 | component={[DatePicker]} 238 | /> 239 | <Field 240 | name="input2" 241 | title="输入框" 242 | decorator={[FormItem]} 243 | component={[Input]} 244 | /> 245 | </VoidField> 246 | <ObjectField 247 | name="iobject" 248 | title="对象节点容器" 249 | reactions={(field) => { 250 | field.title = field.value?.date || field.title 251 | }} 252 | component={[Editable.Popover]} 253 | > 254 | <Field 255 | name="date" 256 | title="日期" 257 | decorator={[FormItem]} 258 | component={[DatePicker]} 259 | /> 260 | <Field 261 | name="input" 262 | title="输入框" 263 | decorator={[FormItem]} 264 | component={[Input]} 265 | /> 266 | </ObjectField> 267 | 268 | <FormButtonGroup> 269 | <Submit onSubmit={console.log}>提交</Submit> 270 | </FormButtonGroup> 271 | </FormProvider> 272 | ) 273 | ``` 274 | 275 | ## API 276 | 277 | ### Editable 278 | 279 | > 内联编辑 280 | 281 | 参考 https://ant.design/components/form-cn/ 中的 FormItem 属性 282 | 283 | ### Editable.Popover 284 | 285 | > 浮层编辑 286 | 287 | 参考 https://ant.design/components/popover-cn/ 288 | ``` -------------------------------------------------------------------------------- /packages/react/src/__tests__/schema.json.spec.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { createForm } from '@formily/core' 3 | import { FormProvider, createSchemaField } from '../index' 4 | import { Schema } from '@formily/json-schema' 5 | import { render } from '@testing-library/react' 6 | 7 | const Input = ({ value, onChange }) => { 8 | return <input data-testid="input" value={value || ''} onChange={onChange} /> 9 | } 10 | 11 | import { Button, Rate } from 'antd' 12 | import { SearchOutlined, DollarOutlined } from '@ant-design/icons' 13 | 14 | describe('json schema field', () => { 15 | test('string field', () => { 16 | const form = createForm() 17 | const SchemaField = createSchemaField({ 18 | components: { 19 | Input, 20 | }, 21 | }) 22 | const { queryByTestId } = render( 23 | <FormProvider form={form}> 24 | <SchemaField 25 | name="string" 26 | schema={ 27 | new Schema({ 28 | type: 'string', 29 | default: '123', 30 | 'x-component': 'Input', 31 | }) 32 | } 33 | /> 34 | </FormProvider> 35 | ) 36 | expect(queryByTestId('input')).toBeVisible() 37 | expect(queryByTestId('input')?.getAttribute('value')).toEqual('123') 38 | }) 39 | test('object field', () => { 40 | const form = createForm() 41 | const SchemaField = createSchemaField({ 42 | components: { 43 | Input, 44 | }, 45 | }) 46 | const { queryByTestId } = render( 47 | <FormProvider form={form}> 48 | <SchemaField 49 | name="object" 50 | schema={{ 51 | type: 'object', 52 | properties: { 53 | string: { 54 | type: 'string', 55 | 'x-component': 'Input', 56 | }, 57 | }, 58 | }} 59 | /> 60 | </FormProvider> 61 | ) 62 | expect(queryByTestId('input')).toBeVisible() 63 | }) 64 | test('x-component-props children', () => { 65 | const form = createForm() 66 | const Text: React.FC = ({ children }) => { 67 | return <div data-testid="children-test">{children}</div> 68 | } 69 | const SchemaField = createSchemaField({ 70 | components: { 71 | Text, 72 | }, 73 | }) 74 | const { queryByTestId } = render( 75 | <FormProvider form={form}> 76 | <SchemaField 77 | name="object" 78 | schema={{ 79 | type: 'object', 80 | properties: { 81 | string: { 82 | type: 'string', 83 | 'x-component': 'Text', 84 | 'x-component-props': { 85 | children: 'children', 86 | }, 87 | }, 88 | }, 89 | }} 90 | /> 91 | </FormProvider> 92 | ) 93 | expect(queryByTestId('children-test')).toBeVisible() 94 | expect(queryByTestId('children-test')?.innerHTML).toEqual('children') 95 | }) 96 | test('x-content', async () => { 97 | const form = createForm() 98 | const Text: React.FC = ({ children }) => { 99 | return <div data-testid="content-test">{children}</div> 100 | } 101 | const SchemaField = createSchemaField({ 102 | components: { 103 | Text, 104 | }, 105 | }) 106 | const { queryByTestId } = render( 107 | <FormProvider form={form}> 108 | <SchemaField 109 | name="object" 110 | schema={{ 111 | type: 'object', 112 | properties: { 113 | string: { 114 | type: 'string', 115 | 'x-component': 'Text', 116 | 'x-content': 'content', 117 | }, 118 | }, 119 | }} 120 | /> 121 | </FormProvider> 122 | ) 123 | expect(queryByTestId('content-test')).toBeVisible() 124 | expect(queryByTestId('content-test')?.innerHTML).toEqual('content') 125 | }) 126 | test('x-slot-node', () => { 127 | const form = createForm() 128 | const SchemaField = createSchemaField({ 129 | components: { 130 | SearchOutlined, 131 | Button, 132 | }, 133 | }) 134 | const { queryByTestId } = render( 135 | <FormProvider form={form}> 136 | <SchemaField 137 | name="object" 138 | schema={{ 139 | type: 'object', 140 | properties: { 141 | icon: { 142 | 'x-slot-node': { 143 | target: 'button.x-component-props.icon', 144 | }, 145 | 'x-component': 'SearchOutlined', 146 | 'x-component-props': { 147 | 'data-testid': 'icon', 148 | }, 149 | }, 150 | button: { 151 | type: 'string', 152 | 'x-component': 'Button', 153 | 'x-component-props': { 154 | 'data-testid': 'button', 155 | }, 156 | }, 157 | }, 158 | }} 159 | /> 160 | </FormProvider> 161 | ) 162 | const button = queryByTestId('button') 163 | const icon = queryByTestId('icon') 164 | 165 | expect(button).toContainElement(icon) 166 | }) 167 | test('x-slot-node render prop', async () => { 168 | const form = createForm() 169 | const SchemaField = createSchemaField({ 170 | components: { 171 | Rate, 172 | DollarOutlined, 173 | }, 174 | }) 175 | const { queryByRole, queryAllByTestId } = render( 176 | <FormProvider form={form}> 177 | <SchemaField 178 | name="object" 179 | schema={{ 180 | type: 'object', 181 | properties: { 182 | icon: { 183 | 'x-slot-node': { 184 | target: 'rate.x-component-props.character', 185 | isRenderProp: true, 186 | }, 187 | 'x-component': 'DollarOutlined', 188 | 'x-component-props': { 189 | 'data-testid': 'icon', 190 | rotate: '{{$slotArgs[0].value * 45}}', 191 | style: { 192 | fontSize: '{{`${$slotArgs[0].value * 10}px`}}', 193 | }, 194 | }, 195 | }, 196 | rate: { 197 | 'x-component': 'Rate', 198 | 'x-component-props': { 199 | defaultValue: 2, 200 | }, 201 | }, 202 | }, 203 | }} 204 | /> 205 | </FormProvider> 206 | ) 207 | 208 | const rate = queryByRole('radiogroup') 209 | expect(rate).toBeVisible() 210 | const icons = queryAllByTestId('icon') 211 | expect(icons).toHaveLength(10) 212 | icons.forEach((icon) => { 213 | expect(rate).toContainElement(icon) 214 | }) 215 | 216 | const style = window.getComputedStyle(icons[0]) 217 | const fontSize = style.fontSize 218 | expect(fontSize).toBe('20px') 219 | }) 220 | }) 221 | ``` -------------------------------------------------------------------------------- /packages/antd/src/form-dialog/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useRef, useLayoutEffect, useState } from 'react' 2 | import { createPortal } from 'react-dom' 3 | import { createForm, IFormProps, Form } from '@formily/core' 4 | import { toJS } from '@formily/reactive' 5 | import { FormProvider, Observer, observer, ReactFC } from '@formily/react' 6 | import { 7 | isNum, 8 | isStr, 9 | isBool, 10 | isFn, 11 | applyMiddleware, 12 | IMiddleware, 13 | } from '@formily/shared' 14 | import { Modal, ModalProps } from 'antd' 15 | import { 16 | usePrefixCls, 17 | loading, 18 | createPortalProvider, 19 | createPortalRoot, 20 | } from '../__builtins__' 21 | 22 | type FormDialogRenderer = 23 | | React.ReactElement 24 | | ((form: Form) => React.ReactElement) 25 | 26 | type ModalTitle = string | number | React.ReactElement 27 | 28 | const isModalTitle = (props: any): props is ModalTitle => { 29 | return ( 30 | isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props) 31 | ) 32 | } 33 | 34 | const getModelProps = (props: any): IModalProps => { 35 | if (isModalTitle(props)) { 36 | return { 37 | title: props, 38 | } 39 | } else { 40 | return props 41 | } 42 | } 43 | 44 | export interface IFormDialog { 45 | forOpen(middleware: IMiddleware<IFormProps>): IFormDialog 46 | forConfirm(middleware: IMiddleware<Form>): IFormDialog 47 | forCancel(middleware: IMiddleware<Form>): IFormDialog 48 | open(props?: IFormProps): Promise<any> 49 | close(): void 50 | } 51 | 52 | export interface IModalProps extends ModalProps { 53 | onOk?: (event: React.MouseEvent<HTMLElement>) => void | boolean 54 | onCancel?: (event: React.MouseEvent<HTMLElement>) => void | boolean 55 | loadingText?: React.ReactNode 56 | } 57 | 58 | export function FormDialog( 59 | title: IModalProps, 60 | id: string, 61 | renderer: FormDialogRenderer 62 | ): IFormDialog 63 | export function FormDialog( 64 | title: IModalProps, 65 | renderer: FormDialogRenderer 66 | ): IFormDialog 67 | export function FormDialog( 68 | title: ModalTitle, 69 | id: string, 70 | renderer: FormDialogRenderer 71 | ): IFormDialog 72 | export function FormDialog( 73 | title: ModalTitle, 74 | renderer: FormDialogRenderer 75 | ): IFormDialog 76 | export function FormDialog(title: any, id: any, renderer?: any): IFormDialog { 77 | if (isFn(id) || React.isValidElement(id)) { 78 | renderer = id 79 | id = 'form-dialog' 80 | } 81 | const env = { 82 | host: document.createElement('div'), 83 | form: null, 84 | promise: null, 85 | openMiddlewares: [], 86 | confirmMiddlewares: [], 87 | cancelMiddlewares: [], 88 | } 89 | const root = createPortalRoot(env.host, id) 90 | const props = getModelProps(title) 91 | const modal = { 92 | ...props, 93 | afterClose: () => { 94 | props?.afterClose?.() 95 | root.unmount() 96 | }, 97 | } 98 | const DialogContent = observer(() => { 99 | return <Fragment>{isFn(renderer) ? renderer(env.form) : renderer}</Fragment> 100 | }) 101 | const renderDialog = ( 102 | visible = true, 103 | resolve?: () => any, 104 | reject?: () => any 105 | ) => { 106 | return ( 107 | <Observer> 108 | {() => ( 109 | <Modal 110 | {...modal} 111 | visible={visible} 112 | confirmLoading={env.form.submitting} 113 | onCancel={(e) => { 114 | if (modal?.onCancel?.(e) !== false) { 115 | reject() 116 | } 117 | }} 118 | onOk={async (e) => { 119 | if (modal?.onOk?.(e) !== false) { 120 | resolve() 121 | } 122 | }} 123 | > 124 | <FormProvider form={env.form}> 125 | <DialogContent /> 126 | </FormProvider> 127 | </Modal> 128 | )} 129 | </Observer> 130 | ) 131 | } 132 | 133 | document.body.appendChild(env.host) 134 | const formDialog = { 135 | forOpen: (middleware: IMiddleware<IFormProps>) => { 136 | if (isFn(middleware)) { 137 | env.openMiddlewares.push(middleware) 138 | } 139 | return formDialog 140 | }, 141 | forConfirm: (middleware: IMiddleware<Form>) => { 142 | if (isFn(middleware)) { 143 | env.confirmMiddlewares.push(middleware) 144 | } 145 | return formDialog 146 | }, 147 | forCancel: (middleware: IMiddleware<Form>) => { 148 | if (isFn(middleware)) { 149 | env.cancelMiddlewares.push(middleware) 150 | } 151 | return formDialog 152 | }, 153 | open: async (props: IFormProps) => { 154 | if (env.promise) return env.promise 155 | env.promise = new Promise(async (resolve, reject) => { 156 | try { 157 | props = await loading(modal.loadingText, () => 158 | applyMiddleware(props, env.openMiddlewares) 159 | ) 160 | env.form = env.form || createForm(props) 161 | } catch (e) { 162 | reject(e) 163 | } 164 | root.render(() => 165 | renderDialog( 166 | true, 167 | () => { 168 | env.form 169 | .submit(async () => { 170 | await applyMiddleware(env.form, env.confirmMiddlewares) 171 | resolve(toJS(env.form.values)) 172 | formDialog.close() 173 | }) 174 | .catch(() => {}) 175 | }, 176 | async () => { 177 | await loading(modal.loadingText, () => 178 | applyMiddleware(env.form, env.cancelMiddlewares) 179 | ) 180 | formDialog.close() 181 | } 182 | ) 183 | ) 184 | }) 185 | return env.promise 186 | }, 187 | close: () => { 188 | if (!env.host) return 189 | root.render(() => renderDialog(false)) 190 | }, 191 | } 192 | return formDialog 193 | } 194 | 195 | const DialogFooter: ReactFC = (props) => { 196 | const ref = useRef<HTMLDivElement>() 197 | const [footer, setFooter] = useState<HTMLDivElement>() 198 | const footerRef = useRef<HTMLDivElement>() 199 | const prefixCls = usePrefixCls('modal') 200 | useLayoutEffect(() => { 201 | const content = ref.current?.closest(`.${prefixCls}-content`) 202 | if (content) { 203 | if (!footerRef.current) { 204 | footerRef.current = content.querySelector(`.${prefixCls}-footer`) 205 | if (!footerRef.current) { 206 | footerRef.current = document.createElement('div') 207 | footerRef.current.classList.add(`${prefixCls}-footer`) 208 | content.appendChild(footerRef.current) 209 | } 210 | } 211 | setFooter(footerRef.current) 212 | } 213 | }) 214 | 215 | footerRef.current = footer 216 | 217 | return ( 218 | <div ref={ref} style={{ display: 'none' }}> 219 | {footer && createPortal(props.children, footer)} 220 | </div> 221 | ) 222 | } 223 | 224 | FormDialog.Footer = DialogFooter 225 | 226 | FormDialog.Portal = createPortalProvider('form-dialog') 227 | 228 | export default FormDialog 229 | ``` -------------------------------------------------------------------------------- /packages/antd/src/form-drawer/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useLayoutEffect, useRef, useState } from 'react' 2 | import { createPortal } from 'react-dom' 3 | import { 4 | createForm, 5 | IFormProps, 6 | Form, 7 | onFormSubmitSuccess, 8 | } from '@formily/core' 9 | import { toJS } from '@formily/reactive' 10 | import { FormProvider, observer, ReactFC } from '@formily/react' 11 | import { 12 | isNum, 13 | isStr, 14 | isBool, 15 | isFn, 16 | applyMiddleware, 17 | IMiddleware, 18 | } from '@formily/shared' 19 | import { Drawer, DrawerProps } from 'antd' 20 | import { 21 | usePrefixCls, 22 | createPortalProvider, 23 | createPortalRoot, 24 | loading, 25 | } from '../__builtins__' 26 | 27 | type FormDrawerRenderer = 28 | | React.ReactElement 29 | | ((form: Form) => React.ReactElement) 30 | 31 | type DrawerTitle = string | number | React.ReactElement 32 | 33 | type EventType = 34 | | React.KeyboardEvent<HTMLDivElement> 35 | | React.MouseEvent<HTMLDivElement | HTMLButtonElement> 36 | 37 | const isDrawerTitle = (props: any): props is DrawerTitle => { 38 | return ( 39 | isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props) 40 | ) 41 | } 42 | 43 | const getDrawerProps = (props: any): IDrawerProps => { 44 | if (isDrawerTitle(props)) { 45 | return { 46 | title: props, 47 | } 48 | } else { 49 | return props 50 | } 51 | } 52 | 53 | export interface IFormDrawer { 54 | forOpen(middleware: IMiddleware<IFormProps>): IFormDrawer 55 | open(props?: IFormProps): Promise<any> 56 | close(): void 57 | } 58 | 59 | export interface IDrawerProps extends DrawerProps { 60 | onClose?: (e: EventType) => void | boolean 61 | loadingText?: React.ReactNode 62 | } 63 | 64 | export function FormDrawer( 65 | title: IDrawerProps, 66 | id: string, 67 | renderer: FormDrawerRenderer 68 | ): IFormDrawer 69 | export function FormDrawer( 70 | title: IDrawerProps, 71 | id: FormDrawerRenderer 72 | ): IFormDrawer 73 | export function FormDrawer( 74 | title: DrawerTitle, 75 | id: string, 76 | renderer: FormDrawerRenderer 77 | ): IFormDrawer 78 | export function FormDrawer( 79 | title: DrawerTitle, 80 | id: FormDrawerRenderer 81 | ): IFormDrawer 82 | export function FormDrawer(title: any, id: any, renderer?: any): IFormDrawer { 83 | if (isFn(id) || React.isValidElement(id)) { 84 | renderer = id 85 | id = 'form-drawer' 86 | } 87 | const env = { 88 | host: document.createElement('div'), 89 | openMiddlewares: [], 90 | form: null, 91 | promise: null, 92 | } 93 | const root = createPortalRoot(env.host, id) 94 | const props = getDrawerProps(title) 95 | const drawer = { 96 | width: '40%', 97 | ...props, 98 | onClose: (e: any) => { 99 | if (props?.onClose?.(e) !== false) { 100 | formDrawer.close() 101 | } 102 | }, 103 | afterVisibleChange: (visible: boolean) => { 104 | props?.afterVisibleChange?.(visible) 105 | if (visible) return 106 | root.unmount() 107 | }, 108 | } 109 | const DrawerContent = observer(() => { 110 | return <Fragment>{isFn(renderer) ? renderer(env.form) : renderer}</Fragment> 111 | }) 112 | const renderDrawer = (visible = true) => { 113 | return ( 114 | <Drawer {...drawer} visible={visible}> 115 | <FormProvider form={env.form}> 116 | <DrawerContent /> 117 | </FormProvider> 118 | </Drawer> 119 | ) 120 | } 121 | 122 | document.body.appendChild(env.host) 123 | const formDrawer = { 124 | forOpen: (middleware: IMiddleware<IFormProps>) => { 125 | if (isFn(middleware)) { 126 | env.openMiddlewares.push(middleware) 127 | } 128 | return formDrawer 129 | }, 130 | open: (props: IFormProps) => { 131 | if (env.promise) return env.promise 132 | env.promise = new Promise(async (resolve, reject) => { 133 | try { 134 | props = await loading(drawer.loadingText, () => 135 | applyMiddleware(props, env.openMiddlewares) 136 | ) 137 | env.form = 138 | env.form || 139 | createForm({ 140 | ...props, 141 | effects(form) { 142 | onFormSubmitSuccess(() => { 143 | resolve(toJS(form.values)) 144 | formDrawer.close() 145 | }) 146 | props?.effects?.(form) 147 | }, 148 | }) 149 | } catch (e) { 150 | reject(e) 151 | } 152 | root.render(() => renderDrawer(false)) 153 | setTimeout(() => { 154 | root.render(() => renderDrawer(true)) 155 | }, 16) 156 | }) 157 | return env.promise 158 | }, 159 | close: () => { 160 | if (!env.host) return 161 | root.render(() => renderDrawer(false)) 162 | }, 163 | } 164 | return formDrawer 165 | } 166 | 167 | const DrawerExtra: ReactFC = (props) => { 168 | const ref = useRef<HTMLDivElement>() 169 | const [extra, setExtra] = useState<HTMLDivElement>() 170 | const extraRef = useRef<HTMLDivElement>() 171 | const prefixCls = usePrefixCls('drawer') 172 | useLayoutEffect(() => { 173 | const content = ref.current 174 | ?.closest(`.${prefixCls}-wrapper-body`) 175 | ?.querySelector(`.${prefixCls}-header`) 176 | if (content) { 177 | if (!extraRef.current) { 178 | extraRef.current = content.querySelector(`.${prefixCls}-extra`) 179 | if (!extraRef.current) { 180 | extraRef.current = document.createElement('div') 181 | extraRef.current.classList.add(`${prefixCls}-extra`) 182 | content.appendChild(extraRef.current) 183 | } 184 | } 185 | setExtra(extraRef.current) 186 | } 187 | }) 188 | 189 | extraRef.current = extra 190 | 191 | return ( 192 | <div ref={ref} style={{ display: 'none' }}> 193 | {extra && createPortal(props.children, extra)} 194 | </div> 195 | ) 196 | } 197 | 198 | const DrawerFooter: ReactFC = (props) => { 199 | const ref = useRef<HTMLDivElement>() 200 | const [footer, setFooter] = useState<HTMLDivElement>() 201 | const footerRef = useRef<HTMLDivElement>() 202 | const prefixCls = usePrefixCls('drawer') 203 | useLayoutEffect(() => { 204 | const content = ref.current?.closest(`.${prefixCls}-wrapper-body`) 205 | if (content) { 206 | if (!footerRef.current) { 207 | footerRef.current = content.querySelector(`.${prefixCls}-footer`) 208 | if (!footerRef.current) { 209 | footerRef.current = document.createElement('div') 210 | footerRef.current.classList.add(`${prefixCls}-footer`) 211 | content.appendChild(footerRef.current) 212 | } 213 | } 214 | setFooter(footerRef.current) 215 | } 216 | }) 217 | 218 | footerRef.current = footer 219 | 220 | return ( 221 | <div ref={ref} style={{ display: 'none' }}> 222 | {footer && createPortal(props.children, footer)} 223 | </div> 224 | ) 225 | } 226 | 227 | FormDrawer.Extra = DrawerExtra 228 | 229 | FormDrawer.Footer = DrawerFooter 230 | 231 | FormDrawer.Portal = createPortalProvider('form-drawer') 232 | 233 | export default FormDrawer 234 | ``` -------------------------------------------------------------------------------- /docs/guide/advanced/destructor.md: -------------------------------------------------------------------------------- ```markdown 1 | # Compatible solution for front-end and back-end data differences 2 | 3 | Many times, we always encounter scenarios where the front-end data structure does not match the back-end data structure. The seemingly simple problem is actually very uncomfortable to solve. The most common problems are: 4 | 5 | The output of the front-end date range component is an array structure, but the format required by the back-end is to split a flat data structure. This problem is largely limited by the back-end domain model. Because from the perspective of back-end model design, splitting the flat structure is the best solution; 6 | 7 | But from the perspective of front-end componentization, the array structure is the best; 8 | 9 | So each side has its truth, but unfortunately, it can only cancel such an unequal treaty at the front end every time. However, with Formily, you don’t need to feel uncomfortable for such an embarrassing situation. **Formily provides the ability to deconstruct the path, which can help users quickly solve such problems.** Let's take a look at an example 10 | 11 | ## Markup Schema Case 12 | 13 | ```tsx 14 | import React from 'react' 15 | import { 16 | Form, 17 | FormItem, 18 | DatePicker, 19 | FormButtonGroup, 20 | Radio, 21 | Submit, 22 | } from '@formily/antd' 23 | import { createForm, onFieldValueChange } from '@formily/core' 24 | import { createSchemaField, FormConsumer } from '@formily/react' 25 | 26 | const SchemaField = createSchemaField({ 27 | components: { 28 | FormItem, 29 | DatePicker, 30 | Radio, 31 | }, 32 | }) 33 | 34 | const form = createForm({ 35 | effects() { 36 | onFieldValueChange('visible_destructor', (field) => { 37 | form.setFieldState('[startDate,endDate]', (state) => { 38 | state.visible = !!field.value 39 | }) 40 | }) 41 | }, 42 | }) 43 | 44 | export default () => { 45 | return ( 46 | <Form form={form} layout="vertical"> 47 | <SchemaField> 48 | <SchemaField.Boolean 49 | name="visible_destructor" 50 | title="Whether to display deconstructed fields" 51 | default={true} 52 | enum={[ 53 | { label: 'yes', value: true }, 54 | { label: 'no', value: false }, 55 | ]} 56 | x-decorator="FormItem" 57 | x-component="Radio.Group" 58 | /> 59 | <SchemaField.String 60 | name="undestructor" 61 | title="before deconstruction" 62 | x-decorator="FormItem" 63 | x-component="DatePicker.RangePicker" 64 | /> 65 | <SchemaField.String 66 | name="[startDate,endDate]" 67 | title="after deconstruction" 68 | default={['2020-11-20', '2021-12-30']} 69 | x-decorator="FormItem" 70 | x-component="DatePicker.RangePicker" 71 | /> 72 | </SchemaField> 73 | <code> 74 | <pre> 75 | <FormConsumer> 76 | {(form) => JSON.stringify(form.values, null, 2)} 77 | </FormConsumer> 78 | </pre> 79 | </code> 80 | <FormButtonGroup> 81 | <Submit onSubmit={console.log}>submit</Submit> 82 | </FormButtonGroup> 83 | </Form> 84 | ) 85 | } 86 | ``` 87 | 88 | ## JSON Schema Cases 89 | 90 | ```tsx 91 | import React from 'react' 92 | import { 93 | Form, 94 | FormItem, 95 | DatePicker, 96 | FormButtonGroup, 97 | Radio, 98 | Submit, 99 | } from '@formily/antd' 100 | import { createForm } from '@formily/core' 101 | import { createSchemaField, FormConsumer } from '@formily/react' 102 | 103 | const SchemaField = createSchemaField({ 104 | components: { 105 | FormItem, 106 | DatePicker, 107 | Radio, 108 | }, 109 | }) 110 | 111 | const form = createForm() 112 | 113 | const schema = { 114 | type: 'object', 115 | properties: { 116 | visible_destructor: { 117 | type: 'boolean', 118 | title: 'Whether to display deconstructed fields', 119 | default: true, 120 | enum: [ 121 | { label: 'yes', value: true }, 122 | { label: 'no', value: false }, 123 | ], 124 | 'x-decorator': 'FormItem', 125 | 'x-component': 'Radio.Group', 126 | }, 127 | undestructor: { 128 | type: 'string', 129 | title: 'before deconstruction', 130 | 'x-decorator': 'FormItem', 131 | 'x-component': 'DatePicker.RangePicker', 132 | }, 133 | '[startDate,endDate]': { 134 | type: 'string', 135 | title: 'after deconstruction', 136 | default: ['2020-11-20', '2021-12-30'], 137 | 'x-decorator': 'FormItem', 138 | 'x-component': 'DatePicker.RangePicker', 139 | 'x-reactions': { 140 | dependencies: ['visible_destructor'], 141 | fulfill: { 142 | state: { 143 | visible: '{{!!$deps[0]}}', 144 | }, 145 | }, 146 | }, 147 | }, 148 | }, 149 | } 150 | 151 | export default () => { 152 | return ( 153 | <Form form={form} layout="vertical"> 154 | <SchemaField schema={schema} /> 155 | <code> 156 | <pre> 157 | <FormConsumer> 158 | {(form) => JSON.stringify(form.values, null, 2)} 159 | </FormConsumer> 160 | </pre> 161 | </code> 162 | <FormButtonGroup> 163 | <Submit onSubmit={console.log}>submit</Submit> 164 | </FormButtonGroup> 165 | </Form> 166 | ) 167 | } 168 | ``` 169 | 170 | ## Pure JSX Case 171 | 172 | ```tsx 173 | import React from 'react' 174 | import { 175 | Form, 176 | FormItem, 177 | DatePicker, 178 | FormButtonGroup, 179 | Radio, 180 | Submit, 181 | } from '@formily/antd' 182 | import { createForm } from '@formily/core' 183 | import { Field, FormConsumer } from '@formily/react' 184 | 185 | const form = createForm() 186 | 187 | export default () => { 188 | return ( 189 | <Form form={form} layout="vertical"> 190 | <Field 191 | name="visible_destructor" 192 | title="Whether to display deconstructed fields" 193 | initialValue={true} 194 | dataSource={[ 195 | { label: 'yes', value: true }, 196 | { label: 'no', value: false }, 197 | ]} 198 | decorator={[FormItem]} 199 | component={[Radio.Group]} 200 | /> 201 | <Field 202 | name="undestructor" 203 | title="before deconstruction" 204 | decorator={[FormItem]} 205 | component={[DatePicker.RangePicker]} 206 | /> 207 | <Field 208 | name="[startDate,endDate]" 209 | title="after deconstruction" 210 | initialValue={['2020-11-20', '2021-12-30']} 211 | decorator={[FormItem]} 212 | component={[DatePicker.RangePicker]} 213 | reactions={(field) => { 214 | field.visible = !!field.query('visible_destructor').value() 215 | }} 216 | /> 217 | <code> 218 | <pre> 219 | <FormConsumer> 220 | {(form) => JSON.stringify(form.values, null, 2)} 221 | </FormConsumer> 222 | </pre> 223 | </code> 224 | <FormButtonGroup> 225 | <Submit onSubmit={console.log}>submit</Submit> 226 | </FormButtonGroup> 227 | </Form> 228 | ) 229 | } 230 | ``` 231 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/.vuepress/components/dumi-previewer.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <client-only> 3 | <section class="dumi-previewer"> 4 | <div class="dumi-previewer-demo"> 5 | <template v-if="demoPath && demo"> 6 | <component :is="demo" /> 7 | </template> 8 | 9 | <template v-else> 10 | <slot name="demo"></slot> 11 | </template> 12 | </div> 13 | 14 | <div class="dumi-previewer-actions"> 15 | <div> 16 | <svg 17 | xmlns="http://www.w3.org/2000/svg" 18 | xmlns:xlink="http://www.w3.org/1999/xlink" 19 | class="dumi-previewer-actions__icon" 20 | viewBox="0 0 256 296" 21 | @click="openCodeSandBox" 22 | > 23 | <path 24 | d="M115.498 261.088v-106.61L23.814 101.73v60.773l41.996 24.347v45.7l49.688 28.54zm23.814.627l50.605-29.151V185.78l42.269-24.495v-60.011l-92.874 53.621v106.82zm80.66-180.887l-48.817-28.289l-42.863 24.872l-43.188-24.897l-49.252 28.667l91.914 52.882l92.206-53.235zM0 222.212V74.495L127.987 0L256 74.182v147.797l-128.016 73.744L0 222.212z" 25 | fill="#000" 26 | ></path> 27 | </svg> 28 | </div> 29 | 30 | <div> 31 | <svg 32 | v-if="copied" 33 | class="dumi-previewer-actions__icon" 34 | style="fill: green" 35 | xmlns="http://www.w3.org/2000/svg" 36 | viewBox="0 0 24 24" 37 | width="24" 38 | height="24" 39 | > 40 | <path fill="none" d="M0 0h24v24H0z" /> 41 | <path 42 | d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z" 43 | /> 44 | </svg> 45 | 46 | <svg 47 | v-else 48 | class="dumi-previewer-actions__icon" 49 | xmlns="http://www.w3.org/2000/svg" 50 | viewBox="0 0 24 24" 51 | width="24" 52 | height="24" 53 | @click="handleCopy" 54 | > 55 | <path fill="none" d="M0 0h24v24H0z" /> 56 | <path 57 | d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zM5.003 8L5 20h10V8H5.003zM9 6h8v10h2V4H9v2z" 58 | /> 59 | </svg> 60 | 61 | <svg 62 | class="dumi-previewer-actions__icon" 63 | xmlns="http://www.w3.org/2000/svg" 64 | viewBox="0 0 24 24" 65 | width="24" 66 | height="24" 67 | @click="handleCollapse" 68 | > 69 | <path fill="none" d="M0 0h24v24H0z" /> 70 | <path 71 | d="M24 12l-5.657 5.657-1.414-1.414L21.172 12l-4.243-4.243 1.414-1.414L24 12zM2.828 12l4.243 4.243-1.414 1.414L0 12l5.657-5.657L7.07 7.757 2.828 12zm6.96 9H7.66l6.552-18h2.128L9.788 21z" 72 | /> 73 | </svg> 74 | </div> 75 | </div> 76 | 77 | <div v-show="!collapsed" class="dumi-previewer-source"> 78 | <div v-html="highlightCode" class="language-vue extra-class" /> 79 | </div> 80 | </section> 81 | </client-only> 82 | </template> 83 | 84 | <script> 85 | import copy from 'copy-to-clipboard' 86 | import highlight from './highlight' 87 | import { createCodeSandBox } from './createCodeSandBox' 88 | 89 | export default { 90 | name: 'dumi-previewer', 91 | 92 | props: { 93 | code: { 94 | type: String, 95 | default: '', 96 | }, 97 | 98 | demoPath: { 99 | type: String, 100 | default: '', 101 | }, 102 | }, 103 | 104 | data() { 105 | return { 106 | collapsed: false, 107 | copied: false, 108 | timerId: null, 109 | demoStr: '', 110 | /** 111 | * take over VuePress render 112 | * 接管VuePress渲染 113 | */ 114 | demo: null, 115 | } 116 | }, 117 | 118 | computed: { 119 | decodedCode() { 120 | return decodeURIComponent(this.code || this.demoStr) 121 | }, 122 | 123 | highlightCode() { 124 | return highlight(this.decodedCode, 'vue') 125 | }, 126 | }, 127 | 128 | created() { 129 | if (this.demoPath) { 130 | import( 131 | /* webpackPrefetch: true */ `../../demos/${this.demoPath}.vue` 132 | ).then((module) => { 133 | this.demo = module.default 134 | }) 135 | import( 136 | /* webpackPrefetch: true */ `!raw-loader!../../demos/${this.demoPath}.vue` 137 | ).then((module) => { 138 | this.demoStr = module.default 139 | }) 140 | } 141 | }, 142 | 143 | beforeDestroy() { 144 | clearTimeout(this.timerId) 145 | }, 146 | 147 | methods: { 148 | handleCollapse() { 149 | this.collapsed = !this.collapsed 150 | }, 151 | 152 | handleCopy() { 153 | this.copied = true 154 | copy(this.decodedCode) 155 | 156 | clearTimeout(this.timer) 157 | this.timerId = setTimeout(() => { 158 | this.copied = false 159 | }, 2000) 160 | }, 161 | 162 | openCodeSandBox() { 163 | createCodeSandBox(this.demoStr) 164 | }, 165 | }, 166 | } 167 | </script> 168 | 169 | <style lang="stylus"> 170 | .dumi-previewer { 171 | background-color: #fff; 172 | border: 1px solid #ebedf1; 173 | border-radius: 1px; 174 | 175 | .dumi-previewer-demo { 176 | padding: 40px 24px; 177 | } 178 | 179 | .dumi-previewer-actions { 180 | display: flex; 181 | align-items: center; 182 | justify-content: space-between; 183 | border-top: 1px dashed #ebedf1; 184 | height: 40px; 185 | padding: 0 1em; 186 | 187 | .dumi-previewer-actions__icon { 188 | width: 16px; 189 | height: 16px; 190 | padding: 8px 4px; 191 | opacity: 0.4; 192 | cursor: pointer; 193 | transition: opacity .3s; 194 | 195 | &:hover { 196 | opacity: 0.6; 197 | } 198 | } 199 | } 200 | 201 | .dumi-previewer-source { 202 | border-top: 1px dashed #ebedf1; 203 | 204 | div[class*="language-"] { 205 | background-color: #f9fafb; 206 | border-radius: 0; 207 | } 208 | 209 | pre[class*="language-"] { 210 | margin: 0 !important; 211 | } 212 | 213 | pre[class*="language-"] code { 214 | color: #000 !important; 215 | } 216 | 217 | .token.cdata,.token.comment,.token.doctype,.token.prolog { 218 | color: #708090 219 | } 220 | 221 | .token.punctuation { 222 | color: #999 223 | } 224 | 225 | .token.namespace { 226 | opacity: .7 227 | } 228 | 229 | .token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag { 230 | color: #905 231 | } 232 | 233 | .token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string { 234 | color: #690 235 | } 236 | 237 | .language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url { 238 | color: #9a6e3a; 239 | background: hsla(0,0%,100%,.5) 240 | } 241 | 242 | .token.atrule,.token.attr-value,.token.keyword { 243 | color: #07a 244 | } 245 | 246 | .token.class-name,.token.function { 247 | color: #dd4a68 248 | } 249 | 250 | .token.important,.token.regex,.token.variable { 251 | color: #e90 252 | } 253 | } 254 | } 255 | </style> 256 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormStep.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormStep 2 | 3 | > 分步表单组件 4 | > 5 | > 注意:该组件只能用在 Schema 场景 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/antd' 12 | import { createForm } from '@formily/core' 13 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/react' 14 | import { Button } from 'antd' 15 | 16 | const SchemaField = createSchemaField({ 17 | components: { 18 | FormItem, 19 | FormStep, 20 | Input, 21 | }, 22 | }) 23 | 24 | const form = createForm() 25 | const formStep = FormStep.createFormStep() 26 | 27 | export default () => { 28 | return ( 29 | <FormProvider form={form}> 30 | <SchemaField> 31 | <SchemaField.Void 32 | x-component="FormStep" 33 | x-component-props={{ formStep }} 34 | > 35 | <SchemaField.Void 36 | x-component="FormStep.StepPane" 37 | x-component-props={{ title: '第一步' }} 38 | > 39 | <SchemaField.String 40 | name="aaa" 41 | x-decorator="FormItem" 42 | required 43 | x-component="Input" 44 | /> 45 | </SchemaField.Void> 46 | <SchemaField.Void 47 | x-component="FormStep.StepPane" 48 | x-component-props={{ title: '第二步' }} 49 | > 50 | <SchemaField.String 51 | name="bbb" 52 | x-decorator="FormItem" 53 | required 54 | x-component="Input" 55 | /> 56 | </SchemaField.Void> 57 | <SchemaField.Void 58 | type="void" 59 | x-component="FormStep.StepPane" 60 | x-component-props={{ title: '第三步' }} 61 | > 62 | <SchemaField.String 63 | name="ccc" 64 | x-decorator="FormItem" 65 | required 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | </SchemaField.Void> 70 | </SchemaField> 71 | <FormConsumer> 72 | {() => ( 73 | <FormButtonGroup> 74 | <Button 75 | disabled={!formStep.allowBack} 76 | onClick={() => { 77 | formStep.back() 78 | }} 79 | > 80 | 上一步 81 | </Button> 82 | <Button 83 | disabled={!formStep.allowNext} 84 | onClick={() => { 85 | formStep.next() 86 | }} 87 | > 88 | 下一步 89 | </Button> 90 | <Button 91 | disabled={formStep.allowNext} 92 | onClick={() => { 93 | formStep.submit(console.log) 94 | }} 95 | > 96 | 提交 97 | </Button> 98 | </FormButtonGroup> 99 | )} 100 | </FormConsumer> 101 | </FormProvider> 102 | ) 103 | } 104 | ``` 105 | 106 | ## JSON Schema 案例 107 | 108 | ```tsx 109 | import React from 'react' 110 | import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/antd' 111 | import { createForm } from '@formily/core' 112 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/react' 113 | import { Button } from 'antd' 114 | 115 | const SchemaField = createSchemaField({ 116 | components: { 117 | FormItem, 118 | FormStep, 119 | Input, 120 | }, 121 | }) 122 | 123 | const form = createForm() 124 | const formStep = FormStep.createFormStep() 125 | 126 | const schema = { 127 | type: 'object', 128 | properties: { 129 | step: { 130 | type: 'void', 131 | 'x-component': 'FormStep', 132 | 'x-component-props': { 133 | formStep: '{{formStep}}', 134 | }, 135 | properties: { 136 | step1: { 137 | type: 'void', 138 | 'x-component': 'FormStep.StepPane', 139 | 'x-component-props': { 140 | title: '第一步', 141 | }, 142 | properties: { 143 | aaa: { 144 | type: 'string', 145 | title: 'AAA', 146 | required: true, 147 | 'x-decorator': 'FormItem', 148 | 'x-component': 'Input', 149 | }, 150 | }, 151 | }, 152 | step2: { 153 | type: 'void', 154 | 'x-component': 'FormStep.StepPane', 155 | 'x-component-props': { 156 | title: '第二步', 157 | }, 158 | properties: { 159 | bbb: { 160 | type: 'string', 161 | title: 'AAA', 162 | required: true, 163 | 'x-decorator': 'FormItem', 164 | 'x-component': 'Input', 165 | }, 166 | }, 167 | }, 168 | step3: { 169 | type: 'void', 170 | 'x-component': 'FormStep.StepPane', 171 | 'x-component-props': { 172 | title: '第三步', 173 | }, 174 | properties: { 175 | ccc: { 176 | type: 'string', 177 | title: 'AAA', 178 | required: true, 179 | 'x-decorator': 'FormItem', 180 | 'x-component': 'Input', 181 | }, 182 | }, 183 | }, 184 | }, 185 | }, 186 | }, 187 | } 188 | 189 | export default () => { 190 | return ( 191 | <FormProvider form={form}> 192 | <SchemaField schema={schema} scope={{ formStep }} /> 193 | <FormConsumer> 194 | {() => ( 195 | <FormButtonGroup> 196 | <Button 197 | disabled={!formStep.allowBack} 198 | onClick={() => { 199 | formStep.back() 200 | }} 201 | > 202 | 上一步 203 | </Button> 204 | <Button 205 | disabled={!formStep.allowNext} 206 | onClick={() => { 207 | formStep.next() 208 | }} 209 | > 210 | 下一步 211 | </Button> 212 | <Button 213 | disabled={formStep.allowNext} 214 | onClick={() => { 215 | formStep.submit(console.log) 216 | }} 217 | > 218 | 提交 219 | </Button> 220 | </FormButtonGroup> 221 | )} 222 | </FormConsumer> 223 | </FormProvider> 224 | ) 225 | } 226 | ``` 227 | 228 | ## API 229 | 230 | ### FormStep 231 | 232 | | 属性名 | 类型 | 描述 | 默认值 | 233 | | -------- | --------- | -------------------------------------------------- | ------ | 234 | | formStep | IFormStep | 传入通过 createFormStep/useFormStep 创建出来的模型 | | 235 | 236 | 其余参考 https://ant.design/components/steps-cn/ 237 | 238 | ### FormStep.StepPane 239 | 240 | 参考 https://ant.design/components/steps-cn/ Steps.Step 属性 241 | 242 | ### FormStep.createFormStep 243 | 244 | ```ts pure 245 | import { Form } from '@formily/core' 246 | 247 | interface createFormStep { 248 | (current?: number): IFormStep 249 | } 250 | 251 | interface IFormTab { 252 | //当前索引 253 | current: number 254 | //是否允许向后 255 | allowNext: boolean 256 | //是否允许向前 257 | allowBack: boolean 258 | //设置当前索引 259 | setCurrent(key: number): void 260 | //提交表单 261 | submit: Form['submit'] 262 | //向后 263 | next(): void 264 | //向前 265 | back(): void 266 | } 267 | ``` 268 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormStep.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormStep 2 | 3 | > 分步表单组件 4 | > 5 | > 注意:该组件只能用在 Schema 场景 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/next' 12 | import { createForm } from '@formily/core' 13 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/react' 14 | import { Button } from '@alifd/next' 15 | 16 | const SchemaField = createSchemaField({ 17 | components: { 18 | FormItem, 19 | FormStep, 20 | Input, 21 | }, 22 | }) 23 | 24 | const form = createForm() 25 | const formStep = FormStep.createFormStep() 26 | 27 | export default () => { 28 | return ( 29 | <FormProvider form={form}> 30 | <SchemaField> 31 | <SchemaField.Void 32 | x-component="FormStep" 33 | x-component-props={{ formStep }} 34 | > 35 | <SchemaField.Void 36 | x-component="FormStep.StepPane" 37 | x-component-props={{ title: '第一步' }} 38 | > 39 | <SchemaField.String 40 | name="aaa" 41 | x-decorator="FormItem" 42 | required 43 | x-component="Input" 44 | /> 45 | </SchemaField.Void> 46 | <SchemaField.Void 47 | x-component="FormStep.StepPane" 48 | x-component-props={{ title: '第二步' }} 49 | > 50 | <SchemaField.String 51 | name="bbb" 52 | x-decorator="FormItem" 53 | required 54 | x-component="Input" 55 | /> 56 | </SchemaField.Void> 57 | <SchemaField.Void 58 | type="void" 59 | x-component="FormStep.StepPane" 60 | x-component-props={{ title: '第三步' }} 61 | > 62 | <SchemaField.String 63 | name="ccc" 64 | x-decorator="FormItem" 65 | required 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | </SchemaField.Void> 70 | </SchemaField> 71 | <FormConsumer> 72 | {() => ( 73 | <FormButtonGroup> 74 | <Button 75 | disabled={!formStep.allowBack} 76 | onClick={() => { 77 | formStep.back() 78 | }} 79 | > 80 | 上一步 81 | </Button> 82 | <Button 83 | disabled={!formStep.allowNext} 84 | onClick={() => { 85 | formStep.next() 86 | }} 87 | > 88 | 下一步 89 | </Button> 90 | <Button 91 | disabled={formStep.allowNext} 92 | onClick={() => { 93 | formStep.submit(console.log) 94 | }} 95 | > 96 | 提交 97 | </Button> 98 | </FormButtonGroup> 99 | )} 100 | </FormConsumer> 101 | </FormProvider> 102 | ) 103 | } 104 | ``` 105 | 106 | ## JSON Schema 案例 107 | 108 | ```tsx 109 | import React from 'react' 110 | import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/next' 111 | import { createForm } from '@formily/core' 112 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/react' 113 | import { Button } from '@alifd/next' 114 | 115 | const SchemaField = createSchemaField({ 116 | components: { 117 | FormItem, 118 | FormStep, 119 | Input, 120 | }, 121 | }) 122 | 123 | const form = createForm() 124 | const formStep = FormStep.createFormStep() 125 | 126 | const schema = { 127 | type: 'object', 128 | properties: { 129 | step: { 130 | type: 'void', 131 | 'x-component': 'FormStep', 132 | 'x-component-props': { 133 | formStep: '{{formStep}}', 134 | }, 135 | properties: { 136 | step1: { 137 | type: 'void', 138 | 'x-component': 'FormStep.StepPane', 139 | 'x-component-props': { 140 | title: '第一步', 141 | }, 142 | properties: { 143 | aaa: { 144 | type: 'string', 145 | title: 'AAA', 146 | required: true, 147 | 'x-decorator': 'FormItem', 148 | 'x-component': 'Input', 149 | }, 150 | }, 151 | }, 152 | step2: { 153 | type: 'void', 154 | 'x-component': 'FormStep.StepPane', 155 | 'x-component-props': { 156 | title: '第二步', 157 | }, 158 | properties: { 159 | bbb: { 160 | type: 'string', 161 | title: 'AAA', 162 | required: true, 163 | 'x-decorator': 'FormItem', 164 | 'x-component': 'Input', 165 | }, 166 | }, 167 | }, 168 | step3: { 169 | type: 'void', 170 | 'x-component': 'FormStep.StepPane', 171 | 'x-component-props': { 172 | title: '第三步', 173 | }, 174 | properties: { 175 | ccc: { 176 | type: 'string', 177 | title: 'AAA', 178 | required: true, 179 | 'x-decorator': 'FormItem', 180 | 'x-component': 'Input', 181 | }, 182 | }, 183 | }, 184 | }, 185 | }, 186 | }, 187 | } 188 | 189 | export default () => { 190 | return ( 191 | <FormProvider form={form}> 192 | <SchemaField schema={schema} scope={{ formStep }} /> 193 | <FormConsumer> 194 | {() => ( 195 | <FormButtonGroup> 196 | <Button 197 | disabled={!formStep.allowBack} 198 | onClick={() => { 199 | formStep.back() 200 | }} 201 | > 202 | 上一步 203 | </Button> 204 | <Button 205 | disabled={!formStep.allowNext} 206 | onClick={() => { 207 | formStep.next() 208 | }} 209 | > 210 | 下一步 211 | </Button> 212 | <Button 213 | disabled={formStep.allowNext} 214 | onClick={() => { 215 | formStep.submit(console.log) 216 | }} 217 | > 218 | 提交 219 | </Button> 220 | </FormButtonGroup> 221 | )} 222 | </FormConsumer> 223 | </FormProvider> 224 | ) 225 | } 226 | ``` 227 | 228 | ## API 229 | 230 | ### FormStep 231 | 232 | | 属性名 | 类型 | 描述 | 默认值 | 233 | | -------- | --------- | -------------------------------------------------- | ------ | 234 | | formStep | IFormStep | 传入通过 createFormStep/useFormStep 创建出来的模型 | | 235 | 236 | 其余参考 https://fusion.design/pc/component/basic/step 237 | 238 | ### FormStep.StepPane 239 | 240 | 参考 https://fusion.design/pc/component/basic/step Steps.Step 属性 241 | 242 | ### FormStep.createFormStep 243 | 244 | ```ts pure 245 | import { Form } from '@formily/core' 246 | 247 | interface createFormStep { 248 | (current?: number): IFormStep 249 | } 250 | 251 | interface IFormTab { 252 | //当前索引 253 | current: number 254 | //是否允许向后 255 | allowNext: boolean 256 | //是否允许向前 257 | allowBack: boolean 258 | //设置当前索引 259 | setCurrent(key: number): void 260 | //提交表单 261 | submit: Form['submit'] 262 | //向后 263 | next(): void 264 | //向前 265 | back(): void 266 | } 267 | ``` 268 | ``` -------------------------------------------------------------------------------- /packages/element/docs/.vuepress/components/dumi-previewer.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <client-only> 3 | <section class="dumi-previewer"> 4 | <div class="dumi-previewer-demo"> 5 | <template v-if="demoPath && demo"> 6 | <component :is="demo" /> 7 | </template> 8 | 9 | <template v-else> 10 | <slot name="demo"></slot> 11 | </template> 12 | </div> 13 | 14 | <div class="dumi-previewer-actions"> 15 | <div> 16 | <svg 17 | xmlns="http://www.w3.org/2000/svg" 18 | xmlns:xlink="http://www.w3.org/1999/xlink" 19 | class="dumi-previewer-actions__icon" 20 | viewBox="0 0 256 296" 21 | @click="openCodeSandBox" 22 | > 23 | <path 24 | d="M115.498 261.088v-106.61L23.814 101.73v60.773l41.996 24.347v45.7l49.688 28.54zm23.814.627l50.605-29.151V185.78l42.269-24.495v-60.011l-92.874 53.621v106.82zm80.66-180.887l-48.817-28.289l-42.863 24.872l-43.188-24.897l-49.252 28.667l91.914 52.882l92.206-53.235zM0 222.212V74.495L127.987 0L256 74.182v147.797l-128.016 73.744L0 222.212z" 25 | fill="#000" 26 | ></path> 27 | </svg> 28 | </div> 29 | 30 | <div> 31 | <svg 32 | v-if="copied" 33 | class="dumi-previewer-actions__icon" 34 | style="fill: green" 35 | xmlns="http://www.w3.org/2000/svg" 36 | viewBox="0 0 24 24" 37 | width="24" 38 | height="24" 39 | > 40 | <path fill="none" d="M0 0h24v24H0z" /> 41 | <path 42 | d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z" 43 | /> 44 | </svg> 45 | 46 | <svg 47 | v-else 48 | class="dumi-previewer-actions__icon" 49 | xmlns="http://www.w3.org/2000/svg" 50 | viewBox="0 0 24 24" 51 | width="24" 52 | height="24" 53 | @click="handleCopy" 54 | > 55 | <path fill="none" d="M0 0h24v24H0z" /> 56 | <path 57 | d="M7 6V3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1.001 1.001 0 0 1 3 21l.003-14c0-.552.45-1 1.007-1H7zM5.003 8L5 20h10V8H5.003zM9 6h8v10h2V4H9v2z" 58 | /> 59 | </svg> 60 | 61 | <svg 62 | class="dumi-previewer-actions__icon" 63 | xmlns="http://www.w3.org/2000/svg" 64 | viewBox="0 0 24 24" 65 | width="24" 66 | height="24" 67 | @click="handleCollapse" 68 | > 69 | <path fill="none" d="M0 0h24v24H0z" /> 70 | <path 71 | d="M24 12l-5.657 5.657-1.414-1.414L21.172 12l-4.243-4.243 1.414-1.414L24 12zM2.828 12l4.243 4.243-1.414 1.414L0 12l5.657-5.657L7.07 7.757 2.828 12zm6.96 9H7.66l6.552-18h2.128L9.788 21z" 72 | /> 73 | </svg> 74 | </div> 75 | </div> 76 | 77 | <div v-show="!innerCollapsed" class="dumi-previewer-source"> 78 | <div v-html="highlightCode" class="language-vue extra-class" /> 79 | </div> 80 | </section> 81 | </client-only> 82 | </template> 83 | 84 | <script> 85 | import copy from 'copy-to-clipboard' 86 | import highlight from './highlight' 87 | import { createCodeSandBox } from './createCodeSandBox' 88 | 89 | export default { 90 | name: 'dumi-previewer', 91 | 92 | props: { 93 | code: { 94 | type: String, 95 | default: '', 96 | }, 97 | 98 | demoPath: { 99 | type: String, 100 | default: '', 101 | }, 102 | collapsed: { 103 | type: Boolean, 104 | default: true, 105 | }, 106 | }, 107 | 108 | data() { 109 | return { 110 | innerCollapsed: this.collapsed, 111 | copied: false, 112 | timerId: null, 113 | demoStr: '', 114 | /** 115 | * take over VuePress render 116 | * 接管VuePress渲染 117 | */ 118 | demo: null, 119 | } 120 | }, 121 | 122 | computed: { 123 | decodedCode() { 124 | return this.code || this.demoStr 125 | }, 126 | 127 | highlightCode() { 128 | return highlight(this.decodedCode, 'vue') 129 | }, 130 | }, 131 | 132 | created() { 133 | if (this.demoPath) { 134 | import( 135 | /* webpackPrefetch: true */ `../../demos/${this.demoPath}.vue` 136 | ).then((module) => { 137 | this.demo = module.default 138 | }) 139 | import( 140 | /* webpackPrefetch: true */ `!raw-loader!../../demos/${this.demoPath}.vue` 141 | ).then((module) => { 142 | this.demoStr = module.default 143 | }) 144 | } 145 | }, 146 | 147 | beforeDestroy() { 148 | clearTimeout(this.timerId) 149 | }, 150 | 151 | methods: { 152 | handleCollapse() { 153 | this.innerCollapsed = !this.innerCollapsed 154 | }, 155 | 156 | handleCopy() { 157 | this.copied = true 158 | copy(this.decodedCode) 159 | 160 | clearTimeout(this.timer) 161 | this.timerId = setTimeout(() => { 162 | this.copied = false 163 | }, 2000) 164 | }, 165 | 166 | openCodeSandBox() { 167 | createCodeSandBox(this.demoStr) 168 | }, 169 | }, 170 | } 171 | </script> 172 | 173 | <style lang="stylus"> 174 | .dumi-previewer { 175 | background-color: #fff; 176 | border: 1px solid #ebedf1; 177 | border-radius: 1px; 178 | 179 | .dumi-previewer-demo { 180 | padding: 40px 24px; 181 | } 182 | 183 | .dumi-previewer-actions { 184 | display: flex; 185 | align-items: center; 186 | justify-content: space-between; 187 | border-top: 1px dashed #ebedf1; 188 | height: 40px; 189 | padding: 0 1em; 190 | 191 | .dumi-previewer-actions__icon { 192 | width: 16px; 193 | height: 16px; 194 | padding: 8px 4px; 195 | opacity: 0.4; 196 | cursor: pointer; 197 | transition: opacity .3s; 198 | 199 | &:hover { 200 | opacity: 0.6; 201 | } 202 | } 203 | } 204 | 205 | .dumi-previewer-source { 206 | border-top: 1px dashed #ebedf1; 207 | 208 | div[class*="language-"] { 209 | background-color: #f9fafb; 210 | border-radius: 0; 211 | } 212 | 213 | pre[class*="language-"] { 214 | margin: 0 !important; 215 | } 216 | 217 | pre[class*="language-"] code { 218 | color: #000 !important; 219 | } 220 | 221 | .token.cdata,.token.comment,.token.doctype,.token.prolog { 222 | color: #708090 223 | } 224 | 225 | .token.punctuation { 226 | color: #999 227 | } 228 | 229 | .token.namespace { 230 | opacity: .7 231 | } 232 | 233 | .token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag { 234 | color: #905 235 | } 236 | 237 | .token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string { 238 | color: #690 239 | } 240 | 241 | .language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url { 242 | color: #9a6e3a; 243 | background: hsla(0,0%,100%,.5) 244 | } 245 | 246 | .token.atrule,.token.attr-value,.token.keyword { 247 | color: #07a 248 | } 249 | 250 | .token.class-name,.token.function { 251 | color: #dd4a68 252 | } 253 | 254 | .token.important,.token.regex,.token.variable { 255 | color: #e90 256 | } 257 | } 258 | } 259 | </style> 260 | ``` -------------------------------------------------------------------------------- /packages/react/src/components/SchemaField.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useContext, Fragment } from 'react' 2 | import { ISchema, Schema } from '@formily/json-schema' 3 | import { RecursionField } from './RecursionField' 4 | import { render } from '../shared/render' 5 | import { 6 | SchemaMarkupContext, 7 | SchemaOptionsContext, 8 | SchemaComponentsContext, 9 | } from '../shared' 10 | import { 11 | ReactComponentPath, 12 | JSXComponent, 13 | ISchemaFieldReactFactoryOptions, 14 | SchemaReactComponents, 15 | ISchemaFieldProps, 16 | ISchemaMarkupFieldProps, 17 | ISchemaTypeFieldProps, 18 | } from '../types' 19 | import { lazyMerge } from '@formily/shared' 20 | import { ExpressionScope } from './ExpressionScope' 21 | const env = { 22 | nonameId: 0, 23 | } 24 | 25 | const getRandomName = () => { 26 | return `NO_NAME_FIELD_$${env.nonameId++}` 27 | } 28 | 29 | export function createSchemaField<Components extends SchemaReactComponents>( 30 | options: ISchemaFieldReactFactoryOptions<Components> = {} 31 | ) { 32 | function SchemaField< 33 | Decorator extends JSXComponent, 34 | Component extends JSXComponent 35 | >(props: ISchemaFieldProps<Decorator, Component>) { 36 | const schema = Schema.isSchemaInstance(props.schema) 37 | ? props.schema 38 | : new Schema({ 39 | type: 'object', 40 | ...props.schema, 41 | }) 42 | const renderMarkup = () => { 43 | env.nonameId = 0 44 | if (props.schema) return null 45 | return render( 46 | <SchemaMarkupContext.Provider value={schema}> 47 | {props.children} 48 | </SchemaMarkupContext.Provider> 49 | ) 50 | } 51 | 52 | const renderChildren = () => { 53 | return <RecursionField {...props} schema={schema} /> 54 | } 55 | 56 | return ( 57 | <SchemaOptionsContext.Provider value={options}> 58 | <SchemaComponentsContext.Provider 59 | value={lazyMerge(options.components, props.components)} 60 | > 61 | <ExpressionScope value={lazyMerge(options.scope, props.scope)}> 62 | {renderMarkup()} 63 | {renderChildren()} 64 | </ExpressionScope> 65 | </SchemaComponentsContext.Provider> 66 | </SchemaOptionsContext.Provider> 67 | ) 68 | } 69 | 70 | SchemaField.displayName = 'SchemaField' 71 | 72 | function MarkupRender(props: any) { 73 | const parent = useContext(SchemaMarkupContext) 74 | if (!parent) return <Fragment /> 75 | const renderChildren = () => { 76 | return <React.Fragment>{props.children}</React.Fragment> 77 | } 78 | const appendArraySchema = (schema: ISchema) => { 79 | const items = parent.items as Schema 80 | if (items && items.name !== props.name) { 81 | return parent.addProperty(props.name, schema) 82 | } else { 83 | return parent.setItems(schema) 84 | } 85 | } 86 | if (parent.type === 'object' || parent.type === 'void') { 87 | const schema = parent.addProperty(props.name, props) 88 | return ( 89 | <SchemaMarkupContext.Provider value={schema}> 90 | {renderChildren()} 91 | </SchemaMarkupContext.Provider> 92 | ) 93 | } else if (parent.type === 'array') { 94 | const schema = appendArraySchema(props) 95 | return ( 96 | <SchemaMarkupContext.Provider 97 | value={Array.isArray(schema) ? schema[0] : schema} 98 | > 99 | {props.children} 100 | </SchemaMarkupContext.Provider> 101 | ) 102 | } else { 103 | return renderChildren() 104 | } 105 | } 106 | 107 | function MarkupField< 108 | Decorator extends ReactComponentPath<Components>, 109 | Component extends ReactComponentPath<Components> 110 | >(props: ISchemaMarkupFieldProps<Components, Component, Decorator>) { 111 | return <MarkupRender {...props} name={props.name || getRandomName()} /> 112 | } 113 | 114 | MarkupField.displayName = 'MarkupField' 115 | 116 | function StringField< 117 | Decorator extends ReactComponentPath<Components>, 118 | Component extends ReactComponentPath<Components> 119 | >(props: ISchemaTypeFieldProps<Components, Component, Decorator>) { 120 | return <MarkupField {...props} type="string" /> 121 | } 122 | 123 | StringField.displayName = 'StringField' 124 | 125 | function ObjectField< 126 | Decorator extends ReactComponentPath<Components>, 127 | Component extends ReactComponentPath<Components> 128 | >(props: ISchemaTypeFieldProps<Components, Component, Decorator>) { 129 | return <MarkupField {...props} type="object" /> 130 | } 131 | 132 | ObjectField.displayName = 'ObjectField' 133 | 134 | function ArrayField< 135 | Decorator extends ReactComponentPath<Components>, 136 | Component extends ReactComponentPath<Components> 137 | >(props: ISchemaTypeFieldProps<Components, Component, Decorator>) { 138 | return <MarkupField {...props} type="array" /> 139 | } 140 | 141 | ArrayField.displayName = 'ArrayField' 142 | function BooleanField< 143 | Decorator extends ReactComponentPath<Components>, 144 | Component extends ReactComponentPath<Components> 145 | >(props: ISchemaTypeFieldProps<Components, Component, Decorator>) { 146 | return <MarkupField {...props} type="boolean" /> 147 | } 148 | 149 | BooleanField.displayName = 'BooleanField' 150 | 151 | function NumberField< 152 | Decorator extends ReactComponentPath<Components>, 153 | Component extends ReactComponentPath<Components> 154 | >(props: ISchemaTypeFieldProps<Components, Component, Decorator>) { 155 | return <MarkupField {...props} type="number" /> 156 | } 157 | 158 | NumberField.displayName = 'NumberField' 159 | 160 | function DateField< 161 | Decorator extends ReactComponentPath<Components>, 162 | Component extends ReactComponentPath<Components> 163 | >(props: ISchemaTypeFieldProps<Components, Component, Decorator>) { 164 | return <MarkupField {...props} type="date" /> 165 | } 166 | 167 | DateField.displayName = 'DateField' 168 | 169 | function DateTimeField< 170 | Decorator extends ReactComponentPath<Components>, 171 | Component extends ReactComponentPath<Components> 172 | >(props: ISchemaTypeFieldProps<Components, Component, Decorator>) { 173 | return <MarkupField {...props} type="datetime" /> 174 | } 175 | 176 | DateTimeField.displayName = 'DateTimeField' 177 | 178 | function VoidField< 179 | Decorator extends ReactComponentPath<Components>, 180 | Component extends ReactComponentPath<Components> 181 | >(props: ISchemaTypeFieldProps<Components, Component, Decorator>) { 182 | return <MarkupField {...props} type="void" /> 183 | } 184 | 185 | VoidField.displayName = 'VoidField' 186 | 187 | SchemaField.Markup = MarkupField 188 | SchemaField.String = StringField 189 | SchemaField.Object = ObjectField 190 | SchemaField.Array = ArrayField 191 | SchemaField.Boolean = BooleanField 192 | SchemaField.Date = DateField 193 | SchemaField.DateTime = DateTimeField 194 | SchemaField.Void = VoidField 195 | SchemaField.Number = NumberField 196 | 197 | return SchemaField 198 | } 199 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/PreviewText.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # PreviewText 2 | 3 | > 阅读态组件,主要用来实现类 Input,类 DatePicker 这些组件的阅读态 4 | 5 | ## 简单用例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { PreviewText, FormItem, FormLayout } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | PreviewText, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => { 23 | return ( 24 | <FormLayout labelCol={6} wrapperCol={10}> 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.String 28 | x-decorator="FormItem" 29 | title="文本预览" 30 | x-component="PreviewText.Input" 31 | default={'Hello world'} 32 | /> 33 | <SchemaField.String 34 | x-decorator="FormItem" 35 | title="选择项预览" 36 | x-component="PreviewText.Select" 37 | x-component-props={{ 38 | mode: 'multiple', 39 | }} 40 | default={['123', '222']} 41 | enum={[ 42 | { label: 'A111', value: '123' }, 43 | { label: 'A222', value: '222' }, 44 | ]} 45 | /> 46 | <SchemaField.String 47 | x-decorator="FormItem" 48 | title="树选择预览" 49 | x-component="PreviewText.TreeSelect" 50 | x-component-props={{ 51 | multiple: true, 52 | }} 53 | default={['123', '222']} 54 | enum={[ 55 | { label: 'A111', value: '123' }, 56 | { label: 'A222', value: '222' }, 57 | ]} 58 | /> 59 | <SchemaField.String 60 | x-decorator="FormItem" 61 | title="树选择(treeData)预览" 62 | x-component="PreviewText.TreeSelect" 63 | x-component-props={{ 64 | multiple: true, 65 | treeNodeLabelProp: 'name', 66 | treeData: [ 67 | { name: 'A111', value: '123' }, 68 | { name: 'A222', value: '222' }, 69 | ], 70 | }} 71 | default={['123', '222']} 72 | /> 73 | <SchemaField.String 74 | x-decorator="FormItem" 75 | title="日期预览" 76 | x-component="PreviewText.DatePicker" 77 | default={'2020-11-23 22:15:20'} 78 | /> 79 | <SchemaField.String 80 | x-decorator="FormItem" 81 | title="Cascader预览" 82 | x-component="PreviewText.Cascader" 83 | default={'yuhang'} 84 | enum={[ 85 | { 86 | label: '杭州', 87 | value: 'hangzhou', 88 | children: [ 89 | { 90 | label: '余杭', 91 | value: 'yuhang', 92 | }, 93 | ], 94 | }, 95 | ]} 96 | /> 97 | </SchemaField> 98 | </FormProvider> 99 | </FormLayout> 100 | ) 101 | } 102 | ``` 103 | 104 | ## 扩展阅读态 105 | 106 | ```tsx 107 | import React from 'react' 108 | import { 109 | PreviewText, 110 | FormItem, 111 | FormLayout, 112 | FormButtonGroup, 113 | } from '@formily/antd' 114 | import { createForm } from '@formily/core' 115 | import { 116 | FormProvider, 117 | mapReadPretty, 118 | connect, 119 | createSchemaField, 120 | } from '@formily/react' 121 | import { Button, Input as AntdInput } from 'antd' 122 | 123 | const Input = connect(AntdInput, mapReadPretty(PreviewText.Input)) 124 | 125 | const SchemaField = createSchemaField({ 126 | components: { 127 | Input, 128 | FormItem, 129 | PreviewText, 130 | }, 131 | }) 132 | 133 | const form = createForm() 134 | 135 | export default () => { 136 | return ( 137 | <PreviewText.Placeholder value="暂无数据"> 138 | <FormLayout labelCol={6} wrapperCol={10}> 139 | <FormProvider form={form}> 140 | <SchemaField> 141 | <SchemaField.Markup 142 | type="string" 143 | x-decorator="FormItem" 144 | title="文本预览" 145 | required 146 | x-component="Input" 147 | default={'Hello world'} 148 | /> 149 | <SchemaField.Markup 150 | type="string" 151 | x-decorator="FormItem" 152 | title="选择项预览" 153 | x-component="PreviewText.Select" 154 | x-component-props={{ 155 | mode: 'multiple', 156 | }} 157 | default={['123']} 158 | enum={[ 159 | { label: 'A111', value: '123' }, 160 | { label: 'A222', value: '222' }, 161 | ]} 162 | /> 163 | <SchemaField.Markup 164 | type="string" 165 | x-decorator="FormItem" 166 | title="日期预览" 167 | x-component="PreviewText.DatePicker" 168 | /> 169 | <SchemaField.Markup 170 | type="string" 171 | x-decorator="FormItem" 172 | title="Cascader预览" 173 | x-component="PreviewText.Cascader" 174 | default={'yuhang'} 175 | enum={[ 176 | { 177 | label: '杭州', 178 | value: 'hangzhou', 179 | children: [ 180 | { 181 | label: '余杭', 182 | value: 'yuhang', 183 | }, 184 | ], 185 | }, 186 | ]} 187 | /> 188 | </SchemaField> 189 | <FormButtonGroup.FormItem> 190 | <Button 191 | onClick={() => { 192 | form.setState((state) => { 193 | state.editable = !state.editable 194 | }) 195 | }} 196 | > 197 | 切换阅读态 198 | </Button> 199 | </FormButtonGroup.FormItem> 200 | </FormProvider> 201 | </FormLayout> 202 | </PreviewText.Placeholder> 203 | ) 204 | } 205 | ``` 206 | 207 | ## API 208 | 209 | ### PreviewText.Input 210 | 211 | 参考 https://ant.design/components/input-cn/ 212 | 213 | ### PreviewText.Select 214 | 215 | 参考 https://ant.design/components/select-cn/ 216 | 217 | ### PreviewText.TreeSelect 218 | 219 | 参考 https://ant.design/components/tree-select-cn/ 220 | 221 | ### PreviewText.Cascader 222 | 223 | 参考 https://ant.design/components/cascader-cn/ 224 | 225 | ### PreviewText.DatePicker 226 | 227 | 参考 https://ant.design/components/date-picker-cn/ 228 | 229 | ### PreviewText.DateRangePicker 230 | 231 | 参考 https://ant.design/components/date-picker-cn/ 232 | 233 | ### PreviewText.TimePicker 234 | 235 | 参考 https://ant.design/components/time-picker-cn/ 236 | 237 | ### PreviewText.TimeRangePicker 238 | 239 | 参考 https://ant.design/components/time-picker-cn/ 240 | 241 | ### PreviewText.NumberPicker 242 | 243 | 参考 https://ant.design/components/input-number-cn/ 244 | 245 | ### PreviewText.Placeholder 246 | 247 | | 属性名 | 类型 | 描述 | 默认值 | 248 | | ------ | ------ | ---------- | ------ | 249 | | value | stirng | 缺省占位符 | N/A | 250 | 251 | ### PreviewText.usePlaceholder 252 | 253 | ```ts pure 254 | interface usePlaceholder { 255 | (): string 256 | } 257 | ``` 258 | ```