This is page 15 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/antd/src/form-step/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment } from 'react' 2 | import { define, observable, action, markRaw, model } from '@formily/reactive' 3 | import { Steps } from 'antd' 4 | import cls from 'classnames' 5 | import { StepsProps, StepProps } from 'antd/lib/steps' 6 | import { Form, VoidField } from '@formily/core' 7 | import { 8 | connect, 9 | useField, 10 | observer, 11 | useFieldSchema, 12 | RecursionField, 13 | } from '@formily/react' 14 | import { Schema, SchemaKey } from '@formily/json-schema' 15 | import { usePrefixCls } from '../__builtins__' 16 | 17 | export interface IFormStep { 18 | connect: (steps: SchemaStep[], field: VoidField) => void 19 | current: number 20 | allowNext: boolean 21 | allowBack: boolean 22 | setCurrent(key: number): void 23 | submit: Form['submit'] 24 | next(): void 25 | back(): void 26 | } 27 | 28 | export interface IFormStepProps extends StepsProps { 29 | formStep?: IFormStep 30 | } 31 | 32 | type ComposedFormStep = React.FC<React.PropsWithChildren<IFormStepProps>> & { 33 | StepPane: React.FC<React.PropsWithChildren<StepProps>> 34 | createFormStep: (defaultCurrent?: number) => IFormStep 35 | } 36 | 37 | type SchemaStep = { 38 | name: SchemaKey 39 | props: any 40 | schema: Schema 41 | } 42 | 43 | type FormStepEnv = { 44 | form: Form 45 | field: VoidField 46 | steps: SchemaStep[] 47 | } 48 | 49 | const parseSteps = (schema: Schema) => { 50 | const steps: SchemaStep[] = [] 51 | schema.mapProperties((schema, name) => { 52 | if (schema['x-component']?.indexOf('StepPane') > -1) { 53 | steps.push({ 54 | name, 55 | props: schema['x-component-props'], 56 | schema, 57 | }) 58 | } 59 | }) 60 | return steps 61 | } 62 | 63 | const createFormStep = (defaultCurrent = 0): IFormStep => { 64 | const env: FormStepEnv = define( 65 | { 66 | form: null, 67 | field: null, 68 | steps: [], 69 | }, 70 | { 71 | form: observable.ref, 72 | field: observable.ref, 73 | steps: observable.shallow, 74 | } 75 | ) 76 | 77 | const setDisplay = action.bound((target: number) => { 78 | const currentStep = env.steps[target] 79 | env.steps.forEach(({ name }) => { 80 | env.form.query(`${env.field.address}.${name}`).take((field) => { 81 | if (name === currentStep.name) { 82 | field.setDisplay('visible') 83 | } else { 84 | field.setDisplay('hidden') 85 | } 86 | }) 87 | }) 88 | }) 89 | 90 | const next = action.bound(() => { 91 | if (formStep.allowNext) { 92 | formStep.setCurrent(formStep.current + 1) 93 | } 94 | }) 95 | 96 | const back = action.bound(() => { 97 | if (formStep.allowBack) { 98 | formStep.setCurrent(formStep.current - 1) 99 | } 100 | }) 101 | 102 | const formStep: IFormStep = model({ 103 | connect(steps, field) { 104 | env.steps = steps 105 | env.form = field?.form 106 | env.field = field 107 | }, 108 | current: defaultCurrent, 109 | setCurrent(key: number) { 110 | setDisplay(key) 111 | formStep.current = key 112 | }, 113 | get allowNext() { 114 | return formStep.current < env.steps.length - 1 115 | }, 116 | get allowBack() { 117 | return formStep.current > 0 118 | }, 119 | async next() { 120 | try { 121 | await env.form.validate() 122 | if (env.form.valid) { 123 | next() 124 | } 125 | } catch {} 126 | }, 127 | async back() { 128 | back() 129 | }, 130 | async submit(onSubmit) { 131 | return env.form?.submit?.(onSubmit) 132 | }, 133 | }) 134 | return markRaw(formStep) 135 | } 136 | 137 | export const FormStep = connect( 138 | observer(({ formStep, className, ...props }: IFormStepProps) => { 139 | const field = useField<VoidField>() 140 | const prefixCls = usePrefixCls('formily-step', props) 141 | const schema = useFieldSchema() 142 | const steps = parseSteps(schema) 143 | const current = props.current || formStep?.current || 0 144 | formStep?.connect?.(steps, field) 145 | return ( 146 | <div className={cls(prefixCls, className)}> 147 | <Steps 148 | {...props} 149 | style={{ marginBottom: 10, ...props.style }} 150 | current={current} 151 | > 152 | {steps.map(({ props }, key) => { 153 | return <Steps.Step {...props} key={key} /> 154 | })} 155 | </Steps> 156 | {steps.map(({ name, schema }, key) => { 157 | if (key !== current) return 158 | return <RecursionField key={key} name={name} schema={schema} /> 159 | })} 160 | </div> 161 | ) 162 | }) 163 | ) as unknown as ComposedFormStep 164 | 165 | const StepPane: React.FC<React.PropsWithChildren<StepProps>> = ({ 166 | children, 167 | }) => { 168 | return <Fragment>{children}</Fragment> 169 | } 170 | 171 | FormStep.StepPane = StepPane 172 | FormStep.createFormStep = createFormStep 173 | 174 | export default FormStep 175 | ``` -------------------------------------------------------------------------------- /packages/next/src/form-step/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment } from 'react' 2 | import { define, observable, model, markRaw, action } from '@formily/reactive' 3 | import cls from 'classnames' 4 | import { 5 | StepProps as StepsProps, 6 | ItemProps as StepProps, 7 | } from '@alifd/next/lib/step' 8 | import { Form, VoidField } from '@formily/core' 9 | import { 10 | connect, 11 | useField, 12 | observer, 13 | useFieldSchema, 14 | RecursionField, 15 | } from '@formily/react' 16 | import { Schema, SchemaKey } from '@formily/json-schema' 17 | import { Step as Steps } from '@alifd/next' 18 | import { usePrefixCls } from '../__builtins__' 19 | 20 | export interface IFormStep { 21 | connect: (steps: SchemaStep[], field: VoidField) => void 22 | current: number 23 | allowNext: boolean 24 | allowBack: boolean 25 | setCurrent(key: number): void 26 | submit: Form['submit'] 27 | next(): void 28 | back(): void 29 | } 30 | 31 | export interface IFormStepProps extends StepsProps { 32 | formStep?: IFormStep 33 | } 34 | 35 | type ComposedFormTab = React.FC<React.PropsWithChildren<IFormStepProps>> & { 36 | StepPane?: React.FC<React.PropsWithChildren<StepProps>> 37 | createFormStep?: (defaultCurrent?: number) => IFormStep 38 | } 39 | 40 | type SchemaStep = { 41 | name: SchemaKey 42 | props: any 43 | schema: Schema 44 | } 45 | 46 | type FormStepEnv = { 47 | form: Form 48 | field: VoidField 49 | steps: SchemaStep[] 50 | } 51 | 52 | const parseSteps = (schema: Schema) => { 53 | const steps: SchemaStep[] = [] 54 | schema.mapProperties((schema, name) => { 55 | if (schema['x-component']?.indexOf('StepPane') > -1) { 56 | steps.push({ 57 | name, 58 | props: schema['x-component-props'], 59 | schema, 60 | }) 61 | } 62 | }) 63 | return steps 64 | } 65 | 66 | const createFormStep = (defaultCurrent = 0): IFormStep => { 67 | const env: FormStepEnv = define( 68 | { 69 | form: null, 70 | field: null, 71 | steps: [], 72 | }, 73 | { 74 | form: observable.ref, 75 | field: observable.ref, 76 | steps: observable.shallow, 77 | } 78 | ) 79 | 80 | const setDisplay = action.bound((target: number) => { 81 | const currentStep = env.steps[target] 82 | env.steps.forEach(({ name }) => { 83 | env.form.query(`${env.field.address}.${name}`).take((field) => { 84 | if (name === currentStep.name) { 85 | field.setDisplay('visible') 86 | } else { 87 | field.setDisplay('hidden') 88 | } 89 | }) 90 | }) 91 | }) 92 | 93 | const next = () => { 94 | if (formStep.allowNext) { 95 | formStep.setCurrent(formStep.current + 1) 96 | } 97 | } 98 | 99 | const back = () => { 100 | if (formStep.allowBack) { 101 | formStep.setCurrent(formStep.current - 1) 102 | } 103 | } 104 | 105 | const formStep: IFormStep = model({ 106 | connect(steps, field) { 107 | env.steps = steps 108 | env.form = field?.form 109 | env.field = field 110 | }, 111 | current: defaultCurrent, 112 | setCurrent(key: number) { 113 | setDisplay(key) 114 | formStep.current = key 115 | }, 116 | get allowNext() { 117 | return formStep.current < env.steps.length - 1 118 | }, 119 | get allowBack() { 120 | return formStep.current > 0 121 | }, 122 | async next() { 123 | try { 124 | await env.form.validate() 125 | if (env.form.valid) { 126 | next() 127 | } 128 | } catch {} 129 | }, 130 | async back() { 131 | back() 132 | }, 133 | async submit(onSubmit) { 134 | return env.form?.submit?.(onSubmit) 135 | }, 136 | }) 137 | return markRaw(formStep) 138 | } 139 | 140 | export const FormStep: ComposedFormTab = connect( 141 | observer(({ formStep, className, ...props }: IFormStepProps) => { 142 | const field = useField<VoidField>() 143 | const prefixCls = usePrefixCls('formily-step', props) 144 | const schema = useFieldSchema() 145 | const steps = parseSteps(schema) 146 | const current = props.current || formStep?.current || 0 147 | formStep?.connect?.(steps, field) 148 | return ( 149 | <div className={cls(prefixCls, className)}> 150 | <Steps 151 | {...props} 152 | style={{ marginBottom: 10, ...props.style }} 153 | current={current} 154 | > 155 | {steps.map(({ props }, key) => { 156 | return <Steps.Item {...props} key={key} /> 157 | })} 158 | </Steps> 159 | {steps.map(({ name, schema }, key) => { 160 | if (key !== current) return 161 | return <RecursionField key={key} name={name} schema={schema} /> 162 | })} 163 | </div> 164 | ) 165 | }) 166 | ) 167 | 168 | const StepPane: React.FC<React.PropsWithChildren<StepProps>> = ({ 169 | children, 170 | }) => { 171 | return <Fragment>{children}</Fragment> 172 | } 173 | 174 | FormStep.StepPane = StepPane 175 | FormStep.createFormStep = createFormStep 176 | 177 | export default FormStep 178 | ``` -------------------------------------------------------------------------------- /packages/element/src/form-tab/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Schema, SchemaKey } from '@formily/json-schema' 2 | import { model } from '@formily/reactive' 3 | import { observer } from '@formily/reactive-vue' 4 | import { 5 | Fragment, 6 | h, 7 | RecursionField, 8 | useField, 9 | useFieldSchema, 10 | } from '@formily/vue' 11 | import { Badge, TabPane, Tabs } from 'element-ui' 12 | import { computed, defineComponent, reactive } from 'vue-demi' 13 | import { stylePrefix } from '../__builtins__/configs' 14 | 15 | import type { TabPane as TabPaneProps, Tabs as TabsProps } from 'element-ui' 16 | import { composeExport } from '../__builtins__/shared' 17 | 18 | export interface IFormTab { 19 | activeKey: string 20 | setActiveKey(key: string): void 21 | } 22 | 23 | export interface IFormTabProps extends TabsProps { 24 | formTab?: IFormTab 25 | } 26 | 27 | export interface IFormTabPaneProps extends TabPaneProps { 28 | key: string | number 29 | } 30 | 31 | const useTabs = () => { 32 | const tabsField = useField().value 33 | const schema = useFieldSchema().value 34 | const tabs: { name: SchemaKey; props: any; schema: Schema }[] = reactive([]) 35 | schema.mapProperties((schema, name) => { 36 | const field = tabsField.query(tabsField.address.concat(name)).take() 37 | if (field?.display === 'none' || field?.display === 'hidden') return 38 | if (schema['x-component']?.indexOf('TabPane') > -1) { 39 | tabs.push({ 40 | name, 41 | props: { 42 | name: schema?.['x-component-props']?.name || name, 43 | ...schema?.['x-component-props'], 44 | }, 45 | schema, 46 | }) 47 | } 48 | }) 49 | return tabs 50 | } 51 | 52 | const createFormTab = (defaultActiveKey?: string) => { 53 | const formTab = model({ 54 | activeKey: defaultActiveKey, 55 | setActiveKey(key: string) { 56 | formTab.activeKey = key 57 | }, 58 | }) 59 | return formTab 60 | } 61 | 62 | const FormTabInner = observer( 63 | defineComponent<IFormTabProps>({ 64 | name: 'FFormTab', 65 | props: ['formTab'], 66 | setup(props, { attrs, listeners }) { 67 | const field = useField().value 68 | const formTabRef = computed(() => props.formTab ?? createFormTab()) 69 | 70 | const prefixCls = `${stylePrefix}-form-tab` 71 | 72 | return () => { 73 | const formTab = formTabRef.value 74 | const tabs = useTabs() 75 | const activeKey = props.value || formTab?.activeKey || tabs?.[0]?.name 76 | const badgedTab = (key: SchemaKey, props: any) => { 77 | const errors = field.form.queryFeedbacks({ 78 | type: 'error', 79 | address: `${field.address.concat(key)}.*`, 80 | }) 81 | if (errors.length) { 82 | return () => 83 | h( 84 | Badge, 85 | { 86 | class: [`${prefixCls}-errors-badge`], 87 | props: { 88 | value: errors.length, 89 | }, 90 | }, 91 | { default: () => props.label } 92 | ) 93 | } 94 | return () => props.label 95 | } 96 | 97 | const getTabs = (tabs) => { 98 | return tabs.map(({ props, schema, name }, key) => { 99 | return h( 100 | TabPane, 101 | { 102 | key, 103 | props, 104 | }, 105 | { 106 | default: () => [ 107 | h( 108 | RecursionField, 109 | { 110 | props: { 111 | schema, 112 | name, 113 | }, 114 | }, 115 | {} 116 | ), 117 | ], 118 | label: () => [ 119 | h('div', {}, { default: badgedTab(name, props) }), 120 | ], 121 | } 122 | ) 123 | }) 124 | } 125 | 126 | return h( 127 | Tabs, 128 | { 129 | class: [prefixCls], 130 | style: attrs.style, 131 | props: { 132 | ...attrs, 133 | value: activeKey, 134 | }, 135 | on: { 136 | ...listeners, 137 | input: (key) => { 138 | listeners.input?.(key) 139 | formTab.setActiveKey?.(key) 140 | }, 141 | }, 142 | }, 143 | { 144 | default: () => getTabs(tabs), 145 | } 146 | ) 147 | } 148 | }, 149 | }) 150 | ) 151 | 152 | const FormTabPane = defineComponent<IFormTabPaneProps>({ 153 | name: 'FFormTabPane', 154 | setup(_props, { slots }) { 155 | return () => h(Fragment, {}, slots) 156 | }, 157 | }) 158 | 159 | export const FormTab = composeExport(FormTabInner, { 160 | TabPane: FormTabPane, 161 | createFormTab, 162 | }) 163 | 164 | export default FormTab 165 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-collapse/markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaArrayField 5 | name="string_array" 6 | :maxItems="3" 7 | x-decorator="FormItem" 8 | x-component="ArrayCollapse" 9 | :x-component-props="{ 10 | accordion: true, 11 | defaultOpenPanelCount: 3, 12 | }" 13 | > 14 | <SchemaVoidField 15 | x-component="ArrayCollapse.Item" 16 | :x-component-props="{ 17 | title: '字符串数组', 18 | }" 19 | > 20 | <SchemaVoidField x-component="ArrayCollapse.Index" /> 21 | <SchemaStringField 22 | name="input" 23 | x-decorator="FormItem" 24 | title="Input" 25 | required 26 | x-component="Input" 27 | /> 28 | <SchemaVoidField x-component="ArrayCollapse.Remove" /> 29 | <SchemaVoidField x-component="ArrayCollapse.MoveUp" /> 30 | <SchemaVoidField x-component="ArrayCollapse.MoveDown" /> 31 | </SchemaVoidField> 32 | <SchemaVoidField 33 | x-component="ArrayCollapse.Addition" 34 | title="添加条目" 35 | /> 36 | </SchemaArrayField> 37 | <SchemaArrayField 38 | name="array" 39 | :maxItems="3" 40 | x-decorator="FormItem" 41 | x-component="ArrayCollapse" 42 | > 43 | <SchemaObjectField 44 | x-component="ArrayCollapse.Item" 45 | :x-component-props="{ 46 | title: '对象数组', 47 | }" 48 | > 49 | <SchemaVoidField x-component="ArrayCollapse.Index" /> 50 | <SchemaStringField 51 | name="input" 52 | x-decorator="FormItem" 53 | title="Input" 54 | required 55 | x-component="Input" 56 | /> 57 | <SchemaVoidField x-component="ArrayCollapse.Remove" /> 58 | <SchemaVoidField x-component="ArrayCollapse.MoveUp" /> 59 | <SchemaVoidField x-component="ArrayCollapse.MoveDown" /> 60 | </SchemaObjectField> 61 | <SchemaVoidField 62 | x-component="ArrayCollapse.Addition" 63 | title="添加条目" 64 | /> 65 | </SchemaArrayField> 66 | <SchemaArrayField 67 | name="string_array_unshift" 68 | :maxItems="3" 69 | x-decorator="FormItem" 70 | x-component="ArrayCollapse" 71 | :x-component-props="{ 72 | defaultOpenPanelCount: 8, 73 | }" 74 | > 75 | <SchemaVoidField 76 | x-component="ArrayCollapse.Item" 77 | :x-component-props="{ 78 | title: '字符串数组', 79 | }" 80 | > 81 | <SchemaVoidField x-component="ArrayCollapse.Index" /> 82 | <SchemaStringField 83 | name="input" 84 | x-decorator="FormItem" 85 | title="Input" 86 | required 87 | x-component="Input" 88 | /> 89 | <SchemaVoidField x-component="ArrayCollapse.Remove" /> 90 | <SchemaVoidField x-component="ArrayCollapse.MoveUp" /> 91 | <SchemaVoidField x-component="ArrayCollapse.MoveDown" /> 92 | </SchemaVoidField> 93 | <SchemaVoidField 94 | x-component="ArrayCollapse.Addition" 95 | title="添加条目(unshift)" 96 | :x-component-props="{ 97 | method: 'unshift', 98 | }" 99 | /> 100 | </SchemaArrayField> 101 | </SchemaField> 102 | <FormButtonGroup> 103 | <Button 104 | @click=" 105 | () => { 106 | form.setInitialValues({ 107 | array: Array.from({ length: 10 }).map(() => ({ 108 | input: 'default value', 109 | })), 110 | string_array: Array.from({ length: 10 }).map( 111 | () => 'default value' 112 | ), 113 | string_array_unshift: Array.from({ length: 10 }).map( 114 | () => 'default value' 115 | ), 116 | }) 117 | } 118 | " 119 | > 120 | 加载默认数据 121 | </Button> 122 | <Submit @submit="log">提交</Submit> 123 | </FormButtonGroup> 124 | </FormProvider> 125 | </template> 126 | 127 | <script> 128 | import { createForm } from '@formily/core' 129 | import { FormProvider, createSchemaField } from '@formily/vue' 130 | import { 131 | FormItem, 132 | FormButtonGroup, 133 | Submit, 134 | Input, 135 | ArrayCollapse, 136 | } from '@formily/element' 137 | import { Button } from 'element-ui' 138 | 139 | const SchemaField = createSchemaField({ 140 | components: { 141 | FormItem, 142 | Input, 143 | ArrayCollapse, 144 | }, 145 | }) 146 | 147 | export default { 148 | components: { 149 | FormProvider, 150 | FormButtonGroup, 151 | Button, 152 | Submit, 153 | ...SchemaField, 154 | }, 155 | 156 | data() { 157 | const form = createForm() 158 | 159 | return { 160 | form, 161 | } 162 | }, 163 | methods: { 164 | log(values) { 165 | console.log(values) 166 | }, 167 | }, 168 | } 169 | </script> 170 | 171 | <style lang="scss" scoped></style> 172 | ``` -------------------------------------------------------------------------------- /packages/element/src/checkbox/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { connect, h, mapProps, mapReadPretty } from '@formily/vue' 2 | import type { 3 | Checkbox as _ElCheckboxProps, 4 | CheckboxGroup as ElCheckboxGroupProps, 5 | } from 'element-ui' 6 | import { 7 | Checkbox as ElCheckbox, 8 | CheckboxButton as ElCheckboxButton, 9 | CheckboxGroup as ElCheckboxGroup, 10 | } from 'element-ui' 11 | import { defineComponent, PropType } from 'vue-demi' 12 | import { PreviewText } from '../preview-text' 13 | import { 14 | composeExport, 15 | resolveComponent, 16 | SlotTypes, 17 | transformComponent, 18 | } from '../__builtins__/shared' 19 | 20 | type ElCheckboxProps = Omit<_ElCheckboxProps, 'value'> & { 21 | value: ElCheckboxProps['label'] 22 | } 23 | 24 | export interface CheckboxProps extends ElCheckboxProps { 25 | option: Omit<_ElCheckboxProps, 'value'> & { 26 | value: ElCheckboxProps['label'] 27 | label: SlotTypes 28 | } 29 | } 30 | 31 | const CheckboxOption = defineComponent<CheckboxProps>({ 32 | name: 'Checkbox', 33 | inheritAttrs: false, 34 | props: { 35 | option: { 36 | type: Object, 37 | default: null, 38 | }, 39 | }, 40 | setup(curtomProps, { attrs, slots, listeners }) { 41 | return () => { 42 | const props = attrs as unknown as CheckboxProps 43 | const option = curtomProps?.option 44 | if (option) { 45 | const children = { 46 | default: () => [ 47 | resolveComponent(slots.default ?? option.label, { option }), 48 | ], 49 | } 50 | const newProps = {} as Partial<ElCheckboxProps> 51 | Object.assign(newProps, option) 52 | newProps.label = option.value 53 | delete newProps.value 54 | 55 | return h( 56 | attrs.optionType === 'button' ? ElCheckboxButton : ElCheckbox, 57 | { 58 | attrs: { 59 | ...newProps, 60 | }, 61 | }, 62 | children 63 | ) 64 | } 65 | 66 | return h( 67 | ElCheckbox, 68 | { 69 | attrs: { 70 | ...props, 71 | }, 72 | on: listeners, 73 | }, 74 | slots 75 | ) 76 | } 77 | }, 78 | }) 79 | 80 | export type CheckboxGroupProps = ElCheckboxGroupProps & { 81 | value: any[] 82 | options?: Array<CheckboxProps | string> 83 | optionType: 'default' | 'button' 84 | } 85 | 86 | const TransformElCheckboxGroup = transformComponent(ElCheckboxGroup, { 87 | change: 'input', 88 | uselessChange: 'change' 89 | }) 90 | 91 | const CheckboxGroupOption = defineComponent<CheckboxGroupProps>({ 92 | name: 'CheckboxGroup', 93 | props: { 94 | options: { 95 | type: Array, 96 | default: () => [], 97 | }, 98 | optionType: { 99 | type: String as PropType<CheckboxGroupProps['optionType']>, 100 | default: 'default', 101 | }, 102 | }, 103 | setup(customProps, { attrs, slots, listeners }) { 104 | return () => { 105 | const options = customProps.options || [] 106 | const children = 107 | options.length !== 0 108 | ? { 109 | default: () => 110 | options.map((option) => { 111 | if (typeof option === 'string') { 112 | return h( 113 | Checkbox, 114 | { 115 | props: { 116 | option: { 117 | label: option, 118 | value: option, 119 | }, 120 | }, 121 | attrs: { 122 | optionType: customProps.optionType, 123 | }, 124 | }, 125 | slots?.option 126 | ? { default: () => slots.option({ option }) } 127 | : {} 128 | ) 129 | } else { 130 | return h( 131 | Checkbox, 132 | { 133 | props: { 134 | option, 135 | }, 136 | attrs: { 137 | optionType: customProps.optionType, 138 | }, 139 | }, 140 | slots?.option 141 | ? { default: () => slots.option({ option }) } 142 | : {} 143 | ) 144 | } 145 | }), 146 | } 147 | : slots 148 | return h( 149 | TransformElCheckboxGroup, 150 | { 151 | attrs: { 152 | ...attrs, 153 | }, 154 | on: listeners, 155 | }, 156 | children 157 | ) 158 | } 159 | }, 160 | }) 161 | 162 | const CheckboxGroup = connect( 163 | CheckboxGroupOption, 164 | mapProps({ dataSource: 'options' }), 165 | mapReadPretty(PreviewText.Select, { 166 | multiple: true, 167 | }) 168 | ) 169 | 170 | export const Checkbox = composeExport(connect(CheckboxOption), { 171 | Group: CheckboxGroup, 172 | }) 173 | 174 | export default Checkbox 175 | ``` -------------------------------------------------------------------------------- /packages/react/src/components/RecursionField.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useMemo } from 'react' 2 | import { FormPath, isBool, isFn, isValid } from '@formily/shared' 3 | import { GeneralField } from '@formily/core' 4 | import { Schema } from '@formily/json-schema' 5 | import { SchemaContext } from '../shared' 6 | import { IRecursionFieldProps, ReactFC } from '../types' 7 | import { useField, useExpressionScope } from '../hooks' 8 | import { ObjectField } from './ObjectField' 9 | import { ArrayField } from './ArrayField' 10 | import { Field } from './Field' 11 | import { VoidField } from './VoidField' 12 | import { ExpressionScope } from './ExpressionScope' 13 | import { observable } from '@formily/reactive' 14 | 15 | const useFieldProps = (schema: Schema) => { 16 | const scope = useExpressionScope() 17 | return schema.toFieldProps({ 18 | scope, 19 | }) as any 20 | } 21 | 22 | const useBasePath = (props: IRecursionFieldProps) => { 23 | const parent = useField() 24 | if (props.onlyRenderProperties) { 25 | return props.basePath || parent?.address.concat(props.name) 26 | } 27 | return props.basePath || parent?.address 28 | } 29 | 30 | export const RecursionField: ReactFC<IRecursionFieldProps> = (props) => { 31 | const basePath = useBasePath(props) 32 | const fieldSchema = useMemo(() => new Schema(props.schema), [props.schema]) 33 | const fieldProps = useFieldProps(fieldSchema) 34 | 35 | const renderSlots = (innerSchema, key) => { 36 | const slot = innerSchema['x-slot-node'] 37 | const { target, isRenderProp } = slot 38 | if (isRenderProp) { 39 | const args = observable({ $slotArgs: [] }) 40 | FormPath.setIn(fieldSchema.properties, target, (..._args: any) => { 41 | args.$slotArgs = _args 42 | return ( 43 | <ExpressionScope value={args}> 44 | <RecursionField schema={innerSchema} name={key} /> 45 | </ExpressionScope> 46 | ) 47 | }) 48 | } else { 49 | FormPath.setIn( 50 | fieldSchema.properties, 51 | target, 52 | <RecursionField schema={innerSchema} name={key} /> 53 | ) 54 | } 55 | } 56 | 57 | const renderProperties = (field?: GeneralField) => { 58 | if (props.onlyRenderSelf) return 59 | const properties = Schema.getOrderProperties(fieldSchema) 60 | if (!properties.length) return 61 | return ( 62 | <Fragment> 63 | {properties.map(({ schema: item, key: name }, index) => { 64 | const base = field?.address || basePath 65 | let schema: Schema = item 66 | if (schema['x-slot-node']) { 67 | renderSlots(schema, name) 68 | return null 69 | } 70 | 71 | if (isFn(props.mapProperties)) { 72 | const mapped = props.mapProperties(item, name) 73 | if (mapped) { 74 | schema = mapped 75 | } 76 | } 77 | if (isFn(props.filterProperties)) { 78 | if (props.filterProperties(schema, name) === false) { 79 | return null 80 | } 81 | } 82 | if (isBool(props.propsRecursion) && props.propsRecursion) { 83 | return ( 84 | <RecursionField 85 | propsRecursion={true} 86 | filterProperties={props.filterProperties} 87 | mapProperties={props.mapProperties} 88 | schema={schema} 89 | key={`${index}-${name}`} 90 | name={name} 91 | basePath={base} 92 | /> 93 | ) 94 | } 95 | return ( 96 | <RecursionField 97 | schema={schema} 98 | key={`${index}-${name}`} 99 | name={name} 100 | basePath={base} 101 | /> 102 | ) 103 | })} 104 | </Fragment> 105 | ) 106 | } 107 | 108 | const render = () => { 109 | if (!isValid(props.name)) return renderProperties() 110 | if (fieldSchema.type === 'object') { 111 | if (props.onlyRenderProperties) return renderProperties() 112 | return ( 113 | <ObjectField {...fieldProps} name={props.name} basePath={basePath}> 114 | {renderProperties} 115 | </ObjectField> 116 | ) 117 | } else if (fieldSchema.type === 'array') { 118 | return ( 119 | <ArrayField {...fieldProps} name={props.name} basePath={basePath} /> 120 | ) 121 | } else if (fieldSchema.type === 'void') { 122 | if (props.onlyRenderProperties) return renderProperties() 123 | return ( 124 | <VoidField {...fieldProps} name={props.name} basePath={basePath}> 125 | {renderProperties} 126 | </VoidField> 127 | ) 128 | } 129 | return <Field {...fieldProps} name={props.name} basePath={basePath} /> 130 | } 131 | 132 | if (!fieldSchema) return <Fragment /> 133 | 134 | return ( 135 | <SchemaContext.Provider value={fieldSchema}> 136 | {render()} 137 | </SchemaContext.Provider> 138 | ) 139 | } 140 | ``` -------------------------------------------------------------------------------- /packages/antd/src/array-cards/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { Card, Empty } from 'antd' 3 | import { CardProps } from 'antd/lib/card' 4 | import { ArrayField } from '@formily/core' 5 | import { 6 | useField, 7 | observer, 8 | useFieldSchema, 9 | RecursionField, 10 | } from '@formily/react' 11 | import cls from 'classnames' 12 | import { ISchema } from '@formily/json-schema' 13 | import { usePrefixCls } from '../__builtins__' 14 | import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base' 15 | 16 | type ComposedArrayCards = React.FC< 17 | React.PropsWithChildren<CardProps & IArrayBaseProps> 18 | > & 19 | ArrayBaseMixins 20 | 21 | const isAdditionComponent = (schema: ISchema) => { 22 | return schema['x-component']?.indexOf('Addition') > -1 23 | } 24 | 25 | const isIndexComponent = (schema: ISchema) => { 26 | return schema['x-component']?.indexOf?.('Index') > -1 27 | } 28 | 29 | const isRemoveComponent = (schema: ISchema) => { 30 | return schema['x-component']?.indexOf?.('Remove') > -1 31 | } 32 | 33 | const isCopyComponent = (schema: ISchema) => { 34 | return schema['x-component']?.indexOf?.('Copy') > -1 35 | } 36 | 37 | const isMoveUpComponent = (schema: ISchema) => { 38 | return schema['x-component']?.indexOf?.('MoveUp') > -1 39 | } 40 | 41 | const isMoveDownComponent = (schema: ISchema) => { 42 | return schema['x-component']?.indexOf?.('MoveDown') > -1 43 | } 44 | 45 | const isOperationComponent = (schema: ISchema) => { 46 | return ( 47 | isAdditionComponent(schema) || 48 | isRemoveComponent(schema) || 49 | isCopyComponent(schema) || 50 | isMoveDownComponent(schema) || 51 | isMoveUpComponent(schema) 52 | ) 53 | } 54 | 55 | export const ArrayCards: ComposedArrayCards = observer((props) => { 56 | const field = useField<ArrayField>() 57 | const schema = useFieldSchema() 58 | const dataSource = Array.isArray(field.value) ? field.value : [] 59 | const prefixCls = usePrefixCls('formily-array-cards', props) 60 | const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props 61 | 62 | if (!schema) throw new Error('can not found schema object') 63 | 64 | const renderItems = () => { 65 | return dataSource?.map((item, index) => { 66 | const items = Array.isArray(schema.items) 67 | ? schema.items[index] || schema.items[0] 68 | : schema.items 69 | const title = ( 70 | <span> 71 | <RecursionField 72 | schema={items} 73 | name={index} 74 | filterProperties={(schema) => { 75 | if (!isIndexComponent(schema)) return false 76 | return true 77 | }} 78 | onlyRenderProperties 79 | /> 80 | {props.title || field.title} 81 | </span> 82 | ) 83 | const extra = ( 84 | <span> 85 | <RecursionField 86 | schema={items} 87 | name={index} 88 | filterProperties={(schema) => { 89 | if (!isOperationComponent(schema)) return false 90 | return true 91 | }} 92 | onlyRenderProperties 93 | /> 94 | {props.extra} 95 | </span> 96 | ) 97 | const content = ( 98 | <RecursionField 99 | schema={items} 100 | name={index} 101 | filterProperties={(schema) => { 102 | if (isIndexComponent(schema)) return false 103 | if (isOperationComponent(schema)) return false 104 | return true 105 | }} 106 | /> 107 | ) 108 | return ( 109 | <ArrayBase.Item 110 | key={index} 111 | index={index} 112 | record={() => field.value?.[index]} 113 | > 114 | <Card 115 | {...props} 116 | onChange={() => {}} 117 | className={cls(`${prefixCls}-item`, props.className)} 118 | title={title} 119 | extra={extra} 120 | > 121 | {content} 122 | </Card> 123 | </ArrayBase.Item> 124 | ) 125 | }) 126 | } 127 | 128 | const renderAddition = () => { 129 | return schema.reduceProperties((addition, schema, key) => { 130 | if (isAdditionComponent(schema)) { 131 | return <RecursionField schema={schema} name={key} /> 132 | } 133 | return addition 134 | }, null) 135 | } 136 | 137 | const renderEmpty = () => { 138 | if (dataSource?.length) return 139 | return ( 140 | <Card 141 | {...props} 142 | onChange={() => {}} 143 | className={cls(`${prefixCls}-item`, props.className)} 144 | title={props.title || field.title} 145 | > 146 | <Empty /> 147 | </Card> 148 | ) 149 | } 150 | 151 | return ( 152 | <ArrayBase 153 | onAdd={onAdd} 154 | onCopy={onCopy} 155 | onRemove={onRemove} 156 | onMoveUp={onMoveUp} 157 | onMoveDown={onMoveDown} 158 | > 159 | {renderEmpty()} 160 | {renderItems()} 161 | {renderAddition()} 162 | </ArrayBase> 163 | ) 164 | }) 165 | 166 | ArrayCards.displayName = 'ArrayCards' 167 | 168 | ArrayBase.mixin(ArrayCards) 169 | 170 | export default ArrayCards 171 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/define.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { define, model, observable, autorun } from '..' 2 | import { observe } from '../observe' 3 | import { FormPath } from '@formily/shared' 4 | import { batch } from '../batch' 5 | 6 | describe('makeObservable', () => { 7 | test('observable annotation', () => { 8 | const target: any = { 9 | aa: {}, 10 | } 11 | define(target, { 12 | aa: observable, 13 | }) 14 | const handler = jest.fn() 15 | const handler1 = jest.fn() 16 | const handler2 = jest.fn() 17 | autorun(() => { 18 | handler(FormPath.getIn(target, 'aa.bb.cc')) 19 | }) 20 | observe(target, handler1) 21 | observe(target.aa, handler2) 22 | target.aa.bb = { cc: { dd: { ee: 123 } } } 23 | target.aa = { hh: 123 } 24 | expect(handler).toBeCalledTimes(3) 25 | expect(handler).nthCalledWith(1, undefined) 26 | expect(handler).nthCalledWith(2, { dd: { ee: 123 } }) 27 | expect(handler).nthCalledWith(3, undefined) 28 | expect(handler1).toBeCalledTimes(2) 29 | expect(handler2).toBeCalledTimes(2) 30 | }) 31 | test('shallow annotation', () => { 32 | const target: any = { 33 | aa: {}, 34 | } 35 | define(target, { 36 | aa: observable.shallow, 37 | }) 38 | const handler = jest.fn() 39 | const handler1 = jest.fn() 40 | const handler2 = jest.fn() 41 | autorun(() => { 42 | handler(FormPath.getIn(target, 'aa.bb.cc')) 43 | }) 44 | observe(target, handler1) 45 | observe(target.aa, handler2) 46 | target.aa.bb = { cc: { dd: { ee: 123 } } } 47 | target.aa.bb.cc.kk = 333 48 | target.aa = { hh: 123 } 49 | expect(handler).toBeCalledTimes(3) 50 | expect(handler).nthCalledWith(1, undefined) 51 | expect(handler).nthCalledWith(2, { dd: { ee: 123 }, kk: 333 }) 52 | expect(handler).nthCalledWith(3, undefined) 53 | expect(handler1).toBeCalledTimes(2) 54 | expect(handler2).toBeCalledTimes(2) 55 | }) 56 | test('box annotation', () => { 57 | const target: any = {} 58 | define(target, { 59 | aa: observable.box, 60 | }) 61 | const handler = jest.fn() 62 | const handler1 = jest.fn() 63 | const handler2 = jest.fn() 64 | autorun(() => { 65 | handler(target.aa.get()) 66 | }) 67 | observe(target, handler1) 68 | observe(target.aa, handler2) 69 | 70 | expect(handler).lastCalledWith(undefined) 71 | target.aa.set(123) 72 | expect(handler).toBeCalledTimes(2) 73 | expect(handler).lastCalledWith(123) 74 | expect(handler1).toBeCalledTimes(1) 75 | expect(handler2).toBeCalledTimes(1) 76 | }) 77 | test('ref annotation', () => { 78 | const target: any = {} 79 | define(target, { 80 | aa: observable.ref, 81 | }) 82 | const handler = jest.fn() 83 | const handler1 = jest.fn() 84 | autorun(() => { 85 | handler(target.aa) 86 | }) 87 | observe(target, handler1) 88 | expect(handler).lastCalledWith(undefined) 89 | target.aa = 123 90 | expect(handler).toBeCalledTimes(2) 91 | expect(handler).lastCalledWith(123) 92 | expect(handler1).toBeCalledTimes(1) 93 | }) 94 | test('action annotation', () => { 95 | const target = { 96 | aa: { 97 | bb: null, 98 | cc: null, 99 | }, 100 | setData() { 101 | target.aa.bb = 123 102 | target.aa.cc = 312 103 | }, 104 | } 105 | define(target, { 106 | aa: observable, 107 | setData: batch, 108 | }) 109 | const handler = jest.fn() 110 | autorun(() => { 111 | handler([target.aa.bb, target.aa.cc]) 112 | }) 113 | expect(handler).toBeCalledTimes(1) 114 | target.setData() 115 | expect(handler).toBeCalledTimes(2) 116 | }) 117 | test('computed annotation', () => { 118 | const handler = jest.fn() 119 | const target = { 120 | aa: 11, 121 | bb: 22, 122 | get cc() { 123 | handler() 124 | return this.aa + this.bb 125 | }, 126 | } 127 | define(target, { 128 | aa: observable, 129 | bb: observable, 130 | cc: observable.computed, 131 | }) 132 | autorun(() => { 133 | target.cc 134 | }) 135 | expect(handler).toBeCalledTimes(1) 136 | expect(target.cc).toEqual(33) 137 | target.aa = 22 138 | expect(handler).toBeCalledTimes(2) 139 | expect(target.cc).toEqual(44) 140 | }) 141 | test('unexpect target', () => { 142 | const testFn = jest.fn() 143 | const testArr = [] 144 | const obs1 = define(4 as any, { 145 | value: observable.computed, 146 | }) 147 | const obs2 = define('123' as any, { 148 | value: observable.computed, 149 | }) 150 | const obs3 = define(testFn as any, { 151 | value: observable.computed, 152 | }) 153 | const obs4 = define(testArr as any, { 154 | value: observable.computed, 155 | }) 156 | expect(obs1).toBe(4) 157 | expect(obs2).toBe('123') 158 | expect(obs3).toBe(testFn) 159 | expect(obs4).toBe(testArr) 160 | }) 161 | }) 162 | 163 | test('define model', () => { 164 | const obs = model({ 165 | aa: 1, 166 | action() { 167 | this.aa++ 168 | }, 169 | }) 170 | const { action } = obs 171 | action() 172 | expect(obs.aa).toEqual(2) 173 | }) 174 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Reset.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Reset 2 | 3 | > 重置按钮 4 | 5 | ## 普通重置 6 | 7 | > 有默认值的控件无法被清空 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd' 12 | import { createForm } from '@formily/core' 13 | import { FormProvider, createSchemaField } from '@formily/react' 14 | 15 | const SchemaField = createSchemaField({ 16 | components: { 17 | Input, 18 | FormItem, 19 | }, 20 | }) 21 | 22 | const form = createForm() 23 | 24 | export default () => ( 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.String 28 | name="input" 29 | title="输入框" 30 | required 31 | x-decorator="FormItem" 32 | x-component="Input" 33 | /> 34 | <SchemaField.String 35 | name="input2" 36 | title="输入框" 37 | default="123" 38 | required 39 | x-decorator="FormItem" 40 | x-component="Input" 41 | /> 42 | </SchemaField> 43 | <FormButtonGroup> 44 | <Reset>重置</Reset> 45 | </FormButtonGroup> 46 | </FormProvider> 47 | ) 48 | ``` 49 | 50 | ## 强制清空重置 51 | 52 | ```tsx 53 | import React from 'react' 54 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd' 55 | import { createForm } from '@formily/core' 56 | import { FormProvider, createSchemaField } from '@formily/react' 57 | 58 | const SchemaField = createSchemaField({ 59 | components: { 60 | Input, 61 | FormItem, 62 | }, 63 | }) 64 | 65 | const form = createForm() 66 | 67 | export default () => ( 68 | <FormProvider form={form}> 69 | <SchemaField> 70 | <SchemaField.String 71 | name="input" 72 | title="输入框" 73 | required 74 | x-decorator="FormItem" 75 | x-component="Input" 76 | /> 77 | <SchemaField.String 78 | name="input2" 79 | title="输入框" 80 | default="123" 81 | required 82 | x-decorator="FormItem" 83 | x-component="Input" 84 | /> 85 | </SchemaField> 86 | <FormButtonGroup> 87 | <Reset forceClear>重置</Reset> 88 | </FormButtonGroup> 89 | </FormProvider> 90 | ) 91 | ``` 92 | 93 | ## 重置并校验 94 | 95 | ```tsx 96 | import React from 'react' 97 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd' 98 | import { createForm } from '@formily/core' 99 | import { FormProvider, createSchemaField } from '@formily/react' 100 | 101 | const SchemaField = createSchemaField({ 102 | components: { 103 | Input, 104 | FormItem, 105 | }, 106 | }) 107 | 108 | const form = createForm() 109 | 110 | export default () => ( 111 | <FormProvider form={form}> 112 | <SchemaField> 113 | <SchemaField.String 114 | name="input" 115 | title="输入框" 116 | required 117 | x-decorator="FormItem" 118 | x-component="Input" 119 | /> 120 | <SchemaField.String 121 | name="input2" 122 | title="输入框" 123 | default="123" 124 | required 125 | x-decorator="FormItem" 126 | x-component="Input" 127 | /> 128 | </SchemaField> 129 | <FormButtonGroup> 130 | <Reset validate>重置</Reset> 131 | </FormButtonGroup> 132 | </FormProvider> 133 | ) 134 | ``` 135 | 136 | ## 强制清空重置并校验 137 | 138 | ```tsx 139 | import React from 'react' 140 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd' 141 | import { createForm } from '@formily/core' 142 | import { FormProvider, createSchemaField } from '@formily/react' 143 | 144 | const SchemaField = createSchemaField({ 145 | components: { 146 | Input, 147 | FormItem, 148 | }, 149 | }) 150 | 151 | const form = createForm() 152 | 153 | export default () => ( 154 | <FormProvider form={form}> 155 | <SchemaField> 156 | <SchemaField.String 157 | name="input" 158 | title="输入框" 159 | required 160 | x-decorator="FormItem" 161 | x-component="Input" 162 | /> 163 | <SchemaField.String 164 | name="input2" 165 | title="输入框" 166 | default="123" 167 | required 168 | x-decorator="FormItem" 169 | x-component="Input" 170 | /> 171 | </SchemaField> 172 | <FormButtonGroup> 173 | <Reset forceClear validate> 174 | 重置 175 | </Reset> 176 | </FormButtonGroup> 177 | </FormProvider> 178 | ) 179 | ``` 180 | 181 | ## API 182 | 183 | ### Reset 184 | 185 | 其余 API 参考 https://ant.design/components/button-cn/ 186 | 187 | | 属性名 | 类型 | 描述 | 默认值 | 188 | | ---------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------- | ------ | 189 | | onClick | `(event: MouseEvent) => void \| boolean` | 点击事件,如果返回 false 可以阻塞重置 | - | 190 | | onResetValidateSuccess | (payload: any) => void | 重置校验成功事件 | - | 191 | | onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/zh-CN/api/models/form#iformfeedback)[]) => void | 重置校验失败事件 | - | 192 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Reset.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Reset 2 | 3 | > 重置按钮 4 | 5 | ## 普通重置 6 | 7 | > 有默认值的控件无法被清空 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next' 12 | import { createForm } from '@formily/core' 13 | import { FormProvider, createSchemaField } from '@formily/react' 14 | 15 | const SchemaField = createSchemaField({ 16 | components: { 17 | Input, 18 | FormItem, 19 | }, 20 | }) 21 | 22 | const form = createForm() 23 | 24 | export default () => ( 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.String 28 | name="input" 29 | title="输入框" 30 | required 31 | x-decorator="FormItem" 32 | x-component="Input" 33 | /> 34 | <SchemaField.String 35 | name="input2" 36 | title="输入框" 37 | default="123" 38 | required 39 | x-decorator="FormItem" 40 | x-component="Input" 41 | /> 42 | </SchemaField> 43 | <FormButtonGroup> 44 | <Reset>重置</Reset> 45 | </FormButtonGroup> 46 | </FormProvider> 47 | ) 48 | ``` 49 | 50 | ## 强制清空重置 51 | 52 | ```tsx 53 | import React from 'react' 54 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next' 55 | import { createForm } from '@formily/core' 56 | import { FormProvider, createSchemaField } from '@formily/react' 57 | 58 | const SchemaField = createSchemaField({ 59 | components: { 60 | Input, 61 | FormItem, 62 | }, 63 | }) 64 | 65 | const form = createForm() 66 | 67 | export default () => ( 68 | <FormProvider form={form}> 69 | <SchemaField> 70 | <SchemaField.String 71 | name="input" 72 | title="输入框" 73 | required 74 | x-decorator="FormItem" 75 | x-component="Input" 76 | /> 77 | <SchemaField.String 78 | name="input2" 79 | title="输入框" 80 | default="123" 81 | required 82 | x-decorator="FormItem" 83 | x-component="Input" 84 | /> 85 | </SchemaField> 86 | <FormButtonGroup> 87 | <Reset forceClear>重置</Reset> 88 | </FormButtonGroup> 89 | </FormProvider> 90 | ) 91 | ``` 92 | 93 | ## 重置并校验 94 | 95 | ```tsx 96 | import React from 'react' 97 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next' 98 | import { createForm } from '@formily/core' 99 | import { FormProvider, createSchemaField } from '@formily/react' 100 | 101 | const SchemaField = createSchemaField({ 102 | components: { 103 | Input, 104 | FormItem, 105 | }, 106 | }) 107 | 108 | const form = createForm() 109 | 110 | export default () => ( 111 | <FormProvider form={form}> 112 | <SchemaField> 113 | <SchemaField.String 114 | name="input" 115 | title="输入框" 116 | required 117 | x-decorator="FormItem" 118 | x-component="Input" 119 | /> 120 | <SchemaField.String 121 | name="input2" 122 | title="输入框" 123 | default="123" 124 | required 125 | x-decorator="FormItem" 126 | x-component="Input" 127 | /> 128 | </SchemaField> 129 | <FormButtonGroup> 130 | <Reset validate>重置</Reset> 131 | </FormButtonGroup> 132 | </FormProvider> 133 | ) 134 | ``` 135 | 136 | ## 强制清空重置并校验 137 | 138 | ```tsx 139 | import React from 'react' 140 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next' 141 | import { createForm } from '@formily/core' 142 | import { FormProvider, createSchemaField } from '@formily/react' 143 | 144 | const SchemaField = createSchemaField({ 145 | components: { 146 | Input, 147 | FormItem, 148 | }, 149 | }) 150 | 151 | const form = createForm() 152 | 153 | export default () => ( 154 | <FormProvider form={form}> 155 | <SchemaField> 156 | <SchemaField.String 157 | name="input" 158 | title="输入框" 159 | required 160 | x-decorator="FormItem" 161 | x-component="Input" 162 | /> 163 | <SchemaField.String 164 | name="input2" 165 | title="输入框" 166 | default="123" 167 | required 168 | x-decorator="FormItem" 169 | x-component="Input" 170 | /> 171 | </SchemaField> 172 | <FormButtonGroup> 173 | <Reset forceClear validate> 174 | 重置 175 | </Reset> 176 | </FormButtonGroup> 177 | </FormProvider> 178 | ) 179 | ``` 180 | 181 | ## API 182 | 183 | ### Reset 184 | 185 | 其余 API 参考 https://fusion.design/pc/component/basic/button 186 | 187 | | 属性名 | 类型 | 描述 | 默认值 | 188 | | ---------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------- | ------ | 189 | | onClick | `(event: MouseEvent) => void \| boolean` | 点击事件,如果返回 false 可以阻塞重置 | - | 190 | | onResetValidateSuccess | (payload: any) => void | 重置校验成功事件 | - | 191 | | onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/zh-CN/api/models/form#iformfeedback)[]) => void | 重置校验失败事件 | - | 192 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Form.md: -------------------------------------------------------------------------------- ```markdown 1 | # Form 2 | 3 | > The combination of FormProvider + FormLayout + form tags can help us quickly implement forms that are submitted with carriage return and can be laid out in batches 4 | 5 | ## Use Cases 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Input, 11 | Select, 12 | Form, 13 | FormItem, 14 | FormGrid, 15 | FormButtonGroup, 16 | Submit, 17 | } from '@formily/next' 18 | import { createForm } from '@formily/core' 19 | import { Field } from '@formily/react' 20 | 21 | const form = createForm() 22 | 23 | export default () => ( 24 | <Form 25 | form={form} 26 | layout="vertical" 27 | feedbackLayout="terse" 28 | onAutoSubmit={console.log} 29 | onAutoSubmitFailed={console.log} 30 | > 31 | <FormGrid maxColumns={4}> 32 | <Field 33 | name="aa" 34 | title="select box" 35 | decorator={[FormItem]} 36 | component={[Select]} 37 | dataSource={[ 38 | { 39 | label: 'Option 1', 40 | value: 1, 41 | }, 42 | { 43 | label: 'Option 2', 44 | value: 2, 45 | }, 46 | ]} 47 | /> 48 | <Field 49 | name="bb" 50 | title="input box" 51 | required 52 | decorator={[FormItem]} 53 | component={[Input]} 54 | /> 55 | <Field 56 | name="cc" 57 | title="input box" 58 | decorator={[FormItem]} 59 | component={[Input]} 60 | /> 61 | <Field 62 | name="dd" 63 | title="input box" 64 | decorator={[FormItem]} 65 | component={[Input]} 66 | /> 67 | <Field 68 | name="ee" 69 | title="input box" 70 | decorator={[FormItem]} 71 | component={[Input]} 72 | /> 73 | <FormButtonGroup.FormItem> 74 | <Submit>Query</Submit> 75 | </FormButtonGroup.FormItem> 76 | </FormGrid> 77 | </Form> 78 | ) 79 | ``` 80 | 81 | ## Fusion Multilingual 82 | 83 | ```tsx 84 | import React from 'react' 85 | import { Input, Form, FormItem, FormButtonGroup, Submit } from '@formily/next' 86 | import { createForm } from '@formily/core' 87 | import { Field } from '@formily/react' 88 | import { ConfigProvider } from '@alifd/next' 89 | import enUS from '@alifd/next/lib/locale/en-us' 90 | 91 | const form = createForm() 92 | 93 | export default () => ( 94 | <ConfigProvider locale={enUS}> 95 | <Form 96 | form={form} 97 | layout="vertical" 98 | feedbackLayout="terse" 99 | onAutoSubmit={console.log} 100 | > 101 | <Field 102 | name="bb" 103 | title="User Name" 104 | required 105 | decorator={[FormItem]} 106 | component={[Input]} 107 | /> 108 | 109 | <FormButtonGroup.FormItem> 110 | <Submit>Submit</Submit> 111 | </FormButtonGroup.FormItem> 112 | </Form> 113 | </ConfigProvider> 114 | ) 115 | ``` 116 | 117 | <Alert style="margin-top:20px"> 118 | Note: To realize the carriage return submission, we cannot pass the onSubmit event to it when using the Submit component, otherwise the carriage return submission will become invalid. The purpose of this is to prevent users from writing onSubmit event listeners in multiple places at the same time, and processing logic If they are inconsistent, it is difficult to locate the problem when submitting. 119 | </Alert> 120 | 121 | ## API 122 | 123 | For layout-related API properties, we can refer to [FormLayout](./form-layout), and the rest are the unique API properties of the Form component 124 | 125 | | Property name | Type | Description | Default value | 126 | | ---------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ------------- | 127 | | form | [Form](https://core.formilyjs.org/api/models/form) | Form example | - | 128 | | component | string | Rendering component, can be specified as custom component rendering | `form` | 129 | | previewTextPlaceholder | ReactNode | Preview State Placeholder | `N/A` | 130 | | onAutoSubmit | `(values:any)=>any` | Carriage return submit event callback | - | 131 | | onAutoSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Carriage return submission verification failure event callback | - | 132 | ``` -------------------------------------------------------------------------------- /packages/validator/src/rules.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | isEmpty, 3 | isValid, 4 | stringLength, 5 | isStr, 6 | isArr, 7 | isFn, 8 | toArr, 9 | isBool, 10 | isNum, 11 | isEqual, 12 | each, 13 | } from '@formily/shared' 14 | import { getValidateFormats } from './registry' 15 | import { IRegistryRules } from './types' 16 | 17 | const isValidateEmpty = (value: any) => { 18 | if (isArr(value)) { 19 | for (let i = 0; i < value.length; i++) { 20 | if (isValid(value[i])) return false 21 | } 22 | return true 23 | } else { 24 | //compat to draft-js 25 | if (value?.getCurrentContent) { 26 | /* istanbul ignore next */ 27 | return !value.getCurrentContent()?.hasText() 28 | } 29 | return isEmpty(value) 30 | } 31 | } 32 | 33 | const getLength = (value: any) => 34 | isStr(value) ? stringLength(value) : value ? value.length : 0 35 | 36 | const extendSameRules = ( 37 | rules: IRegistryRules, 38 | names: Record<string, string> 39 | ) => { 40 | each(names, (realName, name) => { 41 | rules[name] = (value, rule, ...args) => 42 | rules[realName](value, { ...rule, [realName]: rule[name] }, ...args) 43 | }) 44 | } 45 | 46 | const RULES: IRegistryRules = { 47 | format(value, rule) { 48 | if (isValidateEmpty(value)) return '' 49 | if (rule.format) { 50 | const format = getValidateFormats(rule.format) 51 | if (format) { 52 | return !new RegExp(format).test(value) ? rule.message : '' 53 | } 54 | } 55 | return '' 56 | }, 57 | required(value, rule) { 58 | if (rule.required !== true) return '' 59 | return isValidateEmpty(value) ? rule.message : '' 60 | }, 61 | max(value, rule) { 62 | if (isValidateEmpty(value)) return '' 63 | const length = isNum(value) ? value : getLength(value) 64 | const max = Number(rule.max) 65 | return length > max ? rule.message : '' 66 | }, 67 | min(value, rule) { 68 | if (isValidateEmpty(value)) return '' 69 | const length = isNum(value) ? value : getLength(value) 70 | const min = Number(rule.min) 71 | return length < min ? rule.message : '' 72 | }, 73 | exclusiveMaximum(value, rule) { 74 | if (isValidateEmpty(value)) return '' 75 | const length = isNum(value) ? value : getLength(value) 76 | const max = Number(rule.exclusiveMaximum) 77 | return length >= max ? rule.message : '' 78 | }, 79 | exclusiveMinimum(value, rule) { 80 | if (isValidateEmpty(value)) return '' 81 | const length = isNum(value) ? value : getLength(value) 82 | const min = Number(rule.exclusiveMinimum) 83 | return length <= min ? rule.message : '' 84 | }, 85 | len(value, rule) { 86 | if (isValidateEmpty(value)) return '' 87 | const length = getLength(value) 88 | const len = Number(rule.len) 89 | return length !== len ? rule.message : '' 90 | }, 91 | 92 | pattern(value, rule) { 93 | if (isValidateEmpty(value)) return '' 94 | return !new RegExp(rule.pattern).test(value) ? rule.message : '' 95 | }, 96 | async validator(value, rule, context, format) { 97 | if (isFn(rule.validator)) { 98 | const response = await Promise.resolve( 99 | rule.validator(value, rule, context, format) 100 | ) 101 | if (isBool(response)) { 102 | return !response ? rule.message : '' 103 | } else { 104 | return response 105 | } 106 | } 107 | /* istanbul ignore next */ 108 | throw new Error("The rule's validator property must be a function.") 109 | }, 110 | whitespace(value, rule) { 111 | if (isValidateEmpty(value)) return '' 112 | if (rule.whitespace) { 113 | return /^\s+$/.test(value) ? rule.message : '' 114 | } 115 | }, 116 | enum(value, rule) { 117 | if (isValidateEmpty(value)) return '' 118 | const enums = toArr(rule.enum) 119 | return enums.indexOf(value) === -1 ? rule.message : '' 120 | }, 121 | const(value, rule) { 122 | if (isValidateEmpty(value)) return '' 123 | return rule.const !== value ? rule.message : '' 124 | }, 125 | multipleOf(value, rule) { 126 | if (isValidateEmpty(value)) return '' 127 | return Number(value) % Number(rule.multipleOf) !== 0 ? rule.message : '' 128 | }, 129 | uniqueItems(value, rule) { 130 | if (isValidateEmpty(value)) return '' 131 | value = toArr(value) 132 | return value.some((item: any, index: number) => { 133 | for (let i = 0; i < value.length; i++) { 134 | if (i !== index && !isEqual(value[i], item)) { 135 | return false 136 | } 137 | } 138 | return true 139 | }) 140 | ? '' 141 | : rule.message 142 | }, 143 | maxProperties(value, rule) { 144 | if (isValidateEmpty(value)) return '' 145 | return Object.keys(value || {}).length <= Number(rule.maxProperties) 146 | ? '' 147 | : rule.message 148 | }, 149 | minProperties(value, rule) { 150 | if (isValidateEmpty(value)) return '' 151 | return Object.keys(value || {}).length >= Number(rule.minProperties) 152 | ? '' 153 | : rule.message 154 | }, 155 | } 156 | 157 | extendSameRules(RULES, { 158 | maximum: 'max', 159 | minimum: 'min', 160 | maxItems: 'max', 161 | minItems: 'min', 162 | maxLength: 'max', 163 | minLength: 'min', 164 | }) 165 | 166 | export default RULES 167 | ``` -------------------------------------------------------------------------------- /packages/core/src/effects/onFieldEffects.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { FormPath, isFn, toArr } from '@formily/shared' 2 | import { autorun, reaction, batch } from '@formily/reactive' 3 | import { Form } from '../models' 4 | import { 5 | LifeCycleTypes, 6 | FormPathPattern, 7 | GeneralField, 8 | DataField, 9 | IFieldState, 10 | } from '../types' 11 | import { createEffectHook, useEffectForm } from '../shared/effective' 12 | 13 | function createFieldEffect<Result extends GeneralField = GeneralField>( 14 | type: LifeCycleTypes 15 | ) { 16 | return createEffectHook( 17 | type, 18 | (field: Result, form: Form) => 19 | ( 20 | pattern: FormPathPattern, 21 | callback: (field: Result, form: Form) => void 22 | ) => { 23 | if ( 24 | FormPath.parse(pattern).matchAliasGroup(field.address, field.path) 25 | ) { 26 | batch(() => { 27 | callback(field, form) 28 | }) 29 | } 30 | } 31 | ) 32 | } 33 | const _onFieldInit = createFieldEffect(LifeCycleTypes.ON_FIELD_INIT) 34 | export const onFieldMount = createFieldEffect(LifeCycleTypes.ON_FIELD_MOUNT) 35 | export const onFieldUnmount = createFieldEffect(LifeCycleTypes.ON_FIELD_UNMOUNT) 36 | export const onFieldValueChange = createFieldEffect<DataField>( 37 | LifeCycleTypes.ON_FIELD_VALUE_CHANGE 38 | ) 39 | export const onFieldInitialValueChange = createFieldEffect<DataField>( 40 | LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE 41 | ) 42 | export const onFieldInputValueChange = createFieldEffect<DataField>( 43 | LifeCycleTypes.ON_FIELD_INPUT_VALUE_CHANGE 44 | ) 45 | export const onFieldValidateStart = createFieldEffect<DataField>( 46 | LifeCycleTypes.ON_FIELD_VALIDATE_START 47 | ) 48 | export const onFieldValidateEnd = createFieldEffect<DataField>( 49 | LifeCycleTypes.ON_FIELD_VALIDATE_END 50 | ) 51 | export const onFieldValidating = createFieldEffect<DataField>( 52 | LifeCycleTypes.ON_FIELD_VALIDATING 53 | ) 54 | export const onFieldValidateFailed = createFieldEffect<DataField>( 55 | LifeCycleTypes.ON_FIELD_VALIDATE_FAILED 56 | ) 57 | export const onFieldValidateSuccess = createFieldEffect<DataField>( 58 | LifeCycleTypes.ON_FIELD_VALIDATE_SUCCESS 59 | ) 60 | export const onFieldSubmit = createFieldEffect<DataField>( 61 | LifeCycleTypes.ON_FIELD_SUBMIT 62 | ) 63 | export const onFieldSubmitStart = createFieldEffect<DataField>( 64 | LifeCycleTypes.ON_FIELD_SUBMIT_START 65 | ) 66 | export const onFieldSubmitEnd = createFieldEffect<DataField>( 67 | LifeCycleTypes.ON_FIELD_SUBMIT_END 68 | ) 69 | export const onFieldSubmitValidateStart = createFieldEffect<DataField>( 70 | LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_START 71 | ) 72 | export const onFieldSubmitValidateEnd = createFieldEffect<DataField>( 73 | LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_END 74 | ) 75 | export const onFieldSubmitSuccess = createFieldEffect<DataField>( 76 | LifeCycleTypes.ON_FIELD_SUBMIT_SUCCESS 77 | ) 78 | export const onFieldSubmitFailed = createFieldEffect<DataField>( 79 | LifeCycleTypes.ON_FIELD_SUBMIT_FAILED 80 | ) 81 | export const onFieldSubmitValidateSuccess = createFieldEffect<DataField>( 82 | LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_SUCCESS 83 | ) 84 | export const onFieldSubmitValidateFailed = createFieldEffect<DataField>( 85 | LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_FAILED 86 | ) 87 | export const onFieldReset = createFieldEffect<DataField>( 88 | LifeCycleTypes.ON_FIELD_RESET 89 | ) 90 | export const onFieldLoading = createFieldEffect<DataField>( 91 | LifeCycleTypes.ON_FIELD_LOADING 92 | ) 93 | 94 | export function onFieldInit( 95 | pattern: FormPathPattern, 96 | callback?: (field: GeneralField, form: Form) => void 97 | ) { 98 | const form = useEffectForm() 99 | const count = form.query(pattern).reduce((count, field) => { 100 | callback(field, form) 101 | return count + 1 102 | }, 0) 103 | if (count === 0) { 104 | _onFieldInit(pattern, callback) 105 | } 106 | } 107 | 108 | export function onFieldReact( 109 | pattern: FormPathPattern, 110 | callback?: (field: GeneralField, form: Form) => void 111 | ) { 112 | onFieldInit(pattern, (field, form) => { 113 | field.disposers.push( 114 | autorun(() => { 115 | if (isFn(callback)) callback(field, form) 116 | }) 117 | ) 118 | }) 119 | } 120 | export function onFieldChange( 121 | pattern: FormPathPattern, 122 | callback?: (field: GeneralField, form: Form) => void 123 | ): void 124 | export function onFieldChange( 125 | pattern: FormPathPattern, 126 | watches: (keyof IFieldState)[], 127 | callback?: (field: GeneralField, form: Form) => void 128 | ): void 129 | export function onFieldChange( 130 | pattern: FormPathPattern, 131 | watches: any, 132 | callback?: (field: GeneralField, form: Form) => void 133 | ): void { 134 | if (isFn(watches)) { 135 | callback = watches 136 | watches = ['value'] 137 | } else { 138 | watches = watches || ['value'] 139 | } 140 | onFieldInit(pattern, (field, form) => { 141 | if (isFn(callback)) callback(field, form) 142 | const dispose = reaction( 143 | () => { 144 | return toArr(watches).map((key) => { 145 | return field[key] 146 | }) 147 | }, 148 | () => { 149 | if (isFn(callback)) callback(field, form) 150 | } 151 | ) 152 | field.disposers.push(dispose) 153 | }) 154 | } 155 | ``` -------------------------------------------------------------------------------- /packages/reactive-vue/src/__tests__/observer.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { shallowMount, createLocalVue } from '@vue/test-utils' 2 | import { observable, autorun } from '@formily/reactive' 3 | import { CreateElement } from 'vue' 4 | import CompositionAPI, { defineComponent, h } from '@vue/composition-api' 5 | import { observer } from '../' 6 | import collectData from '../observer/collectData' 7 | import { observer as observerInVue2 } from '../observer/observerInVue2' 8 | import expect from 'expect' 9 | 10 | test('observer: component', async () => { 11 | const model = observable<any>({ 12 | age: 10, 13 | setAge() { 14 | model.age++ 15 | }, 16 | }) 17 | const Component = observer({ 18 | data() { 19 | return { 20 | model, 21 | } 22 | }, 23 | render(this: any, h: CreateElement) { 24 | return h('button', { 25 | on: { click: this.model.setAge }, 26 | domProps: { textContent: this.model.age }, 27 | }) 28 | }, 29 | }) 30 | const wrapper = shallowMount(Component) 31 | expect(wrapper.find('button').text()).toBe('10') 32 | wrapper.find('button').trigger('click') 33 | expect(wrapper.find('button').text()).toBe('11') 34 | wrapper.destroy() 35 | }) 36 | 37 | test('observer: component with setup', async () => { 38 | const Vue = createLocalVue() 39 | Vue.use(CompositionAPI) 40 | const model = observable<any>({ 41 | age: 30, 42 | get sub10() { 43 | return model.age - 10 44 | }, 45 | get sub20() { 46 | return model.sub10 - 10 47 | }, 48 | setAge() { 49 | model.age++ 50 | }, 51 | }) 52 | const Component = observer( 53 | defineComponent({ 54 | setup() { 55 | return () => { 56 | return h('button', { 57 | on: { click: model.setAge }, 58 | domProps: { textContent: model.sub20 }, 59 | }) 60 | } 61 | }, 62 | // to fix 'Maximum call stack size exceeded' error of @vue/test-utils 63 | render() { 64 | return null 65 | }, 66 | }) 67 | ) 68 | const wrapper = shallowMount(Component) 69 | expect(wrapper.find('button').text()).toBe('10') 70 | wrapper.find('button').trigger('click') 71 | expect(wrapper.find('button').text()).toBe('11') 72 | model.age++ 73 | expect(wrapper.find('button').text()).toBe('12') 74 | wrapper.destroy() 75 | }) 76 | 77 | test('observer: component scheduler', async () => { 78 | let schedulerRequest = null 79 | 80 | const model = observable<any>({ 81 | age: 10, 82 | setAge() { 83 | model.age++ 84 | }, 85 | }) 86 | const Component = observer( 87 | { 88 | data() { 89 | return { 90 | model, 91 | } 92 | }, 93 | render(this: any, h: CreateElement) { 94 | return h('button', { 95 | on: { click: this.model.setAge }, 96 | domProps: { textContent: this.model.age }, 97 | }) 98 | }, 99 | }, 100 | { 101 | scheduler: (update) => { 102 | clearTimeout(schedulerRequest) 103 | schedulerRequest = setTimeout(() => { 104 | update() 105 | }, 100) 106 | }, 107 | } 108 | ) 109 | const wrapper = shallowMount(Component) 110 | 111 | expect(wrapper.find('button').text()).toBe('10') 112 | 113 | wrapper.find('button').trigger('click') 114 | await new Promise((r) => setTimeout(r, 150)) 115 | expect(wrapper.find('button').text()).toBe('11') 116 | 117 | // test second render 118 | wrapper.find('button').trigger('click') 119 | await new Promise((r) => setTimeout(r, 150)) 120 | expect(wrapper.find('button').text()).toBe('12') 121 | 122 | wrapper.destroy() 123 | }) 124 | 125 | test('observer: stop tracking if watcher is destroyed', async () => { 126 | let count = 0 127 | const model = observable<any>({ 128 | age: 10, 129 | name: 'test', 130 | }) 131 | 132 | const Component = observer({ 133 | name: 'test', 134 | data() { 135 | return { 136 | model: model, 137 | } 138 | }, 139 | render() { 140 | count++ 141 | return h('div', [this.model.name, this.model.age]) 142 | }, 143 | }) 144 | 145 | const wrapper = shallowMount(Component) 146 | 147 | const childInst = wrapper.find({ name: 'test' }) 148 | 149 | expect(childInst.exists()).toBe(true) 150 | ;(childInst.vm as any)._isDestroyed = true 151 | model.age++ 152 | wrapper.destroy() 153 | expect(count).toEqual(1) // 不触发 reactiveRender 154 | }) 155 | 156 | test('collectData', async () => { 157 | const model = observable<any>({ 158 | age: 10, 159 | name: 'test', 160 | }) 161 | 162 | const target = { 163 | value: 1, 164 | } 165 | 166 | const data = collectData( 167 | {}, 168 | { 169 | model, 170 | target, 171 | } 172 | ) 173 | 174 | const fn1 = jest.fn() 175 | const fn2 = jest.fn() 176 | 177 | autorun(() => fn1(model.age)) 178 | autorun(() => fn2(data.target.value)) 179 | 180 | model.age++ 181 | expect(fn1).toBeCalledTimes(2) 182 | 183 | target.value++ 184 | expect(fn2).toBeCalledTimes(1) 185 | }) 186 | 187 | test('observerInVue2', () => { 188 | const componentObj = Object.create(null) 189 | 190 | componentObj.data = () => { 191 | return {} 192 | } 193 | 194 | const ExtendedComponent1 = observerInVue2(componentObj) 195 | expect(ExtendedComponent1.name).toEqual('<component>') 196 | 197 | function Component() {} 198 | Component.options = { 199 | data: () => { 200 | return {} 201 | }, 202 | } 203 | 204 | const ExtendedComponent2 = observerInVue2(Component, { name: 'abc' }) 205 | expect(ExtendedComponent2.name).toEqual('abc') 206 | }) 207 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-table/effects-json-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField :schema="schema" /> 4 | <Submit @submit="log">提交</Submit> 5 | </FormProvider> 6 | </template> 7 | 8 | <script> 9 | import { createForm, onFieldChange, onFieldReact } from '@formily/core' 10 | import { FormProvider, createSchemaField } from '@formily/vue' 11 | import { 12 | Submit, 13 | FormItem, 14 | ArrayTable, 15 | Input, 16 | Editable, 17 | Switch, 18 | } from '@formily/element' 19 | 20 | const fields = createSchemaField({ 21 | components: { 22 | FormItem, 23 | ArrayTable, 24 | Input, 25 | Editable, 26 | Switch, 27 | }, 28 | }) 29 | 30 | export default { 31 | components: { FormProvider, Submit, ...fields }, 32 | data() { 33 | const form = createForm({ 34 | effects: () => { 35 | //主动联动模式 36 | onFieldChange('hideFirstColumn', ['value'], (field) => { 37 | field.query('array.column3').take((target) => { 38 | target.visible = !field.value 39 | }) 40 | field.query('array.*.a2').take((target) => { 41 | target.visible = !field.value 42 | }) 43 | }) 44 | //被动联动模式 45 | onFieldReact('array.*.a2', (field) => { 46 | field.visible = !field.query('.a1').get('value') 47 | }) 48 | }, 49 | }) 50 | const schema = { 51 | type: 'object', 52 | properties: { 53 | hideFirstColumn: { 54 | type: 'boolean', 55 | title: '隐藏A2', 56 | 'x-decorator': 'FormItem', 57 | 'x-component': 'Switch', 58 | }, 59 | array: { 60 | type: 'array', 61 | 'x-decorator': 'FormItem', 62 | 'x-component': 'ArrayTable', 63 | items: { 64 | type: 'object', 65 | properties: { 66 | column1: { 67 | type: 'void', 68 | 'x-component': 'ArrayTable.Column', 69 | 'x-component-props': { 70 | width: 80, 71 | title: 'Index', 72 | align: 'center', 73 | }, 74 | properties: { 75 | index: { 76 | type: 'void', 77 | 'x-component': 'ArrayTable.Index', 78 | }, 79 | }, 80 | }, 81 | column2: { 82 | type: 'void', 83 | 'x-component': 'ArrayTable.Column', 84 | 'x-component-props': { width: 100, title: '显隐->A2' }, 85 | properties: { 86 | a1: { 87 | type: 'boolean', 88 | 'x-decorator': 'FormItem', 89 | 'x-component': 'Switch', 90 | }, 91 | }, 92 | }, 93 | column3: { 94 | type: 'void', 95 | 'x-component': 'ArrayTable.Column', 96 | 'x-component-props': { width: 200, title: 'A2' }, 97 | properties: { 98 | a2: { 99 | type: 'string', 100 | 'x-decorator': 'FormItem', 101 | 'x-component': 'Input', 102 | }, 103 | }, 104 | }, 105 | column4: { 106 | type: 'void', 107 | 'x-component': 'ArrayTable.Column', 108 | 'x-component-props': { title: 'A3' }, 109 | properties: { 110 | a3: { 111 | type: 'string', 112 | 'x-decorator': 'FormItem', 113 | 'x-component': 'Input', 114 | }, 115 | }, 116 | }, 117 | column5: { 118 | type: 'void', 119 | 'x-component': 'ArrayTable.Column', 120 | 'x-component-props': { 121 | title: 'Operations', 122 | prop: 'operations', 123 | width: 200, 124 | fixed: 'right', 125 | }, 126 | properties: { 127 | item: { 128 | type: 'void', 129 | 'x-component': 'FormItem', 130 | properties: { 131 | remove: { 132 | type: 'void', 133 | 'x-component': 'ArrayTable.Remove', 134 | }, 135 | moveDown: { 136 | type: 'void', 137 | 'x-component': 'ArrayTable.MoveDown', 138 | }, 139 | moveUp: { 140 | type: 'void', 141 | 'x-component': 'ArrayTable.MoveUp', 142 | }, 143 | }, 144 | }, 145 | }, 146 | }, 147 | }, 148 | }, 149 | properties: { 150 | add: { 151 | type: 'void', 152 | 'x-component': 'ArrayTable.Addition', 153 | title: '添加条目', 154 | }, 155 | }, 156 | }, 157 | }, 158 | } 159 | return { 160 | form, 161 | schema, 162 | } 163 | }, 164 | methods: { 165 | log(...v) { 166 | console.log(...v) 167 | }, 168 | }, 169 | } 170 | </script> 171 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-items/markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaArrayField 5 | name="string_array" 6 | title="字符串数组" 7 | x-decorator="FormItem" 8 | x-component="ArrayItems" 9 | > 10 | <SchemaVoidField x-component="Space"> 11 | <SchemaVoidField 12 | x-decorator="FormItem" 13 | x-component="ArrayItems.SortHandle" 14 | /> 15 | <SchemaStringField 16 | x-decorator="FormItem" 17 | required 18 | name="input" 19 | x-component="Input" 20 | :x-component-props="{ 21 | style: { 22 | width: '160px', 23 | }, 24 | }" 25 | /> 26 | <SchemaVoidField 27 | x-decorator="FormItem" 28 | x-component="ArrayItems.Remove" 29 | /> 30 | </SchemaVoidField> 31 | <SchemaVoidField x-component="ArrayItems.Addition" title="添加条目" /> 32 | </SchemaArrayField> 33 | <SchemaArrayField 34 | name="array" 35 | title="对象数组" 36 | x-decorator="FormItem" 37 | x-component="ArrayItems" 38 | > 39 | <SchemaObjectField> 40 | <SchemaVoidField x-component="Space"> 41 | <SchemaVoidField 42 | x-decorator="FormItem" 43 | x-component="ArrayItems.SortHandle" 44 | /> 45 | <SchemaStringField 46 | x-decorator="FormItem" 47 | required 48 | title="日期" 49 | name="date" 50 | x-component="DatePicker" 51 | :x-component-props="{ 52 | type: 'daterange', 53 | style: { 54 | width: '160px', 55 | }, 56 | }" 57 | /> 58 | <SchemaStringField 59 | x-decorator="FormItem" 60 | required 61 | title="输入框" 62 | name="input" 63 | x-component="Input" 64 | /> 65 | <SchemaStringField 66 | x-decorator="FormItem" 67 | required 68 | title="选择框" 69 | name="select" 70 | :enum="[ 71 | { label: '选项1', value: 1 }, 72 | { label: '选项2', value: 2 }, 73 | ]" 74 | x-component="Select" 75 | :x-component-props="{ 76 | style: { 77 | width: 160, 78 | }, 79 | }" 80 | /> 81 | <SchemaVoidField 82 | x-decorator="FormItem" 83 | x-component="ArrayItems.Remove" 84 | /> 85 | </SchemaVoidField> 86 | </SchemaObjectField> 87 | <SchemaVoidField x-component="ArrayItems.Addition" title="添加条目" /> 88 | </SchemaArrayField> 89 | <SchemaArrayField 90 | name="array2" 91 | title="对象数组" 92 | x-decorator="FormItem" 93 | x-component="ArrayItems" 94 | :x-component-props="{ style: { width: '600px' } }" 95 | > 96 | <SchemaObjectField x-decorator="ArrayItems.Item"> 97 | <SchemaVoidField x-component="Space"> 98 | <SchemaVoidField 99 | x-decorator="FormItem" 100 | x-component="ArrayItems.SortHandle" 101 | /> 102 | <SchemaStringField 103 | x-decorator="FormItem" 104 | required 105 | title="日期" 106 | name="date" 107 | x-component="DatePicker" 108 | :x-component-props="{ 109 | type: 'daterange', 110 | style: { 111 | width: '250px', 112 | }, 113 | }" 114 | /> 115 | <SchemaStringField 116 | x-decorator="FormItem" 117 | required 118 | title="输入框" 119 | name="input" 120 | x-component="Input" 121 | /> 122 | <SchemaVoidField 123 | x-decorator="FormItem" 124 | x-component="ArrayItems.Remove" 125 | /> 126 | </SchemaVoidField> 127 | </SchemaObjectField> 128 | <SchemaVoidField x-component="ArrayItems.Addition" title="添加条目" /> 129 | </SchemaArrayField> 130 | </SchemaField> 131 | <FormButtonGroup> 132 | <Submit @submit="log">提交</Submit> 133 | </FormButtonGroup> 134 | </FormProvider> 135 | </template> 136 | 137 | <script> 138 | import { createForm } from '@formily/core' 139 | import { FormProvider, createSchemaField } from '@formily/vue' 140 | import { 141 | FormItem, 142 | FormButtonGroup, 143 | Submit, 144 | Input, 145 | Select, 146 | Space, 147 | DatePicker, 148 | ArrayItems, 149 | } from '@formily/element' 150 | import { Button } from 'element-ui' 151 | 152 | const SchemaField = createSchemaField({ 153 | components: { 154 | FormItem, 155 | Space, 156 | Input, 157 | Select, 158 | DatePicker, 159 | ArrayItems, 160 | }, 161 | }) 162 | 163 | export default { 164 | components: { 165 | FormProvider, 166 | FormButtonGroup, 167 | Button, 168 | Submit, 169 | ...SchemaField, 170 | }, 171 | 172 | data() { 173 | const form = createForm() 174 | 175 | return { 176 | form, 177 | } 178 | }, 179 | methods: { 180 | log(values) { 181 | console.log(values) 182 | }, 183 | }, 184 | } 185 | </script> 186 | 187 | <style lang="scss" scoped></style> 188 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Reset.md: -------------------------------------------------------------------------------- ```markdown 1 | # Reset 2 | 3 | > Reset button 4 | 5 | ## Normal reset 6 | 7 | > Controls with default values cannot be cleared 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd' 12 | import { createForm } from '@formily/core' 13 | import { FormProvider, createSchemaField } from '@formily/react' 14 | 15 | const SchemaField = createSchemaField({ 16 | components: { 17 | Input, 18 | FormItem, 19 | }, 20 | }) 21 | 22 | const form = createForm() 23 | 24 | export default () => ( 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.String 28 | name="input" 29 | title="input box" 30 | required 31 | x-decorator="FormItem" 32 | x-component="Input" 33 | /> 34 | <SchemaField.String 35 | name="input2" 36 | title="input box" 37 | default="123" 38 | required 39 | x-decorator="FormItem" 40 | x-component="Input" 41 | /> 42 | </SchemaField> 43 | <FormButtonGroup> 44 | <Reset>Reset</Reset> 45 | </FormButtonGroup> 46 | </FormProvider> 47 | ) 48 | ``` 49 | 50 | ## Force empty reset 51 | 52 | ```tsx 53 | import React from 'react' 54 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd' 55 | import { createForm } from '@formily/core' 56 | import { FormProvider, createSchemaField } from '@formily/react' 57 | 58 | const SchemaField = createSchemaField({ 59 | components: { 60 | Input, 61 | FormItem, 62 | }, 63 | }) 64 | 65 | const form = createForm() 66 | 67 | export default () => ( 68 | <FormProvider form={form}> 69 | <SchemaField> 70 | <SchemaField.String 71 | name="input" 72 | title="input box" 73 | required 74 | x-decorator="FormItem" 75 | x-component="Input" 76 | /> 77 | <SchemaField.String 78 | name="input2" 79 | title="input box" 80 | default="123" 81 | required 82 | x-decorator="FormItem" 83 | x-component="Input" 84 | /> 85 | </SchemaField> 86 | <FormButtonGroup> 87 | <Reset forceClear>Reset</Reset> 88 | </FormButtonGroup> 89 | </FormProvider> 90 | ) 91 | ``` 92 | 93 | ## Reset and verify 94 | 95 | ```tsx 96 | import React from 'react' 97 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd' 98 | import { createForm } from '@formily/core' 99 | import { FormProvider, createSchemaField } from '@formily/react' 100 | 101 | const SchemaField = createSchemaField({ 102 | components: { 103 | Input, 104 | FormItem, 105 | }, 106 | }) 107 | 108 | const form = createForm() 109 | 110 | export default () => ( 111 | <FormProvider form={form}> 112 | <SchemaField> 113 | <SchemaField.String 114 | name="input" 115 | title="input box" 116 | required 117 | x-decorator="FormItem" 118 | x-component="Input" 119 | /> 120 | <SchemaField.String 121 | name="input2" 122 | title="input box" 123 | default="123" 124 | required 125 | x-decorator="FormItem" 126 | x-component="Input" 127 | /> 128 | </SchemaField> 129 | <FormButtonGroup> 130 | <Reset validate>Reset</Reset> 131 | </FormButtonGroup> 132 | </FormProvider> 133 | ) 134 | ``` 135 | 136 | ## Force empty reset and verify 137 | 138 | ```tsx 139 | import React from 'react' 140 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/antd' 141 | import { createForm } from '@formily/core' 142 | import { FormProvider, createSchemaField } from '@formily/react' 143 | 144 | const SchemaField = createSchemaField({ 145 | components: { 146 | Input, 147 | FormItem, 148 | }, 149 | }) 150 | 151 | const form = createForm() 152 | 153 | export default () => ( 154 | <FormProvider form={form}> 155 | <SchemaField> 156 | <SchemaField.String 157 | name="input" 158 | title="input box" 159 | required 160 | x-decorator="FormItem" 161 | x-component="Input" 162 | /> 163 | <SchemaField.String 164 | name="input2" 165 | title="input box" 166 | default="123" 167 | required 168 | x-decorator="FormItem" 169 | x-component="Input" 170 | /> 171 | </SchemaField> 172 | <FormButtonGroup> 173 | <Reset forceClear validate> 174 | Reset 175 | </Reset> 176 | </FormButtonGroup> 177 | </FormProvider> 178 | ) 179 | ``` 180 | 181 | ## API 182 | 183 | ### Reset 184 | 185 | Other API reference https://ant.design/components/button-cn/ 186 | 187 | | Property name | Type | Description | Default value | 188 | | ---------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ------------- | 189 | | onClick | `(event: MouseEvent) => void \| boolean` | Click event, if it returns false, it can block resetting | - | 190 | | onResetValidateSuccess | (payload: any) => void | Reset validation success event | - | 191 | | onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Reset validation failure event | - | 192 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Reset.md: -------------------------------------------------------------------------------- ```markdown 1 | # Reset 2 | 3 | > Reset button 4 | 5 | ## Normal reset 6 | 7 | > Controls with default values cannot be cleared 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next' 12 | import { createForm } from '@formily/core' 13 | import { FormProvider, createSchemaField } from '@formily/react' 14 | 15 | const SchemaField = createSchemaField({ 16 | components: { 17 | Input, 18 | FormItem, 19 | }, 20 | }) 21 | 22 | const form = createForm() 23 | 24 | export default () => ( 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.String 28 | name="input" 29 | title="input box" 30 | required 31 | x-decorator="FormItem" 32 | x-component="Input" 33 | /> 34 | <SchemaField.String 35 | name="input2" 36 | title="input box" 37 | default="123" 38 | required 39 | x-decorator="FormItem" 40 | x-component="Input" 41 | /> 42 | </SchemaField> 43 | <FormButtonGroup> 44 | <Reset>Reset</Reset> 45 | </FormButtonGroup> 46 | </FormProvider> 47 | ) 48 | ``` 49 | 50 | ## Force empty reset 51 | 52 | ```tsx 53 | import React from 'react' 54 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next' 55 | import { createForm } from '@formily/core' 56 | import { FormProvider, createSchemaField } from '@formily/react' 57 | 58 | const SchemaField = createSchemaField({ 59 | components: { 60 | Input, 61 | FormItem, 62 | }, 63 | }) 64 | 65 | const form = createForm() 66 | 67 | export default () => ( 68 | <FormProvider form={form}> 69 | <SchemaField> 70 | <SchemaField.String 71 | name="input" 72 | title="input box" 73 | required 74 | x-decorator="FormItem" 75 | x-component="Input" 76 | /> 77 | <SchemaField.String 78 | name="input2" 79 | title="input box" 80 | default="123" 81 | required 82 | x-decorator="FormItem" 83 | x-component="Input" 84 | /> 85 | </SchemaField> 86 | <FormButtonGroup> 87 | <Reset forceClear>Reset</Reset> 88 | </FormButtonGroup> 89 | </FormProvider> 90 | ) 91 | ``` 92 | 93 | ## Reset and verify 94 | 95 | ```tsx 96 | import React from 'react' 97 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next' 98 | import { createForm } from '@formily/core' 99 | import { FormProvider, createSchemaField } from '@formily/react' 100 | 101 | const SchemaField = createSchemaField({ 102 | components: { 103 | Input, 104 | FormItem, 105 | }, 106 | }) 107 | 108 | const form = createForm() 109 | 110 | export default () => ( 111 | <FormProvider form={form}> 112 | <SchemaField> 113 | <SchemaField.String 114 | name="input" 115 | title="input box" 116 | required 117 | x-decorator="FormItem" 118 | x-component="Input" 119 | /> 120 | <SchemaField.String 121 | name="input2" 122 | title="input box" 123 | default="123" 124 | required 125 | x-decorator="FormItem" 126 | x-component="Input" 127 | /> 128 | </SchemaField> 129 | <FormButtonGroup> 130 | <Reset validate>Reset</Reset> 131 | </FormButtonGroup> 132 | </FormProvider> 133 | ) 134 | ``` 135 | 136 | ## Force empty reset and verify 137 | 138 | ```tsx 139 | import React from 'react' 140 | import { Input, FormItem, FormButtonGroup, Reset } from '@formily/next' 141 | import { createForm } from '@formily/core' 142 | import { FormProvider, createSchemaField } from '@formily/react' 143 | 144 | const SchemaField = createSchemaField({ 145 | components: { 146 | Input, 147 | FormItem, 148 | }, 149 | }) 150 | 151 | const form = createForm() 152 | 153 | export default () => ( 154 | <FormProvider form={form}> 155 | <SchemaField> 156 | <SchemaField.String 157 | name="input" 158 | title="input box" 159 | required 160 | x-decorator="FormItem" 161 | x-component="Input" 162 | /> 163 | <SchemaField.String 164 | name="input2" 165 | title="input box" 166 | default="123" 167 | required 168 | x-decorator="FormItem" 169 | x-component="Input" 170 | /> 171 | </SchemaField> 172 | <FormButtonGroup> 173 | <Reset forceClear validate> 174 | Reset 175 | </Reset> 176 | </FormButtonGroup> 177 | </FormProvider> 178 | ) 179 | ``` 180 | 181 | ## API 182 | 183 | ### Reset 184 | 185 | Other API reference https://fusion.design/pc/component/basic/button 186 | 187 | | Property name | Type | Description | Default value | 188 | | ---------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ------------- | 189 | | onClick | `(event: MouseEvent) => void \| boolean` | Click event, if it returns false, it can block resetting | - | 190 | | onResetValidateSuccess | (payload: any) => void | Reset validation success event | - | 191 | | onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Reset validation failure event | - | 192 | ``` -------------------------------------------------------------------------------- /packages/element/src/form-step/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Form, VoidField } from '@formily/core' 2 | import { Schema, SchemaKey } from '@formily/json-schema' 3 | import { action, model, observable } from '@formily/reactive' 4 | import { observer } from '@formily/reactive-vue' 5 | import { 6 | Fragment, 7 | h, 8 | RecursionField, 9 | useField, 10 | useFieldSchema, 11 | } from '@formily/vue' 12 | import { Step, Steps } from 'element-ui' 13 | import { defineComponent, PropType } from 'vue-demi' 14 | import { stylePrefix } from '../__builtins__/configs' 15 | 16 | import type { Step as StepProps, Steps as StepsProps } from 'element-ui' 17 | import { composeExport } from '../__builtins__/shared' 18 | 19 | export interface IFormStep { 20 | connect: (steps: SchemaStep[], field: VoidField) => void 21 | current: number 22 | allowNext: boolean 23 | allowBack: boolean 24 | setCurrent(key: number): void 25 | submit: Form['submit'] 26 | next(): void 27 | back(): void 28 | } 29 | 30 | export interface IFormStepProps extends StepsProps { 31 | formStep?: IFormStep 32 | } 33 | 34 | type SchemaStep = { 35 | name: SchemaKey 36 | props: any 37 | schema: Schema 38 | } 39 | 40 | type FormStepEnv = { 41 | form: Form 42 | field: VoidField 43 | steps: SchemaStep[] 44 | } 45 | 46 | const parseSteps = (schema: Schema) => { 47 | const steps: SchemaStep[] = [] 48 | schema.mapProperties((schema, name) => { 49 | if (schema['x-component']?.indexOf('StepPane') > -1) { 50 | steps.push({ 51 | name, 52 | props: schema['x-component-props'], 53 | schema, 54 | }) 55 | } 56 | }) 57 | return steps 58 | } 59 | 60 | const createFormStep = (defaultCurrent = 0): IFormStep => { 61 | const env: FormStepEnv = observable({ 62 | form: null, 63 | field: null, 64 | steps: [], 65 | }) 66 | 67 | const setDisplay = action.bound((target: number) => { 68 | const currentStep = env.steps[target] 69 | env.steps.forEach(({ name }) => { 70 | env.form.query(`${env.field.address}.${name}`).take((field) => { 71 | if (name === currentStep.name) { 72 | field.setDisplay('visible') 73 | } else { 74 | field.setDisplay('hidden') 75 | } 76 | }) 77 | }) 78 | }) 79 | 80 | const next = action.bound(() => { 81 | if (formStep.allowNext) { 82 | setDisplay(formStep.current + 1) 83 | formStep.setCurrent(formStep.current + 1) 84 | } 85 | }) 86 | 87 | const back = action.bound(() => { 88 | if (formStep.allowBack) { 89 | setDisplay(formStep.current - 1) 90 | formStep.setCurrent(formStep.current - 1) 91 | } 92 | }) 93 | 94 | const formStep: IFormStep = model({ 95 | connect(steps, field) { 96 | env.steps = steps 97 | env.form = field?.form 98 | env.field = field 99 | }, 100 | current: defaultCurrent, 101 | setCurrent(key: number) { 102 | formStep.current = key 103 | }, 104 | get allowNext() { 105 | return formStep.current < env.steps.length - 1 106 | }, 107 | get allowBack() { 108 | return formStep.current > 0 109 | }, 110 | async next() { 111 | try { 112 | await env.form.validate() 113 | next() 114 | } catch {} 115 | }, 116 | async back() { 117 | back() 118 | }, 119 | async submit(onSubmit) { 120 | return env.form?.submit?.(onSubmit) 121 | }, 122 | }) 123 | return formStep 124 | } 125 | 126 | const FormStepInner = observer( 127 | defineComponent<IFormStepProps>({ 128 | name: 'FFormStep', 129 | props: { 130 | formStep: { 131 | type: Object as PropType<IFormStep>, 132 | default() { 133 | return { 134 | current: 0, 135 | } 136 | }, 137 | }, 138 | }, 139 | setup(props, { attrs }) { 140 | const field = useField<VoidField>().value 141 | const prefixCls = `${stylePrefix}-form-step` 142 | const fieldSchemaRef = useFieldSchema() 143 | 144 | const steps = parseSteps(fieldSchemaRef.value) 145 | 146 | props.formStep.connect?.(steps, field) 147 | 148 | return () => { 149 | const current = props.active || props.formStep?.current || 0 150 | 151 | const renderSteps = (steps: SchemaStep[], callback) => { 152 | return steps.map(callback) 153 | } 154 | 155 | return h( 156 | 'div', 157 | { 158 | class: [prefixCls], 159 | }, 160 | { 161 | default: () => [ 162 | h( 163 | Steps, 164 | { 165 | props: { 166 | active: current, 167 | }, 168 | style: [{ marginBottom: '10px' }, attrs.style], 169 | attrs, 170 | }, 171 | { 172 | default: () => 173 | renderSteps(steps, ({ props }, key) => { 174 | return h(Step, { props, key }, {}) 175 | }), 176 | } 177 | ), 178 | 179 | renderSteps(steps, ({ name, schema }, key) => { 180 | if (key !== current) return 181 | return h(RecursionField, { props: { name, schema }, key }, {}) 182 | }), 183 | ], 184 | } 185 | ) 186 | } 187 | }, 188 | }) 189 | ) 190 | 191 | const StepPane = defineComponent<StepProps>({ 192 | name: 'FFormStepPane', 193 | setup(_props, { slots }) { 194 | return () => h(Fragment, {}, slots) 195 | }, 196 | }) 197 | 198 | export const FormStep = composeExport(FormStepInner, { 199 | StepPane, 200 | createFormStep, 201 | }) 202 | 203 | export default FormStep 204 | ```