This is page 30 of 35. Use http://codebase.md/alibaba/formily?lines=false&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/react/docs/api/shared/Schema.md: -------------------------------------------------------------------------------- ```markdown # Schema ## Description The core part of the @formily/react protocol driver. Schema is a general class in which users can use it by themselves. At the same time, both SchemaField and RecursionField rely on it. It has several core capabilities: - Ability to parse json-schema - The ability to convert json-schema to Field Model - The ability to compile json-schema expressions You can export the Schema Class from @formily/react, but if you don’t want to use @formily/react, you can rely on the @formily/json-schema package alone ## Constructor ```ts class Schema { constructor(json: ISchema, parent?: ISchema) } ``` Create a Schema Tree based on a piece of json schema data to ensure that each schema node contains the corresponding method ## Attributes | Property | Description | Type | Field Model Mapping | | -------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | type | Type | [SchemaTypes](#schematypes) | [GeneralField](https://core.formilyjs.org/api/models/field#generalfield) | | title | Title | React.ReactNode | `title` | | description | Description | React.ReactNode | `description` | | default | Default value | Any | `initialValue` | | readOnly | Is it read-only | Boolean | `readOnly` | | writeOnly | Whether to write only | Boolean | `editable` | | enum | Enumeration | [SchemaEnum](#schemaenum) | `dataSource` | | const | Check whether the field value is equal to the value of const | Any | `validator` | | multipleOf | Check whether the field value is divisible by the value of multipleOf | Number | `validator` | | maximum | Check the maximum value (greater than) | Number | `validator` | | exclusiveMaximum | Check the maximum value (greater than or equal to | Number | `validator` | | minimum | Validation minimum value (less than) | Number | `validator` | | exclusiveMinimum | Minimum value (less than or equal to) | Number | `validator` | | maxLength | Maximum length of verification | Number | `validator` | | minLength | Check minimum length | Number | `validator` | | pattern | Regular verification rules | RegExpString | `validator` | | maxItems | Maximum number of items | Number | `validator` | | minItems | Minimum number of items | Number | `validator` | | uniqueItems | Whether to verify duplicates | Boolean | `validator` | | maxProperties | Maximum number of properties | Number | `validator` | | minProperties | Minimum number of properties | Number | `validator` | | required | required | Boolean | `validator` | | format | Regular verification format | [ValidatorFormats](https://core.formilyjs.org/api/models/field#fieldvalidator) | `validator` | | properties | Property description | [SchemaProperties](#schemaproperties) | - | | items | Array description | [SchemaItems](#schemaitems) | - | | additionalItems | Additional array element description | Schema | - | | patternProperties | Schema of a certain property of the dynamic matching object | [SchemaProperties](#schemaproperties) | - | | additionalProperties | Schema of matching object additional properties | Schema | - | | x-index | UI display order | Number | - | | x-pattern | UI interaction mode | [FieldPatternTypes](https://core.formilyjs.org/api/models/field#fieldpatterntypes) | `pattern` | | x-display | UI display | [FieldDisplayTypes](https://core.formilyjs.org/api/models/field#fielddisplaytypes) | `display` | | x-validator | Field Validator | [FieldValidator](https://core.formilyjs.org/api/models/field#fieldvalidator) | `validator` | | x-decorator | Field UI wrapper component | `String \| React.FC` | `decorator` | | x-decorator-props | Field UI wrapper component properties | Any | `decorator` | | x-component | Field UI component | `String \| React.FC` | `component` | | x-component-props | Field UI component properties | Any | `component` | | x-reactions | Field linkage agreement | [SchemaReactions](#schemareactions) | `reactions` | | x-content | Field content, used to pass in the child nodes of a component | React.ReactNode | `content` | | x-visible | Field display hidden | Boolean | `visible` | | x-hidden | Field UI hidden (data retention) | Boolean | `hidden` | | x-disabled | Field disabled | Boolean | `disabled` | | x-editable | Editable field | Boolean | `editable` | | x-read-only | Field read-only | Boolean | `readOnly` | | x-read-pretty | Field Reading State | Boolean | `readPretty` | | definitions | Schema predefined | [SchemaProperties](#schemaproperties) | - | | $ref | Read the Schema from the Schema predefined and merge it into the current Schema | String | - | | x-data | Extends Data | Object | `data` | | x-compile-omitted | list of attributes to ignore compiled expressions | string[] | `[]` | | x-slot-node | Slot node mark | [Slot](#slot) | - | #### Detailed description - The component ID of x-component matches the key of the component collection passed in [createSchemaField](/api/components/schema-field#signature) - The component ID of x-decorator matches the key of the component collection passed in [createSchemaField](/api/components/schema-field#signature) - Every attribute of Schema can use string expression `{{expression}}`, expression variables can be passed in from createSchemaField or from SchemaField component - The predefined format of $ref specified Schema must be `#/definitions/address` this format, loading remote JSON Schema is not supported ## Method ### addProperty #### Description Add attribute description #### Signature ```ts interface addProperty { (key: string | number, schema: ISchema): Schema //Return the added Schema object } ``` ### removeProperty #### Description Remove attribute description #### Signature ```ts interface removeProperty { (key: string | number): Schema //Return the removed Schema object } ``` ### setProperties #### Description Overwrite update attribute description #### Signature ```ts interface setProperties { (properties: SchemaProperties): Schema //Return the current Schema object } ``` SchemaProperties Reference [SchemaProperties](#schemaproperties) ### addPatternProperty #### Description Add regular attribute description #### Signature ```ts interface addPatternProperty { (regexp: string, schema: ISchema): Schema //Return the added Schema object } ``` ### removePatternProperty #### Description Remove regular attribute description #### Signature ```ts interface removePatternProperty { (regexp: string): Schema //Return the removed Schema object } ``` ### setPatternProperties #### Description Override update regular attribute description #### Signature ```ts interface setPatternProperties { (properties: SchemaProperties): Schema //Return the current Schema object } ``` SchemaProperties Reference [SchemaProperties](#schemaproperties) ### setAdditionalProperties #### Description Overwrite update extended attribute description #### Signature ```ts interface setAdditionalProperties { (properties: ISchema): Schema //Returns the extended properties Schema object } ``` ### setItems #### Description Override to update the array item description #### Signature ```ts interface setItems { (items: SchemaItems): SchemaItems //Return the updated SchemaItems object } ``` SchemaItems Reference [SchemaItems](#schemaitems) ### setAdditionalItems #### Description Override to update the array extension item description #### Signature ```ts interface setAdditionalItems { (items: ISchema): Schema //Return the updated Schema object } ``` SchemaItems Reference [SchemaItems](#schemaitems) ### mapProperties #### Description Traverse and map the properties of the current Schema, and traverse based on the x-index order #### Signature ```ts interface mapProperties<T> { (mapper: (property: Schema, key: string | number) => T): T[] } ``` ### mapPatternProperties #### Description Traverse and map the patternProperties attribute of the current Schema, and traverse based on the x-index order #### Signature ```ts interface mapPatternProperties<T> { (mapper: (property: Schema, key: string | number) => T): T[] } ``` ### reduceProperties #### Description reduce the properties of the current Schema, and it will be traversed based on the x-index order #### Signature ```ts interface reduceProperties<T> { ( reducer: (value: T, property: Schema, key: string | number) => T, initialValue?: T ): T } ``` ### reducePatternProperties #### Description reduce the patternProperties attribute of the current Schema, and it will be traversed based on the x-index order #### Signature ```ts interface reducePatternProperties<T> { ( reducer: (value: T, property: Schema, key: string | number) => T, initialValue?: T ): T } ``` ### compile #### Description Deeply recurse the expression fragments in the current Schema object, compile the expression, and return the Schema. We can pass in the scope object, and then consume the scope variable in the expression Expression fragment convention: a string ending with `{{`beginning with `}}` represents an expression fragment #### Signature ```ts interface compile { (scope: any): Schema } ``` ### fromJSON #### Description Convert ordinary json data into Schema objects #### Signature ```ts interface fromJSON { (json: ISchema): Schema } ``` ### toJSON #### Description Convert the current Schema object into ordinary json data #### Signature ```ts interface toJSON { (): ISchema } ``` ### toFieldProps #### Description Convert the current Schema object into a Formily field model attribute, refer to the mapping relationship [attribute](#attributes) #### Signature ```ts import { IFieldFactoryProps } from '@formily/core' interface toFieldProps { (): IFieldFactoryProps } ``` IFieldFactoryProps reference [IFieldFactoryProps](https://core.formilyjs.org/api/models/form#ifieldfactoryprops) ## Static method ### getOrderProperties #### Description Get the sorted properties from the Schema #### Signature ```ts interface getOrderProperties { (schema: ISchema = {}, propertiesName: keyof ISchema = 'properties'): ISchema } ``` ### compile #### Description In-depth traversal of expression fragments in any object, expression fragment convention: a string ending with `{{`beginning`}}` represents an expression fragment #### Signature ```ts interface compile { (target: any, scope: any): any } ``` ### shallowCompile #### Description Shallow traversal of expression fragments in any object, expression fragment convention: a string ending with `{{`beginning with `}}` represents an expression fragment #### Signature ```ts interface shallowCompile { (target: any, scope: any): any } ``` ### silent #### Description Whether to compile silently, if it is, there will be no reminder if the expression error is reported #### Signature ```ts interface silent { (value?: boolean): void } ``` ### isSchemaInstance #### Description Determine whether an object is an instance of Schema Class #### Signature ```ts interface isSchemaInstance { (target: any): target is Schema } ``` ### registerCompiler #### Description Register the expression compiler #### Signature ```ts interface registerCompiler { (compiler: (expression: string, scope: any) => any): void } ``` ### registerPatches #### Description Register Schema patch to facilitate compatibility of different versions of Schema protocol #### Signature ```ts type SchemaPatch = (schema: ISchema) => ISchema interface registerPatches { (...args: SchemaPatch[]): void } ``` ### registerVoidComponents #### Description Mark the field component to indicate that the component is a virtual component and is compatible with formily1.x #### Signature ```ts interface registerVoidComponents { (components: string[]): void } ``` #### Example ```ts import { Schema } from '@formily/react' Schema.registerVoidComponents(['card', 'tab', 'step']) ``` <Alert type="warning"> Note that this api needs to be used with <code>enablePolyfills(['1.0'])</code> </Alert> ### registerTypeDefaultComponents #### Description Identify the default component type for the Schema type #### Signature ```ts interface registerTypeDefaultComponents { (maps: Record<string, string>): void } ``` #### Example ```ts import { Schema } from '@formily/react' Schema.registerTypeDefaultComponents({ string: 'Input', number: 'NumberPicker', array: 'ArrayTable', }) ``` <Alert type="warning"> Note that this api needs to be used with <code>enablePolyfills(['1.0'])</code> </Alert> ### registerPolyfills #### Description Registration agreement compatible gasket #### Signature ```ts type SchemaPatch = (schema: ISchema) => ISchema interface registerPolyfills { (version: string, patch: SchemaPatch): void } ``` #### Example ```ts import { Schema } from '@formily/react' Schema.registerPolyfills('1.0', (schema) => { schema['x-decorator'] = 'FormItem' return schema }) ``` ### enablePolyfills #### Description Turn on the protocol gasket, the 1.0 version protocol compatible gasket is built in by default, and the main compatibility features are: - x-decorator does not declare, it is automatically used as FormItem - x-linkages converted to x-reactions - x-props is automatically converted to x-decorator-props - x-rules converted to x-validator - convert editable to x-editable - Convert visible to x-visible - x-component is automatically converted to VoidField for card/block/grid-row/grid-col/grid/layout/step/tab/text-box, #### Signature ```ts interface enablePolyfills { (versions: string[]): void } ``` #### Example ```ts import { Schema } from '@formily/react' Schema.enablePolyfills(['1.0']) ``` ## Types of ### ISchema #### Description ISchema is a normal JSON data, and at the same time it is JSON data following the Schema [Attribute](#attributes) specification ### SchemaTypes #### Description Schema description type #### Signature ```ts type SchemaTypes = | 'string' | 'object' | 'array' | 'number' | 'boolean' | 'void' | 'date' | 'datetime' | (string & {}) ``` ### SchemaProperties #### Description Schema attribute description #### Signature ```ts type SchemaProperties = Record<string, ISchema> ``` ### SchemaItems #### Description Schema array item description #### Signature ```ts type SchemaItems = ISchema | ISchema[] ``` ### SchemaEnum #### Description Schema enum #### Signature ```ts type SchemaEnum<Message> = Array< | string | number | { label: Message; value: any; [key: string]: any } | { key: any; title: Message; [key: string]: any } > ``` ### SchemaReactions #### Description Schema linkage protocol, if the reaction object contains target, it represents active linkage mode, otherwise it represents passive linkage mode If you want to achieve more complex linkage, you can pass in the reaction responder function through the scope for processing FormPathPattern path syntax documentation is [here](https://core.formilyjs.org/api/entry/form-path#formpathpattern) #### Signature ```ts import { IGeneralFieldState } from '@formily/core' type SchemaReactionEffect = | 'onFieldInit' | 'onFieldMount' | 'onFieldUnmount' | 'onFieldValueChange' | 'onFieldInputValueChange' | 'onFieldInitialValueChange' | 'onFieldValidateStart' | 'onFieldValidateEnd' | 'onFieldValidateFailed' | 'onFieldValidateSuccess' type SchemaReaction<Field = any> = | { dependencies?: //The list of dependent field paths can only describe dependencies in dot paths, and supports relative paths | Array< | string //If it is an array contains string format, then it is also an array format when reading | { //If it is an array contains object format, then it is an object format when reading, but the name field is equivalent to an alias name?: string type?: string source?: string property?: string } > | Record<string, string> //If it is an object format, It is also an object format when reading, but the key for object is equivalent to an alias when?: string | boolean //Linkage condition target?: string //The field path to be operated, supports FormPathPattern path syntax, note: relative path is not supported! ! effects?: SchemaReactionEffect[] //Independent life cycle hook in active mode fulfill?: { //To meet the conditions state?: IGeneralFieldState //Update state schema?: ISchema //Update Schema run?: string //Execute statement } otherwise?: { //Does not meet the conditions state?: IGeneralFieldState //Update state schema?: ISchema //Update Schema run?: string //Execute statement } } | ((field: Field) => void) //Can be complex linkage type SchemaReactions<Field = any> = | SchemaReaction<Field> | SchemaReaction<Field>[] ``` #### Example **Active linkage** Writing method one, standard initiative linkage ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input", "x-reactions": { "target": "target", "when": "{{$self.value === '123'}}", "fulfill": { "state": { "visible": false } }, "otherwise": { "state": { "visible": true } } } }, "target": { "type": "string", "x-component": "Input" } } } ``` Writing method two, local expression distribution linkage ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input", "x-reactions": { "target": "target", "fulfill": { "state": { "visible": "{{$self.value === '123'}}" //Any level of attributes supports expressions } } } }, "target": { "type": "string", "x-component": "Input" } } } ``` Writing method three, based on Schema protocol linkage ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input", "x-reactions": { "target": "target", "fulfill": { "schema": { "x-visible": "{{$self.value === '123'}}" //Any level of attributes supports expressions } } } }, "target": { "type": "string", "x-component": "Input" } } } ``` Writing method four, based on run statement linkage ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input", "x-reactions": { "fulfill": { "run": "$form.setFieldState('target',state=>{state.visible = $self.value === '123'})" } } }, "target": { "type": "string", "x-component": "Input" } } } ``` Writing method five, based on the linkage of life cycle hooks ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input", "x-reactions": { "target": "target", "effects": ["onFieldInputValueChange"], "fulfill": { "state": { "visible": "{{$self.value === '123'}}" //Any level of attributes supports expressions } } } }, "target": { "type": "string", "x-component": "Input" } } } ``` **Passive linkage** Writing method one, standard passive linkage ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input" }, "target": { "type": "string", "x-component": "Input", "x-reactions": { "dependencies": ["source"], //Dependency path is written by default to take value. If you rely on other attributes of the field, you can use source#modified, and use # to split to get detailed attributes // "dependencies":{ aliasName:"source" }, //alias form "fulfill": { "schema": { "x-visible": "{{$deps[0] === '123'}}" //Any level of attributes supports expressions } } } } } } ``` Writing method two, linkage of adjacent elements ```json { "type": "array", "x-component": "ArrayTable", "items": { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input" }, "target": { "type": "string", "x-component": "Input", "x-reactions": { "dependencies": [".source"], "fulfill": { "schema": { "x-visible": "{{$deps[0] === '123'}}" } } } } } } } ``` **Complex linkage** ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input" }, "target": { "type": "string", "x-component": "Input", "x-reactions": "{{myReaction}}" //For externally passed functions, more complex linkages can be realized within the function } } } ``` **Component attribute linkage** Writing one, operating status ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input", "x-reactions": { "target": "target", "fulfill": { "state": { "component[1].style.color": "{{$self.value === '123'?'red':'blue'}}" //Any level attribute supports expressions, and the key is a support path Expression, can achieve precise manipulation of attributes } } } }, "target": { "type": "string", "x-component": "Input" } } } ``` Writing method two, operating the Schema protocol ```json { "type": "object", "properties": { "source": { "type": "string", "x-component": "Input", "x-reactions": { "target": "target", "fulfill": { "schema": { "x-component-props.style.color": "{{$self.value === '123'?'red':'blue'}}" //Any level of property supports expressions, and the key is supported Path expression, can achieve precise operation properties } } } }, "target": { "type": "string", "x-component": "Input" } } } ``` ### Slot #### Description Mark this node as a Slot node, which will be skipped in the normal rendering process. Use `target` to specify the target property for rendering this node, which must be a sibling property at the same level. You can use `isRenderProp` to specify that this node is passed in the form of the renderProp function. When `isRenderProp` is `true`, the renderProp function’s argument list can be accessed within the Slot through `$slotArgs`. #### Signature ```ts type Slot = { //Slot target: Specify the target property for rendering this node, which must be a sibling property at the same level. target: string // 'some-sibling-node.x-component-props.xxx' or 'some-sibling-node.x-decorator-props.xxx' //whether it is a renderProp Slot isRenderProp?: boolean } ``` #### Example **ReactNode Prop** Reference [SchemaField](https://react.formilyjs.org/api/components/schema-field#json-schema-reactnode-prop-use-case-x-slot-node) ```json { "type": "object", "properties": { "search_icon": { "x-slot-node": { "target": "button.x-component-props.icon" //Specify to render the search_icon node as a slot into the icon prop of the Button component. }, "x-component": "SearchOutlined", "x-component-props": { "data-testid": "icon" } }, "button": { "type": "string", "x-component": "Button", "x-component-props": { "data-testid": "button" } } } } ``` **RenderProp** Reference [SchemaField](https://react.formilyjs.org/api/components/schema-field#json-schema-render-prop-use-case-x-slot-node--isrenderprop) ```json { "type": "object", "properties": { "dollar_icon": { "x-slot-node": { "target": "rate.x-component-props.character", //Specify to render the dollar_icon node as a slot into the character prop of the Rate component. "isRenderProp": true //The character prop accepts a renderProp function. Specify the Slot as a renderProp to take control of the rendering of the rating icons. }, "x-component": "DollarOutlined", "x-component-props": { "data-testid": "icon", "rotate": "{{$slotArgs[0].value * 45}}", //When isRenderProp is true, the renderProp function’s argument list can be accessed within the Slot through $slotArgs. "style": { "fontSize": "50px" } } }, "rate": { "x-component": "Rate" } } } ``` ## Built-in expression scope Built-in expression scope is mainly used to realize various linkage relationships in expressions ### $self Represents the current field instance, can be used in ordinary attribute expressions, and can also be used in x-reactions ### $values Represents the top-level form data, which can be used in ordinary attribute expressions, and can also be used in x-reactions ### $form Represents the current Form instance, which can be used in ordinary attribute expressions, and can also be used in x-reactions ### $observable It is used to create reactive objects in the same way as observable ### $memo Used to create persistent reference data in the same way as autorun.memo ### $effect The timing of the next microtask in response to autorun's first execution and the dispose in response to autorun are used in the same way as autorun.effect ### $dependencies It can only be consumed by expressions in x-reactions, corresponding to the dependencies defined by x-reactions, and the sequence of the array is the same ### $deps It can only be consumed by expressions in x-reactions, corresponding to the dependencies defined by x-reactions, and the sequence of the array is the same ### $target Can only be consumed in expressions in x-reactions, representing the target field of active mode ### $slotArgs Can only be consumed in slot node. When slot used as render prop, $slotArgs can access render function arguments array ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormItem.md: -------------------------------------------------------------------------------- ```markdown # FormItem > The brand-new FormItem component, compared to Antd's FormItem, it supports more functions. At the same time, it is positioned as a pure style component and does not manage the state of the form, so it will be lighter and more convenient for customization ## Markup Schema example ```tsx import React from 'react' import { Input, Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, Select, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="input box" x-decorator="FormItem" x-component="Input" required /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { style: { width: 240, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="input" title="input box" required decorator={[FormItem]} component={[ Input, { style: { width: 240, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Commonly used attribute cases ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Display when label is empty' }} /> <SchemaField.String x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.String title="" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'colon' }} /> <SchemaField.String title="default" x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="no colon (colon=false)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ colon: false, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Fixed width settings' }} /> <SchemaField.String title="Fixed label width (labelWidth)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.String title="Fixed label width (labelWidth) overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow" description="description description" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, tooltip: 'Prompt Tip', tooltipLayout: 'text', }} /> <SchemaField.String title="Fixed label width (labelWidth) newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline" description="description description" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelWrap: true, tooltip: 'Prompt Tip', }} /> <SchemaField.String title="fixed content width (wrapperWidth)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 300, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Alignment settings' }} /> <SchemaField.String title="labelLeft Alignment(labelAlign=left)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelAlign: 'left', }} /> <SchemaField.String title="label right alignment (labelAlign=right default)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelAlign: 'right', }} /> <SchemaField.String title="Content left aligned (wrapperAlign=left default)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 240, wrapperAlign: 'left', }} /> <SchemaField.String title="Content align right (wrapperAlign=right)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 240, wrapperAlign: 'right', }} /> <SchemaField.String title="tooltip" x-decorator="FormItem" x-component="Input" x-decorator-props={{ tooltip: 'tooltip', }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Is it full?' }} /> <SchemaField.String title="The default is not full (fullness=false)" x-decorator="FormItem" x-component="Select" /> <SchemaField.String title="Fullness(fullness=true)" x-decorator="FormItem" x-component="Select" x-decorator-props={{ fullness: true, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'auxiliary information' }} /> <SchemaField.String title="Required asterisk" x-decorator="FormItem" x-component="Input" x-decorator-props={{ asterisk: true, labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="prefix" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonBefore: 'addonBefore', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="suffix" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'addonAfter', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="Help information feedbackText" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackText: 'feedbackText', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="extra information extra" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackText: 'feedbackText', extra: 'extra', labelCol: 6, wrapperCol: 10, }} /> </SchemaField> </FormProvider> ) } ``` ## Required style ```tsx import React, { useState } from 'react' import { Input, FormItem, FormLayout } from '@formily/antd' import { Radio } from 'antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => { const [requiredMark, setRequiredMark] = useState(true) return ( <div> <p> Required Mark: <Radio.Group value={requiredMark} onChange={(e) => setRequiredMark(e.target.value)} > <Radio.Button value="optional">optional</Radio.Button> <Radio.Button value={true}>true</Radio.Button> <Radio.Button value={false}>false</Radio.Button> </Radio.Group> </p> <FormProvider form={form}> <FormLayout requiredMark={requiredMark}> <SchemaField> <SchemaField.String title="I am Required" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="I am Optional" x-decorator="FormItem" x-component="Input" /> <SchemaField.String default="When the field is not editable, always hide the required/optional flag" x-editable={false} x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="I am Required" required default="Not editable" x-editable={false} x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="I am Optional" default="Not editable" x-editable={false} x-decorator="FormItem" x-component="Input" /> </SchemaField> </FormLayout> </FormProvider> </div> ) } ``` ## Borderless case Set to remove the component border ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required x-decorator-props={{ bordered: false, }} /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required x-decorator-props={{ bordered: false, }} /> </SchemaField> </FormProvider> ) } ``` ## Embedded mode case Set the form component to inline mode ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required x-decorator-props={{ inset: true, }} /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required x-decorator-props={{ inset: false, }} /> </SchemaField> </FormProvider> ) } ``` ## Feedback Customization Case The button for specifying feedback can be passed in through `feedbackIcon` ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, TimePicker, FormItem, FormLayout, NumberPicker, Switch, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { CheckCircleFilled, LoadingOutlined } from '@ant-design/icons' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, TimePicker, NumberPicker, Switch, Radio, FormItem, Title, FormLayout, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String title="error status (feedbackStatus=error)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'error', }} /> <SchemaField.String title="Warning Status(feedbackStatus=warning)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'warning', }} /> <SchemaField.String title="Success Status (feedbackStatus=success)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="Loading Status(feedbackStatus=pending)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'pending', feedbackIcon: <LoadingOutlined style={{ color: '#1890ff' }} />, }} /> <SchemaField.String title="Status border style disabled(feedbackStatus=error)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ enableOutlineFeedback: false, feedbackStatus: 'error', }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Layout of feedback information' }} /> <SchemaField.String title="Compact mode required" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ feedbackLayout: 'terse', }} /> <SchemaField.String title="Compact mode has feedback(feedbackLayout=terse)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'terse', }} /> <SchemaField.String title="Compact mode without feedback(feedbackLayout=terse)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackLayout: 'terse', }} /> <SchemaField.String title="loose mode (feedbackLayout=loose)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'loose', }} /> <SchemaField.String title="Popup Mode (feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'warning', feedbackText: 'warning message', feedbackLayout: 'popover', }} /> <SchemaField.String title="Popup Mode (feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'popover', }} /> <SchemaField.String title="Popup Mode (feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'success', feedbackText: 'success message', feedbackLayout: 'popover', }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Component adaptation' }} /> <SchemaField.Void x-component="FormLayout" x-component-props={{ layout: 'vertical' }} > <SchemaField.String title="Select" x-decorator="FormItem" x-component="Select" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker" x-decorator="FormItem" x-component="DatePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.RangePicker" x-decorator="FormItem" x-component="DatePicker.RangePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.YearPicker" x-decorator="FormItem" x-component="DatePicker.YearPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.MonthPicker" x-decorator="FormItem" x-component="DatePicker.MonthPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.TimePicker" x-decorator="FormItem" x-component="TimePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="Cascader" x-decorator="FormItem" x-component="Cascader" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> </SchemaField.Void> </SchemaField> </FormProvider> ) } ``` ## Size control case ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/antd' import { createForm, onFieldChange } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Div = (props) => <div {...props} /> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Div, }, }) const form = createForm({ values: { size: 'default', }, effects: () => { onFieldChange('size', ['value'], (field, form) => { form.setFieldState('sizeWrap.*', (state) => { if (state.decorator[1]) { state.decorator[1].size = field.value } }) }) }, }) export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="size" title="Radio.Group" x-decorator="FormItem" x-component="Radio.Group" enum={[ { value: 'small', label: 'Small' }, { value: 'default', label: 'Default' }, { value: 'large', label: 'Large' }, ]} /> <SchemaField.Void name="sizeWrap" x-component="Div"> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required /> <SchemaField.String name="select1" title="Multiple Select" x-decorator="FormItem" x-component="Select" enum={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} x-component-props={{ mode: 'multiple', placeholder: 'Please choose', }} required /> <SchemaField.String name="select2" title="Select" x-decorator="FormItem" x-component="Select" enum={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} x-component-props={{ placeholder: 'Please choose', }} required /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required /> </SchemaField.Void> </SchemaField> </FormProvider> ) } ``` ## API ### FormItem | Property name | Type | Description | Default value | | --------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | | label | ReactNode | label | - | | style | CSSProperties | Style | - | | labelStyle | CSSProperties | Label style | - | | wrapperStyle | CSSProperties | Component container style | - | | className | string | Component style class name | - | | colon | boolean | colon | true | | tooltip | ReactNode | Question mark prompt | - | | tooltipLayout | `"icon" \| "text"` | Ask the prompt layout | `"icon"` | | tooltipIcon | ReactNode | Ask the prompt icon | `?` | | labelAlign | `"left"` \| `"right"` | Label text alignment | `"right"` | | labelWrap | boolean | Label change, otherwise an ellipsis appears, hover has tooltip | false | | labelWidth | `number \| string` | Label fixed width | - | | wrapperWidth | `number \| string` | Content fixed width | - | | labelCol | number | The number of columns occupied by the label grid, and the number of content columns add up to 24 | - | | wrapperCol | number | The number of columns occupied by the content grid, and the number of label columns add up to 24 | - | | wrapperAlign | `"left"` \| `"right"` | Content text alignment | `"left"` | | wrapperWrap | boolean | Change the content, otherwise an ellipsis appears, and hover has tooltip | false | | fullness | boolean | fullness | false | | addonBefore | ReactNode | Prefix content | - | | addonAfter | ReactNode | Suffix content | - | | size | `"small"` \| `"default"` \| `"large"` | size | - | | inset | boolean | Is it an inline layout | false | | extra | ReactNode | Extended description script | - | | feedbackText | ReactNode | Feedback Case | - | | feedbackLayout | `"loose"` \| `"terse"` \| `"popover" \| "none"` | Feedback layout | - | | feedbackStatus | `"error"` \| `"warning"` \| `"success"` \| `"pending"` | Feedback layout | - | | feedbackIcon | ReactNode | Feedback icon | - | | enableOutlineFeedback | boolean | Enable the border color style of the abnormal state, it is recommended to turn off this item when there is a sub-form in the custom component | true | | getPopupContainer | function(triggerNode) | when `feedbackLayout` is popover, The DOM container of the tip, the default behavior is to create a div element in body | () => document.body | | asterisk | boolean | Asterisk reminder | - | | gridSpan | number | Grid layout occupies width | - | | bordered | boolean | Is there a border | - | ### FormItem.BaseItem Pure style components, the properties are the same as FormItem, and Formily Core does not do state bridging. It is mainly used for scenarios that need to rely on the style layout capabilities of FormItem but do not want to access the Field state. ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/form.spec.ts: -------------------------------------------------------------------------------- ```typescript import { createForm } from '../' import { onFieldValidateStart, onFieldValueChange, onFormInitialValuesChange, onFormValuesChange, } from '../effects' import { attach, sleep } from './shared' import { LifeCycleTypes } from '../types' import { observable, batch } from '@formily/reactive' test('create form', () => { const form = attach(createForm()) expect(form).not.toBeUndefined() }) test('createField/createArrayField/createObjectField/createVoidField', () => { const form = attach(createForm()) const normal = attach( form.createField({ name: 'normal', basePath: 'parent', }) ) const normal2 = attach( form.createField({ name: 'normal', basePath: 'parent', }) ) const array_ = attach( form.createArrayField({ name: 'array', basePath: 'parent' }) ) const array2_ = attach( form.createArrayField({ name: 'array', basePath: 'parent' }) ) const object_ = attach( form.createObjectField({ name: 'object', basePath: 'parent' }) ) const object2_ = attach( form.createObjectField({ name: 'object', basePath: 'parent' }) ) const void_ = attach( form.createVoidField({ name: 'void', basePath: 'parent' }) ) const void2_ = attach( form.createVoidField({ name: 'void', basePath: 'parent' }) ) const children_ = attach( form.createField({ name: 'children', basePath: 'parent.void' }) ) expect(normal).not.toBeUndefined() expect(array_).not.toBeUndefined() expect(object_).not.toBeUndefined() expect(void_).not.toBeUndefined() expect(normal.address.toString()).toEqual('parent.normal') expect(normal.path.toString()).toEqual('parent.normal') expect(array_.address.toString()).toEqual('parent.array') expect(array_.path.toString()).toEqual('parent.array') expect(object_.address.toString()).toEqual('parent.object') expect(object_.path.toString()).toEqual('parent.object') expect(void_.address.toString()).toEqual('parent.void') expect(void_.path.toString()).toEqual('parent.void') expect(children_.address.toString()).toEqual('parent.void.children') expect(children_.path.toString()).toEqual('parent.children') expect(form.createField({ name: '' })).toBeUndefined() expect(form.createArrayField({ name: '' })).toBeUndefined() expect(form.createObjectField({ name: '' })).toBeUndefined() expect(form.createVoidField({ name: '' })).toBeUndefined() expect(array_ === array2_).toBeTruthy() expect(object_ === object2_).toBeTruthy() expect(void_ === void2_).toBeTruthy() expect(normal === normal2).toBeTruthy() }) test('setValues/setInitialValues', () => { const form = attach(createForm()) form.setValues({ aa: 123, cc: { kk: 321, }, }) const field = attach( form.createField({ name: 'cc.mm', initialValue: 'ooo', }) ) const field2 = attach( form.createField({ name: 'cc.pp', initialValue: 'www', }) ) expect(form.values.aa).toEqual(123) expect(form.values.cc.kk).toEqual(321) expect(form.values.cc.mm).toEqual('ooo') expect(form.initialValues.cc.mm).toEqual('ooo') expect(form.values.cc.pp).toEqual('www') expect(form.initialValues.cc.pp).toEqual('www') expect(field.value).toEqual('ooo') expect(field2.value).toEqual('www') form.setInitialValues({ bb: '123', cc: { dd: 'xxx', pp: 'www2', }, }) expect(form.values.aa).toEqual(123) expect(form.values.bb).toEqual('123') expect(form.values.cc.kk).toEqual(321) expect(form.values.cc.dd).toEqual('xxx') expect(form.initialValues.bb).toEqual('123') expect(form.initialValues.cc.kk).toBeUndefined() expect(form.initialValues.cc.dd).toEqual('xxx') expect(form.values.cc.mm).toEqual('ooo') expect(form.initialValues.cc.mm).toEqual('ooo') expect(field.value).toEqual('ooo') expect(form.values.cc.pp).toEqual('www2') expect(form.initialValues.cc.pp).toEqual('www2') expect(field2.value).toEqual('www2') form.setInitialValues({}, 'overwrite') expect(form.initialValues?.cc?.pp).toBeUndefined() form.setValues({}, 'overwrite') expect(form.values.aa).toBeUndefined() form.setInitialValues({ aa: { bb: [{ cc: 123 }] } }, 'deepMerge') expect(form.values).toEqual({ aa: { bb: [{ cc: 123 }] } }) form.setValues({ bb: { bb: [{ cc: 123 }] } }, 'deepMerge') expect(form.values).toEqual({ aa: { bb: [{ cc: 123 }] }, bb: { bb: [{ cc: 123 }] }, }) form.setInitialValues({ aa: [123] }, 'shallowMerge') expect(form.values).toEqual({ aa: [123], bb: { bb: [{ cc: 123 }] }, }) form.setValues({ bb: [123] }, 'shallowMerge') expect(form.values).toEqual({ aa: [123], bb: [123], }) }) test('no field initialValues merge', () => { const form = attach( createForm<any>({ values: { aa: '123', }, initialValues: { aa: '333', bb: '321', }, }) ) expect(form.values).toEqual({ aa: '123', bb: '321', }) }) test('setLoading', async () => { const form = attach(createForm()) expect(form.loading).toBeFalsy() form.setLoading(true) await sleep(100) expect(form.loading).toBeTruthy() }) test('setValues with null', () => { const form = attach(createForm()) form.setInitialValues({ 'object-1': { 'array-1': null, }, 'object-2': { 'array-2': null, }, }) form.setValues({ 'object-1': { 'array-1': null, }, 'object-2': { 'array-2': null, }, }) expect(form.values).toEqual({ 'object-1': { 'array-1': null, }, 'object-2': { 'array-2': null, }, }) }) test('observable values/initialValues', () => { const values: any = observable({ aa: 123, bb: 321, }) const initialValues: any = observable({ cc: 321, dd: 444, }) const form = attach( createForm({ values, initialValues, }) ) batch(() => { values.kk = 321 }) expect(form.values.kk).toEqual(321) }) test('deleteValuesIn/deleteInitialValuesIn', () => { const form = attach( createForm<{ aa?: number bb?: number }>({ values: { aa: 123, }, initialValues: { bb: 123, }, }) ) expect(form.values.aa).toEqual(123) expect(form.values.bb).toEqual(123) form.deleteValuesIn('aa') form.deleteInitialValuesIn('bb') expect(form.existValuesIn('aa')).toBeFalsy() expect(form.existInitialValuesIn('bb')).toBeFalsy() }) test('setSubmitting/setValidating', async () => { const form = attach(createForm()) form.setSubmitting(true) expect(form.submitting).toBeFalsy() await sleep() expect(form.submitting).toBeTruthy() form.setSubmitting(false) expect(form.submitting).toBeFalsy() form.setValidating(true) expect(form.validating).toBeFalsy() await sleep() expect(form.validating).toBeTruthy() form.setValidating(false) expect(form.validating).toBeFalsy() }) test('setEffects/addEffects/removeEffects', () => { const form = attach(createForm()) const valueChange = jest.fn() const valueChange2 = jest.fn() form.addEffects('e1', () => { onFieldValueChange('aa', valueChange) }) const field = attach( form.createField({ name: 'aa', }) ) field.setValue('123') expect(valueChange).toBeCalledTimes(1) form.removeEffects('e1') field.setValue('321') expect(valueChange).toBeCalledTimes(1) form.addEffects('e2', () => { onFieldValueChange('aa', valueChange) }) field.setValue('444') expect(valueChange).toBeCalledTimes(2) form.setEffects(() => { onFieldValueChange('aa', valueChange2) }) field.setValue('555') expect(valueChange).toBeCalledTimes(3) expect(valueChange2).toBeCalledTimes(1) }) test('query', () => { const form = attach(createForm()) attach( form.createObjectField({ name: 'object', }) ) attach( form.createVoidField({ name: 'void', basePath: 'object', }) ) attach( form.createField({ name: 'normal', basePath: 'object.void', }) ) attach( form.createArrayField({ name: 'array', }) ) expect(form.query('object').take()).not.toBeUndefined() expect(form.query('object.void').take()).not.toBeUndefined() expect(form.query('object.void.normal').take()).not.toBeUndefined() expect(form.query('object.normal').take()).not.toBeUndefined() expect(form.query('object.*').map((field) => field.path.toString())).toEqual([ 'object.void', 'object.normal', ]) expect(form.query('*').map((field) => field.path.toString())).toEqual([ 'object', 'object.void', 'object.normal', 'array', ]) expect(form.query('array').take()).not.toBeUndefined() expect(form.query('*').take()).not.toBeUndefined() expect(form.query('*(oo)').take()).toBeUndefined() expect(form.query('*(oo)').map()).toEqual([]) expect(form.query('object.void').get('value')).toBeUndefined() expect(form.query('object.void').get('initialValue')).toBeUndefined() expect(form.query('object.void').get('inputValue')).toBeUndefined() expect(form.query('array').get('value')).toEqual([]) expect(form.query('array').get('initialValue')).toBeUndefined() expect(form.query('array').get('inputValue')).toBeNull() form.setFieldState('array', (state) => { state.value = [111] state.initialValue = [111] state.inputValue = [111] }) expect(form.query('array').get('value')).toEqual([111]) expect(form.query('array').get('initialValue')).toEqual([111]) expect(form.query('array').get('inputValue')).toEqual([111]) expect(form.query('array').getIn('inputValue')).toEqual([111]) expect(form.query('opo').get('value')).toBeUndefined() expect(form.query('opo').getIn('value')).toBeUndefined() expect(form.query('opo').get('initialValue')).toBeUndefined() expect(form.query('opo').get('inputValue')).toBeUndefined() }) test('notify/subscribe/unsubscribe', () => { const form = attach(createForm()) const subscribe = jest.fn() const id = form.subscribe(subscribe) expect(subscribe).toBeCalledTimes(0) form.setInitialValues({ aa: 123 }) expect(subscribe).toBeCalledTimes(2) expect(form.values).toEqual({ aa: 123 }) form.notify(LifeCycleTypes.ON_FORM_SUBMIT) expect(subscribe).toBeCalledTimes(3) form.unsubscribe(id) form.notify(LifeCycleTypes.ON_FORM_SUBMIT) expect(subscribe).toBeCalledTimes(3) }) test('setState/getState/setFormState/getFormState/setFieldState/getFieldState', () => { const form = attach(createForm()) const state = form.getState() form.setState((state) => { state.pattern = 'disabled' state.values = { aa: 123 } }) expect(form.pattern).toEqual('disabled') expect(form.disabled).toBeTruthy() expect(form.values.aa).toEqual(123) form.setState(state) expect(form.pattern).toEqual('editable') expect(form.disabled).toBeFalsy() expect(form.values.aa).toBeUndefined() form.setFormState((state) => { state.pattern = 'readOnly' state.values = { bb: 321 } }) expect(form.pattern).toEqual('readOnly') expect(form.disabled).toBeFalsy() expect(form.readOnly).toBeTruthy() expect(form.values.aa).toBeUndefined() expect(form.values.bb).toEqual(321) form.setFormState(state) expect(form.pattern).toEqual('editable') expect(form.disabled).toBeFalsy() expect(form.readOnly).toBeFalsy() expect(form.values.aa).toBeUndefined() expect(form.values.bb).toBeUndefined() attach( form.createField({ name: 'aa', }) ) const fieldState = form.getFieldState('aa') form.setFieldState('aa', (state) => { state.title = 'AA' state.description = 'This is AA' state.value = '123' }) expect(form.getFieldState('aa', (state) => state.title)).toEqual('AA') expect(form.getFieldState('aa', (state) => state.description)).toEqual( 'This is AA' ) expect(form.getFieldState('aa', (state) => state.value)).toEqual('123') form.setFieldState('aa', fieldState) expect(form.getFieldState('aa', (state) => state.title)).toBeUndefined() expect(form.getFieldState('aa', (state) => state.description)).toBeUndefined() expect(form.getFieldState('aa', (state) => state.value)).toBeUndefined() form.setState((state) => { state.display = 'none' }) expect(form.getFieldState('aa', (state) => state.visible)).toBeFalsy() const update = (value: any) => (state: any) => { state.value = value } const update2 = (state: any) => { state.value = 123 } form.setFieldState('kk', update(123)) form.setFieldState('kk', update(321)) form.setFieldState('oo', update2) form.setFieldState('oo', update2) const oo = attach( form.createField({ name: 'oo', }) ) const kk = attach( form.createField({ name: 'kk', }) ) expect(oo.value).toBeUndefined() expect(kk.value).toBeUndefined() }) test('validate/valid/invalid/errors/warnings/successes/clearErrors/clearWarnings/clearSuccesses/queryFeedbacks', async () => { const form = attach(createForm()) const aa = attach( form.createField({ name: 'aa', required: true, validator(value) { if (value == '123') { return { type: 'success', message: 'success', } } else if (value == '321') { return { type: 'warning', message: 'warning', } } else if (value == '111') { return 'error' } }, }) ) const bb = attach( form.createField({ name: 'bb', required: true, }) ) attach( form.createVoidField({ name: 'cc', }) ) try { await form.validate() } catch {} expect(form.invalid).toBeTruthy() expect(form.valid).toBeFalsy() expect(form.errors).toEqual([ { type: 'error', address: 'aa', path: 'aa', code: 'ValidateError', triggerType: 'onInput', messages: ['The field value is required'], }, { type: 'error', address: 'bb', path: 'bb', code: 'ValidateError', triggerType: 'onInput', messages: ['The field value is required'], }, ]) await aa.onInput('123') expect(form.errors).toEqual([ { type: 'error', address: 'bb', path: 'bb', code: 'ValidateError', triggerType: 'onInput', messages: ['The field value is required'], }, ]) expect(form.successes).toEqual([ { type: 'success', address: 'aa', path: 'aa', code: 'ValidateSuccess', triggerType: 'onInput', messages: ['success'], }, ]) await aa.onInput('321') expect(form.errors).toEqual([ { type: 'error', address: 'bb', path: 'bb', code: 'ValidateError', triggerType: 'onInput', messages: ['The field value is required'], }, ]) expect(form.warnings).toEqual([ { type: 'warning', address: 'aa', path: 'aa', code: 'ValidateWarning', triggerType: 'onInput', messages: ['warning'], }, ]) await aa.onInput('111') expect(form.errors).toEqual([ { type: 'error', address: 'aa', path: 'aa', code: 'ValidateError', triggerType: 'onInput', messages: ['error'], }, { type: 'error', address: 'bb', path: 'bb', code: 'ValidateError', triggerType: 'onInput', messages: ['The field value is required'], }, ]) await aa.onInput('yes') await bb.onInput('yes') await form.validate() expect(form.invalid).toBeFalsy() expect(form.valid).toBeTruthy() expect(form.errors).toEqual([]) expect(form.successes).toEqual([]) expect(form.warnings).toEqual([]) await aa.onInput('') await bb.onInput('') try { await form.validate() } catch {} expect(form.errors).toEqual([ { type: 'error', address: 'aa', path: 'aa', code: 'ValidateError', triggerType: 'onInput', messages: ['The field value is required'], }, { type: 'error', address: 'bb', path: 'bb', code: 'ValidateError', triggerType: 'onInput', messages: ['The field value is required'], }, ]) form.clearErrors('aa') expect(form.errors).toEqual([ { type: 'error', address: 'bb', path: 'bb', code: 'ValidateError', triggerType: 'onInput', messages: ['The field value is required'], }, ]) form.clearErrors('*') expect(form.errors).toEqual([]) await aa.onInput('123') expect(form.errors).toEqual([]) expect(form.successes).toEqual([ { type: 'success', address: 'aa', path: 'aa', code: 'ValidateSuccess', triggerType: 'onInput', messages: ['success'], }, ]) form.clearSuccesses('aa') expect(form.successes).toEqual([]) await aa.onInput('321') expect(form.errors).toEqual([]) expect(form.successes).toEqual([]) expect(form.warnings).toEqual([ { type: 'warning', address: 'aa', path: 'aa', code: 'ValidateWarning', triggerType: 'onInput', messages: ['warning'], }, ]) form.clearWarnings('*') expect(form.errors).toEqual([]) expect(form.successes).toEqual([]) expect(form.warnings).toEqual([]) await aa.onInput('123') await bb.onInput('') expect( form.queryFeedbacks({ type: 'error', }).length ).toEqual(1) expect( form.queryFeedbacks({ type: 'success', }).length ).toEqual(1) expect( form.queryFeedbacks({ code: 'ValidateError', }).length ).toEqual(1) expect( form.queryFeedbacks({ code: 'ValidateSuccess', }).length ).toEqual(1) expect( form.queryFeedbacks({ code: 'EffectError', }).length ).toEqual(0) expect( form.queryFeedbacks({ code: 'EffectSuccess', }).length ).toEqual(0) expect( form.queryFeedbacks({ path: 'aa', }).length ).toEqual(1) expect( form.queryFeedbacks({ path: 'bb', }).length ).toEqual(1) expect( form.queryFeedbacks({ address: 'aa', }).length ).toEqual(1) expect( form.queryFeedbacks({ address: 'bb', }).length ).toEqual(1) aa.setValue('') bb.setValue('') form.clearErrors() form.clearSuccesses() form.clearWarnings() try { await form.validate('aa') } catch {} expect( form.queryFeedbacks({ type: 'error', }).length ).toEqual(1) try { await form.validate('*') } catch {} expect( form.queryFeedbacks({ type: 'error', }).length ).toEqual(2) }) test('setPattern/pattern/editable/readOnly/disabled/readPretty', () => { const form = attach( createForm({ pattern: 'disabled', }) ) const field = attach( form.createField({ name: 'aa', }) ) expect(form.pattern).toEqual('disabled') expect(form.disabled).toBeTruthy() expect(field.pattern).toEqual('disabled') expect(field.disabled).toBeTruthy() form.setPattern('readOnly') expect(form.pattern).toEqual('readOnly') expect(form.readOnly).toBeTruthy() expect(field.pattern).toEqual('readOnly') expect(field.readOnly).toBeTruthy() form.setPattern('readPretty') expect(form.pattern).toEqual('readPretty') expect(form.readPretty).toBeTruthy() expect(field.pattern).toEqual('readPretty') expect(field.readPretty).toBeTruthy() const form2 = attach( createForm({ editable: false, }) ) expect(form2.pattern).toEqual('readPretty') expect(form2.readPretty).toBeTruthy() const form3 = attach( createForm({ disabled: true, }) ) expect(form3.pattern).toEqual('disabled') expect(form3.disabled).toBeTruthy() const form4 = attach( createForm({ readOnly: true, }) ) expect(form4.pattern).toEqual('readOnly') expect(form4.readOnly).toBeTruthy() const form5 = attach( createForm({ readPretty: true, }) ) expect(form5.pattern).toEqual('readPretty') expect(form5.readPretty).toBeTruthy() }) test('setDisplay/display/visible/hidden', () => { const form = attach( createForm({ display: 'hidden', }) ) const field = attach( form.createField({ name: 'aa', }) ) expect(form.display).toEqual('hidden') expect(form.hidden).toBeTruthy() expect(field.display).toEqual('hidden') expect(field.hidden).toBeTruthy() form.setDisplay('visible') expect(form.display).toEqual('visible') expect(form.visible).toBeTruthy() expect(field.display).toEqual('visible') expect(field.visible).toBeTruthy() form.setDisplay('none') expect(form.display).toEqual('none') expect(form.visible).toBeFalsy() expect(field.display).toEqual('none') expect(field.visible).toBeFalsy() const form2 = attach( createForm({ hidden: true, }) ) expect(form2.display).toEqual('hidden') expect(form2.hidden).toBeTruthy() expect(form2.visible).toBeFalsy() const form3 = attach( createForm({ visible: false, }) ) expect(form3.display).toEqual('none') expect(form3.visible).toBeFalsy() }) test('submit', async () => { const form = attach(createForm()) const onSubmit = jest.fn() const field = attach( form.createField({ name: 'aa', required: true, }) ) let errors1: Error try { await form.submit(onSubmit) } catch (e) { errors1 = e } expect(errors1).not.toBeUndefined() expect(onSubmit).toBeCalledTimes(0) field.onInput('123') await form.submit(onSubmit) expect(onSubmit).toBeCalledTimes(1) let errors2: Error try { await form.submit(() => { throw new Error('xxx') }) } catch (e) { errors2 = e } expect(errors2).not.toBeUndefined() expect(form.valid).toBeTruthy() }) test('reset', async () => { const form = attach( createForm<{ aa?: number bb?: number }>({ values: { bb: 123, }, initialValues: { aa: 123, }, }) ) const field = attach( form.createField({ name: 'aa', required: true, }) ) const field2 = attach( form.createField({ name: 'bb', required: true, }) ) attach( form.createVoidField({ name: 'cc', }) ) expect(field.value).toEqual(123) expect(field2.value).toEqual(123) expect(form.values.aa).toEqual(123) expect(form.values.bb).toEqual(123) field.onInput('xxxxx') expect(form.values.aa).toEqual('xxxxx') try { await form.reset() } catch {} expect(form.valid).toBeTruthy() expect(form.values.aa).toEqual(123) expect(field.value).toEqual(123) expect(form.values.bb).toBeUndefined() expect(field2.value).toBeUndefined() field.onInput('aaa') field2.onInput('bbb') expect(form.valid).toBeTruthy() expect(form.values.aa).toEqual('aaa') expect(field.value).toEqual('aaa') expect(form.values.bb).toEqual('bbb') expect(field2.value).toEqual('bbb') try { await form.reset('*', { validate: true, }) } catch {} expect(form.valid).toBeFalsy() expect(form.values.aa).toEqual(123) expect(field.value).toEqual(123) expect(form.values.bb).toBeUndefined() expect(field2.value).toBeUndefined() field.onInput('aaa') field2.onInput('bbb') try { await form.reset('*', { forceClear: true, }) } catch {} expect(form.valid).toBeTruthy() expect(form.values.aa).toBeUndefined() expect(field.value).toBeUndefined() expect(form.values.bb).toBeUndefined() expect(field2.value).toBeUndefined() field.onInput('aaa') field2.onInput('bbb') try { await form.reset('aa', { forceClear: true, }) } catch {} expect(form.valid).toBeTruthy() expect(form.values.aa).toBeUndefined() expect(field.value).toBeUndefined() expect(form.values.bb).toEqual('bbb') expect(field2.value).toEqual('bbb') }) test('devtools', () => { // @ts-ignore window['__FORMILY_DEV_TOOLS_HOOK__'] = { inject() {}, unmount() {}, } const form = attach(createForm()) form.onUnmount() }) test('reset array field', async () => { const form = attach( createForm({ values: { array: [{ value: 123 }], }, }) ) attach( form.createArrayField({ name: 'array', required: true, }) ) expect(form.values).toEqual({ array: [{ value: 123 }], }) await form.reset('*', { forceClear: true, }) expect(form.values).toEqual({ array: [], }) }) test('reset object field', async () => { const form = attach( createForm({ values: { object: { value: 123 }, }, }) ) attach( form.createObjectField({ name: 'object', required: true, }) ) expect(form.values).toEqual({ object: { value: 123 }, }) await form.reset('*', { forceClear: true, }) expect(form.values).toEqual({ object: {}, }) }) test('initialValues merge values before create field', () => { const form = attach(createForm()) const array = attach( form.createArrayField({ name: 'array', }) ) form.values.array = [{ aa: '321' }] const arr_0_aa = attach( form.createField({ name: 'aa', basePath: 'array.0', initialValue: '123', }) ) expect(array.value).toEqual([{ aa: '321' }]) expect(arr_0_aa.value).toEqual('321') }) test('no patch with empty initialValues', () => { const form = attach( createForm({ values: { array: [1, 2, 3], }, }) ) attach( form.createObjectField({ name: 'array.0.1', }) ) expect(form.values).toEqual({ array: [1, 2, 3], }) }) test('initialValues merge values after create field', () => { const form = attach(createForm()) const aa = attach( form.createArrayField({ name: 'aa', initialValue: '111', }) ) const array = attach( form.createArrayField({ name: 'array', }) ) const arr_0_aa = attach( form.createField({ name: 'aa', basePath: 'array.0', initialValue: '123', }) ) form.values.aa = '222' form.values.array = [{ aa: '321' }] expect(array.value).toEqual([{ aa: '321' }]) expect(arr_0_aa.value).toEqual('321') expect(aa.value).toEqual('222') }) test('remove property of form values with undefined value', () => { const form = attach(createForm()) const field = attach( form.createField({ name: 'aaa', initialValue: 123, }) ) expect(form.values).toMatchObject({ aaa: 123 }) field.display = 'none' expect(form.values.hasOwnProperty('aaa')).toBeFalsy() field.display = 'visible' expect(form.values.hasOwnProperty('aaa')).toBeTruthy() field.setValue(undefined) expect(form.values.hasOwnProperty('aaa')).toBeTruthy() }) test('empty array initialValues', () => { const form = attach( createForm({ initialValues: { aa: [0], bb: [''], cc: [], dd: [null], ee: [undefined], }, }) ) form.createArrayField({ name: 'aa', }) form.createArrayField({ name: 'bb', }) form.createArrayField({ name: 'cc', }) form.createArrayField({ name: 'dd', }) form.createArrayField({ name: 'ee', }) expect(form.values.aa).toEqual([0]) expect(form.values.bb).toEqual(['']) expect(form.values.cc).toEqual([]) expect(form.values.dd).toEqual([null]) expect(form.values.ee).toEqual([undefined]) }) test('form lifecycle can be triggered after call form.setXXX', () => { let initialValuesTriggerNum = 0 let valuesTriggerNum = 0 const form = attach( createForm<{ aa?: number bb?: number }>({ initialValues: { aa: 1, }, values: { bb: 1, }, }) ) form.setEffects(() => { onFormInitialValuesChange(() => { initialValuesTriggerNum++ }) onFormValuesChange(() => { valuesTriggerNum++ }) }) expect(initialValuesTriggerNum).toEqual(0) expect(valuesTriggerNum).toEqual(0) form.initialValues.aa = 2 form.values.bb = 2 expect(initialValuesTriggerNum).toEqual(1) // initialValues 会通过 applyValuesPatch 改变 values,导致 onFormValuesChange 多触发一次 expect(valuesTriggerNum).toEqual(2) form.setInitialValues({ aa: 3 }) form.setValues({ bb: 3 }) expect(initialValuesTriggerNum).toEqual(2) expect(valuesTriggerNum).toEqual(4) // 测试 form.setXXX 之后还能正常触发:https://github.com/alibaba/formily/issues/1675 form.initialValues.aa = 4 form.values.bb = 4 expect(initialValuesTriggerNum).toEqual(3) expect(valuesTriggerNum).toEqual(6) }) test('form values change with array field(default value)', async () => { const handler = jest.fn() const form = attach( createForm({ effects() { onFormValuesChange(handler) }, }) ) const array = attach( form.createArrayField({ name: 'array', initialValue: [ { hello: 'world', }, ], }) ) await array.push({}) expect(handler).toBeCalledTimes(2) }) test('setValues deep merge', () => { const form = attach( createForm({ initialValues: { aa: { bb: 123, cc: 321, dd: [11, 22, 33], }, }, }) ) expect(form.values).toEqual({ aa: { bb: 123, cc: 321, dd: [11, 22, 33], }, }) form.setValues({ aa: { bb: '', cc: '', dd: [44, 55, 66], }, }) expect(form.values).toEqual({ aa: { bb: '', cc: '', dd: [44, 55, 66], }, }) }) test('exception validate', async () => { const form = attach(createForm()) attach( form.createField({ name: 'aa', validator() { throw new Error('runtime error') }, }) ) try { await form.validate() } catch {} expect(form.invalid).toBeTruthy() expect(form.validating).toBeFalsy() }) test('designable form', () => { const form = attach( createForm({ designable: true, }) ) attach( form.createField({ name: 'bb', initialValue: 123, }) ) attach( form.createField({ name: 'bb', initialValue: 321, }) ) attach( form.createField({ name: 'aa', value: 123, }) ) attach( form.createField({ name: 'aa', value: 321, }) ) expect(form.values.aa).toEqual(321) expect(form.initialValues.bb).toEqual(321) }) test('validate will skip display none', async () => { const validateA = jest.fn() const validateB = jest.fn() const form = attach( createForm({ effects() { onFieldValidateStart('aa', validateA) onFieldValidateStart('bb', validateB) }, }) ) const validator = jest.fn() const aa = attach( form.createField({ name: 'aa', validator() { validator() return 'error' }, }) ) const bb = attach( form.createField({ name: 'bb', validator() { validator() return 'error' }, }) ) try { await form.validate() } catch (e) { expect(e).toEqual([ { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'aa', path: 'aa', }, { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'bb', path: 'bb', }, ]) } expect(validateA).toBeCalledTimes(1) expect(validateB).toBeCalledTimes(1) expect(aa.invalid).toBeTruthy() expect(bb.invalid).toBeTruthy() expect(validator).toBeCalledTimes(2) aa.display = 'none' try { await form.validate() } catch (e) { expect(e).toEqual([ { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'bb', path: 'bb', }, ]) } expect(validateA).toBeCalledTimes(1) expect(validateB).toBeCalledTimes(2) expect(aa.invalid).toBeFalsy() expect(bb.invalid).toBeTruthy() expect(validator).toBeCalledTimes(3) bb.display = 'none' await form.validate() expect(validateA).toBeCalledTimes(1) expect(validateB).toBeCalledTimes(2) expect(aa.invalid).toBeFalsy() expect(bb.invalid).toBeFalsy() expect(validator).toBeCalledTimes(3) }) test('validate will skip unmounted', async () => { const validateA = jest.fn() const validateB = jest.fn() const form = attach( createForm({ effects() { onFieldValidateStart('aa', validateA) onFieldValidateStart('bb', validateB) }, }) ) const validator = jest.fn() const aa = attach( form.createField({ name: 'aa', validator() { validator() return 'error' }, }) ) const bb = attach( form.createField({ name: 'bb', validator() { validator() return 'error' }, }) ) try { await form.validate() } catch (e) { expect(e).toEqual([ { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'aa', path: 'aa', }, { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'bb', path: 'bb', }, ]) } expect(validateA).toBeCalledTimes(1) expect(validateB).toBeCalledTimes(1) expect(aa.invalid).toBeTruthy() expect(bb.invalid).toBeTruthy() expect(validator).toBeCalledTimes(2) aa.onUnmount() try { await form.validate() } catch (e) { expect(e).toEqual([ { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'aa', path: 'aa', }, { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'bb', path: 'bb', }, ]) } expect(validateA).toBeCalledTimes(2) expect(validateB).toBeCalledTimes(2) expect(aa.invalid).toBeTruthy() expect(bb.invalid).toBeTruthy() expect(validator).toBeCalledTimes(4) form.clearFormGraph('*(aa,bb)') await form.validate() expect(validateA).toBeCalledTimes(2) expect(validateB).toBeCalledTimes(2) expect(aa.invalid).toBeFalsy() expect(bb.invalid).toBeFalsy() expect(validator).toBeCalledTimes(4) }) test('validate will skip uneditable', async () => { const validateA = jest.fn() const validateB = jest.fn() const form = attach( createForm({ effects() { onFieldValidateStart('aa', validateA) onFieldValidateStart('bb', validateB) }, }) ) const validator = jest.fn() const aa = attach( form.createField({ name: 'aa', validator() { validator() return 'error' }, }) ) const bb = attach( form.createField({ name: 'bb', validator() { validator() return 'error' }, }) ) try { await form.validate() } catch (e) { expect(e).toEqual([ { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'aa', path: 'aa', }, { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'bb', path: 'bb', }, ]) } expect(validateA).toBeCalledTimes(1) expect(validateB).toBeCalledTimes(1) expect(aa.invalid).toBeTruthy() expect(bb.invalid).toBeTruthy() expect(validator).toBeCalledTimes(2) aa.editable = false try { await form.validate() } catch (e) { expect(e).toEqual([ { triggerType: 'onInput', type: 'error', code: 'ValidateError', messages: ['error'], address: 'bb', path: 'bb', }, ]) } expect(validateA).toBeCalledTimes(1) expect(validateB).toBeCalledTimes(2) expect(aa.invalid).toBeFalsy() expect(bb.invalid).toBeTruthy() expect(validator).toBeCalledTimes(3) bb.editable = false await form.validate() expect(validateA).toBeCalledTimes(1) expect(validateB).toBeCalledTimes(2) expect(aa.invalid).toBeFalsy() expect(bb.invalid).toBeFalsy() expect(validator).toBeCalledTimes(3) }) test('validator order with format', async () => { const form = attach(createForm()) attach( form.createField({ name: 'aa', required: true, validator: { format: 'url', message: 'custom', }, }) ) attach( form.createField({ name: 'bb', required: true, validator: (value) => { if (!value) return '' return value !== '111' ? 'custom' : '' }, }) ) const results = await form.submit<any[]>(() => {}).catch((e) => e) expect(results.map(({ messages }) => messages)).toEqual([ ['The field value is required'], ['The field value is required'], ]) }) test('form unmount can not effect field values', () => { const form = attach( createForm({ values: { aa: '123', }, }) ) attach( form.createField({ name: 'aa', }) ) expect(form.values.aa).toEqual('123') form.onUnmount() expect(form.values.aa).toEqual('123') }) test('form clearFormGraph need clear field values', () => { const form = attach( createForm({ values: { aa: '123', }, }) ) attach( form.createField({ name: 'aa', }) ) expect(form.values.aa).toEqual('123') form.clearFormGraph('*') expect(form.values.aa).toBeUndefined() }) test('form clearFormGraph not clear field values', () => { const form = attach( createForm({ values: { aa: '123', }, }) ) attach( form.createField({ name: 'aa', }) ) expect(form.values.aa).toEqual('123') form.clearFormGraph('*', false) expect(form.values.aa).toEqual('123') }) test('form values auto clean with visible false', () => { const form = attach( createForm({ initialValues: { aa: '123', bb: '321', cc: 'cc', }, }) ) attach( form.createField({ name: 'aa', }) ) attach( form.createField({ name: 'bb', reactions: (field) => { field.visible = form.values.aa === '1233' }, }) ) attach( form.createField({ name: 'cc', }) ) expect(form.values).toEqual({ aa: '123', cc: 'cc', }) }) test('form values auto clean with visible false in async setInitialValues', () => { const form = attach(createForm()) attach( form.createField({ name: 'aa', }) ) attach( form.createField({ name: 'bb', reactions: (field) => { field.visible = form.values.aa === '1233' }, }) ) attach( form.createField({ name: 'cc', }) ) form.setInitialValues({ aa: '123', bb: '321', cc: 'cc', }) expect(form.values).toEqual({ aa: '123', cc: 'cc', }) }) test('form values ref should not changed with setValues', () => { const form = attach( createForm({ values: { aa: '123', }, }) ) const values = form.values form.setValues({ bb: '321', }) expect(form.values === values).toBeTruthy() }) test('form initial values ref should not changed with setInitialValues', () => { const form = attach( createForm({ initialValues: { aa: '123', }, }) ) const values = form.initialValues form.setInitialValues({ bb: '321', }) expect(form.initialValues === values).toBeTruthy() }) test('form query undefined query should not throw error', () => { const form = attach(createForm()) ;(form.fields as any)['a'] = undefined expect(() => form.query('*').take()).not.toThrowError() expect(Object.keys(form.fields)).toEqual([]) }) ```