This is page 17 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/core/src/__tests__/void.spec.ts: -------------------------------------------------------------------------------- ```typescript import { createForm } from '../' import { attach } from './shared' test('create void field', () => { const form = attach(createForm()) const field = attach( form.createVoidField({ name: 'void', }) ) field.destroy() }) test('create void field props', () => { const form = attach(createForm()) const field1 = attach( form.createVoidField({ name: 'field1', title: 'Field 1', description: 'This is Field 1', }) ) expect(field1.title).toEqual('Field 1') expect(field1.description).toEqual('This is Field 1') const field2 = attach( form.createVoidField({ name: 'field2', disabled: true, hidden: true, }) ) expect(field2.pattern).toEqual('disabled') expect(field2.disabled).toBeTruthy() expect(field2.display).toEqual('hidden') expect(field2.hidden).toBeTruthy() const field3 = attach( form.createVoidField({ name: 'field3', readOnly: true, visible: false, }) ) expect(field3.pattern).toEqual('readOnly') expect(field3.readOnly).toBeTruthy() expect(field3.display).toEqual('none') expect(field3.visible).toBeFalsy() }) test('setComponent/setComponentProps', () => { const form = attach(createForm()) const field = attach( form.createVoidField({ name: 'aa', }) ) const component = () => null field.setComponent(component, { props: 123 }) expect(field.component[0]).toEqual(component) expect(field.component[1]).toEqual({ props: 123 }) field.setComponentProps({ hello: 'world', }) expect(field.component[1]).toEqual({ props: 123, hello: 'world' }) }) test('setTitle/setDescription', () => { const form = attach(createForm()) const aa = attach( form.createVoidField({ name: 'aa', }) ) aa.setTitle('AAA') aa.setDescription('This is AAA') expect(aa.title).toEqual('AAA') expect(aa.description).toEqual('This is AAA') }) test('setComponent/setComponentProps', () => { const component = () => null const form = attach(createForm()) const field = attach( form.createVoidField({ name: 'aa', }) ) field.setComponent(undefined, { props: 123 }) field.setComponent(component) expect(field.component[0]).toEqual(component) expect(field.component[1]).toEqual({ props: 123 }) field.setComponentProps({ hello: 'world', }) expect(field.component[1]).toEqual({ props: 123, hello: 'world' }) }) test('setDecorator/setDecoratorProps', () => { const component = () => null const form = attach(createForm()) const field = attach( form.createVoidField({ name: 'aa', }) ) field.setDecorator(undefined, { props: 123 }) field.setDecorator(component) expect(field.decorator[0]).toEqual(component) expect(field.decorator[1]).toEqual({ props: 123 }) field.setDecoratorProps({ hello: 'world', }) expect(field.decorator[1]).toEqual({ props: 123, hello: 'world' }) }) test('setState/getState', () => { const form = attach(createForm()) const aa = attach( form.createVoidField({ name: 'aa', }) ) const state = aa.getState() aa.setState((state) => { state.title = 'AAA' }) expect(aa.title).toEqual('AAA') aa.setState(state) expect(aa.title).toBeUndefined() aa.setState((state) => { state.hidden = false }) expect(aa.display).toEqual('visible') aa.setState((state) => { state.visible = true }) expect(aa.display).toEqual('visible') aa.setState((state) => { state.readOnly = false }) expect(aa.pattern).toEqual('editable') aa.setState((state) => { state.disabled = false }) expect(aa.pattern).toEqual('editable') aa.setState((state) => { state.editable = true }) expect(aa.pattern).toEqual('editable') aa.setState((state) => { state.editable = false }) expect(aa.pattern).toEqual('readPretty') aa.setState((state) => { state.readPretty = true }) expect(aa.pattern).toEqual('readPretty') aa.setState((state) => { state.readPretty = false }) expect(aa.pattern).toEqual('editable') expect(aa.parent).toBeUndefined() }) test('nested display/pattern', () => { const form = attach(createForm()) attach( form.createObjectField({ name: 'object', }) ) const void_ = attach( form.createVoidField({ name: 'void', basePath: 'object', }) ) const void2_ = attach( form.createVoidField({ name: 'void', basePath: 'object.void.0', }) ) const aaa = attach( form.createField({ name: 'aaa', basePath: 'object.void', }) ) const bbb = attach( form.createField({ name: 'bbb', basePath: 'object.void', }) ) void_.setPattern('readPretty') expect(void_.pattern).toEqual('readPretty') expect(aaa.pattern).toEqual('readPretty') expect(bbb.pattern).toEqual('readPretty') void_.setPattern('readOnly') expect(void_.pattern).toEqual('readOnly') expect(aaa.pattern).toEqual('readOnly') expect(bbb.pattern).toEqual('readOnly') void_.setPattern('disabled') expect(void_.pattern).toEqual('disabled') expect(aaa.pattern).toEqual('disabled') expect(bbb.pattern).toEqual('disabled') void_.setPattern() expect(void_.pattern).toEqual('editable') expect(aaa.pattern).toEqual('editable') expect(bbb.pattern).toEqual('editable') void_.setDisplay('hidden') expect(void_.display).toEqual('hidden') expect(aaa.display).toEqual('hidden') expect(bbb.display).toEqual('hidden') void_.setDisplay('none') expect(void_.display).toEqual('none') expect(aaa.display).toEqual('none') expect(bbb.display).toEqual('none') void_.setDisplay() expect(void_.display).toEqual('visible') expect(aaa.display).toEqual('visible') expect(bbb.display).toEqual('visible') void_.onUnmount() expect(void2_.parent === void_).toBeTruthy() }) test('reactions', async () => { const form = attach(createForm()) const aa = attach( form.createField({ name: 'aa', }) ) const bb = attach( form.createVoidField({ name: 'bb', reactions: [ (field) => { const aa = field.query('aa') if (aa.get('value') === '123') { field.visible = false } else { field.visible = true } if (aa.get('inputValue') === '333') { field.editable = false } else if (aa.get('inputValue') === '444') { field.editable = true } if (aa.get('initialValue') === '555') { field.readOnly = true } else if (aa.get('initialValue') === '666') { field.readOnly = false } }, null, ], }) ) expect(bb.visible).toBeTruthy() aa.setValue('123') expect(bb.visible).toBeFalsy() await aa.onInput('333') expect(bb.editable).toBeFalsy() await aa.onInput('444') expect(bb.editable).toBeTruthy() aa.setInitialValue('555') expect(bb.readOnly).toBeTruthy() aa.setInitialValue('666') expect(bb.readOnly).toBeFalsy() form.onUnmount() }) test('fault tolerance', () => { const form = attach(createForm()) form.setDisplay(null) form.setPattern(null) const field = attach( form.createVoidField({ name: 'xxx', }) ) expect(field.display).toEqual('visible') expect(field.pattern).toEqual('editable') }) test('child field reactions', () => { const form = attach(createForm()) const voidField = attach(form.createVoidField({ name: 'void' })) const field1 = attach( form.createField({ name: 'field1', basePath: voidField.address, reactions: [ (field) => { field.value = field.query('field3').getIn('value') }, ], }) ) const field2 = attach( form.createField({ name: 'field2', basePath: voidField.address, reactions: [ (field) => { field.value = field.query('.field3').getIn('value') }, ], }) ) expect(field1.value).toBeUndefined() expect(field2.value).toBeUndefined() const field3 = attach( form.createField({ name: 'field3', basePath: voidField.address, value: 1, }) ) expect(field1.value).toBe(1) expect(field2.value).toBe(1) field3.value = 2 expect(field1.value).toBe(2) expect(field2.value).toBe(2) }) ``` -------------------------------------------------------------------------------- /packages/core/docs/api/entry/FormChecker.md: -------------------------------------------------------------------------------- ```markdown --- order: 4 --- # Form Checkers > The type checker is mainly used to determine the specific type of an object ## isForm #### Description Determine whether an object is a [Form](/api/models/form) object #### Signature ```ts interface isForm { (target: any): target is Form } ``` #### Example ```ts import { createForm, isForm } from '@formily/core' const form = createForm() console.log(isForm(form)) //true ``` ## isField #### Description Determine whether an object is a [Field](/api/models/field) object #### Signature ```ts interface isField { (target: any): target is Field } ``` #### Example ```ts import { createForm, isField } from '@formily/core' const form = createForm() const field = form.createField({ name: 'target' }) console.log(isField(field)) //true ``` ## isArrayField #### Description Determine whether an object is [ArrayField](/api/models/array-field) object #### Signature ```ts interface isArrayField { (target: any): target is ArrayField } ``` #### Example ```ts import { createForm, isArrayField } from '@formily/core' const form = createForm() const field = form.createArrayField({ name: 'target' }) console.log(isArrayField(field)) //true ``` ## isObjectField #### Description Determine whether an object is a [ObjectField](/api/models/object-field) object #### Signature ```ts interface isObjectField { (target: any): target is ObjectField } ``` #### Example ```ts import { createForm, isObjectField } from '@formily/core' const form = createForm() const field = form.createObjectField({ name: 'target' }) console.log(isObjectField(field)) //true ``` ## isVoidField #### Description Determine whether an object is a [VoidField](/api/models/void-field) object #### Signature ```ts interface isVoidField { (target: any): target is VoidField } ``` #### Example ```ts import { createForm, isVoidField } from '@formily/core' const form = createForm() const field = form.createVoidField({ name: 'target' }) console.log(isVoidField(field)) //true ``` ## isGeneralField #### Description Determine whether an object is a Field/ArrayField/ObjectField/VoidField object #### Signature ```ts interface isGeneralField { (target: any): target is Field | ArrayField | ObjectField | VoidField } ``` #### Example ```ts import { createForm, isGeneralField } from '@formily/core' const form = createForm() const field = form.createField({ name: 'target' }) const arr = form.createArrayField({ name: 'array' }) const obj = form.createObjectField({ name: 'object' }) const vod = form.createVoidField({ name: 'void' }) console.log(isGeneralField(field)) //true console.log(isGeneralField(arr)) //true console.log(isGeneralField(obj)) //true console.log(isGeneralField(vod)) //true console.log(isGeneralField({})) //false ``` ## isDataField #### Description Determine whether an object is a Field/ArrayField/ObjectField object #### Signature ```ts interface isDataField { (target: any): target is Field | ArrayField | ObjectField } ``` #### Example ```ts import { createForm, isDataField } from '@formily/core' const form = createForm() const field = form.createField({ name: 'target' }) const arr = form.createArrayField({ name: 'array' }) const obj = form.createObjectField({ name: 'object' }) const vod = form.createVoidField({ name: 'void' }) console.log(isDataField(field)) //true console.log(isDataField(arr)) //true console.log(isDataField(obj)) //true console.log(isDataField(vod)) //false console.log(isDataField({})) //false ``` ## isFormState #### Description Determine whether an object is [IFormState](/api/models/form#iformstate) object #### Signature ```ts interface isFormState { (target: any): target is IFormState } ``` #### Example ```ts import { createForm, isFormState } from '@formily/core' const form = createForm() console.log(isFormState(form)) //false console.log(isFormState(form.getState())) //true ``` ## isFieldState #### Description Determine whether an object is [IFieldState](/api/models/field#ifieldstate) object #### Signature ```ts interface isFieldState { (target: any): target is IFieldState } ``` #### Example ```ts import { createForm, isFieldState } from '@formily/core' const form = createForm() const field = form.createField({ name: 'target', }) console.log(isFieldState(field)) //false console.log(isFieldState(field.getState())) //true ``` ## isArrayFieldState #### Description Determine whether an object is [IArrayFieldState](/api/models/array-field#iarrayfieldstate) object #### Signature ```ts interface isArrayFieldState { (target: any): target is IArrayFieldState } ``` #### Example ```ts import { createForm, isArrayFieldState } from '@formily/core' const form = createForm() const field = form.createArrayField({ name: 'target', }) console.log(isArrayFieldState(field)) //false console.log(isArrayFieldState(field.getState())) //true ``` ## isObjectFieldState #### Description Determine whether an object is [IObjectFieldState](/api/models/object-field#iobjectfieldstate) object #### Signature ```ts interface isObjectFieldState { (target: any): target is IObjectFieldState } ``` #### Example ```ts import { createForm, isObjectFieldState } from '@formily/core' const form = createForm() const field = form.createObjectField({ name: 'target', }) console.log(isObjectFieldState(field)) //false console.log(isObjectFieldState(field.getState())) //true ``` ## isVoidFieldState #### Description Determine whether an object is [IVoidFieldState](/api/models/void-field#ivoidfieldstate) object #### Signature ```ts interface isVoidFieldState { (target: any): target is IVoidFieldState } ``` #### Example ```ts import { createForm, isVoidFieldState } from '@formily/core' const form = createForm() const field = form.createVoidField({ name: 'target', }) console.log(isVoidFieldState(field)) //false console.log(isVoidFieldState(field.getState())) //true ``` ## isGeneralFieldState #### Description Determine whether an object is an IFieldState/IArrayFieldState/IObjectFieldState/IVoidFieldState object #### Signature ```ts interface isGeneralFieldState { (target: any): target is | IFieldState | IArrayFieldState | IObjectFieldState | IVoidFieldState } ``` #### Example ```ts import { createForm, isGeneralFieldState } from '@formily/core' const form = createForm() const field = form.createField({ name: 'target' }) const arr = form.createArrayField({ name: 'array' }) const obj = form.createObjectField({ name: 'object' }) const vod = form.createVoidField({ name: 'void' }) console.log(isGeneralFieldState(field)) //false console.log(isGeneralFieldState(arr)) //false console.log(isGeneralFieldState(obj)) //false console.log(isGeneralFieldState(vod)) //false console.log(isGeneralFieldState(field.getState())) //true console.log(isGeneralFieldState(arr.getState())) //true console.log(isGeneralFieldState(obj.getState())) //true console.log(isGeneralFieldState(vod.getState())) //true console.log(isGeneralFieldState({})) //false ``` ## isDataFieldState #### Description Determine whether an object is an IFieldState/IArrayFieldState/IObjectFieldState object #### Signature ```ts interface isDataFieldState { (target: any): target is IFieldState | IArrayFieldState | IObjectFieldState } ``` #### Example ```ts import { createForm, isDataFieldState } from '@formily/core' const form = createForm() const field = form.createField({ name: 'target' }) const arr = form.createArrayField({ name: 'array' }) const obj = form.createObjectField({ name: 'object' }) const vod = form.createVoidField({ name: 'void' }) console.log(isDataFieldState(field)) //false console.log(isDataFieldState(arr)) //false console.log(isDataFieldState(obj)) //false console.log(isDataFieldState(vod)) //false console.log(isDataFieldState(field.getState())) //true console.log(isDataFieldState(arr.getState())) //true console.log(isDataFieldState(obj.getState())) //true console.log(isDataFieldState(vod.getState())) //false console.log(isDataFieldState({})) //false ``` ## isQuery #### Description Determine whether an object is a Query object #### Signature ```ts interface isQuery { (target: any): target is Query } ``` #### Example ```ts import { createForm, isQuery } from '@formily/core' const form = createForm() console.log(isQuery(form.query('target'))) //true ``` ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormLayout.md: -------------------------------------------------------------------------------- ```markdown # FormLayout > Block-level layout batch control component, with the help of this component, we can easily control the layout mode of all FormItem components enclosed by FormLayout ## Markup Schema example ```tsx import React from 'react' import { Input, Select, FormItem, FormLayout } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, Select, FormItem, FormLayout, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Void x-component="FormLayout" x-component-props={{ labelCol: 6, wrapperCol: 10, }} > <SchemaField.String name="input" title="input box" x-decorator="FormItem" x-decorator-props={{ tooltip: <div>123</div>, }} x-component="Input" required /> <SchemaField.String name="select" title="select box" x-decorator="FormItem" x-component="Select" required /> </SchemaField.Void> </SchemaField> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Input, Select, FormItem, FormLayout } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, Select, FormItem, FormLayout, }, }) const schema = { type: 'object', properties: { layout: { type: 'void', 'x-component': 'FormLayout', 'x-component-props': { labelCol: 6, wrapperCol: 10, layout: 'vertical', }, properties: { input: { type: 'string', title: 'input box', required: true, 'x-decorator': 'FormItem', 'x-decorator-props': { tooltip: <div>123</div>, }, 'x-component': 'Input', }, select: { type: 'string', title: 'Select box', required: true, 'x-decorator': 'FormItem', 'x-component': 'Select', }, }, }, }, } const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Input, Select, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <Field name="input" required title="input box" decorator={[FormItem]} component={[Input]} /> <Field name="select" required title="select box" decorator={[FormItem]} component={[Select]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API | Property name | Type | Description | Default value | | -------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------- | | style | CSSProperties | Style | - | | className | string | class name | - | | colon | boolean | Is there a colon | true | | requiredMark | boolean \| `"optional"` | Required mark style. Can use required mark or optional mark | true | | labelAlign | `'right' \| 'left' \| ('right' \| 'left')[]` | Label content alignment | - | | wrapperAlign | `'right' \| 'left' \| ('right' \| 'left')[]` | Component container content alignment | - | | labelWrap | boolean | Wrap label content | false | | labelWidth | number | Label width (px) | - | | wrapperWidth | number | Component container width (px) | - | | wrapperWrap | boolean | Component container wrap | false | | labelCol | `number \| number[]` | Label width (24 column) | - | | wrapperCol | `number \| number[]` | Component container width (24 column) | - | | fullness | boolean | Component container width 100% | false | | size | `'small' \|'default' \|'large'` | component size | default | | layout | `'vertical' \| 'horizontal' \| 'inline' \| ('vertical' \| 'horizontal' \| 'inline')[]` | layout mode | horizontal | | direction | `'rtl' \|'ltr'` | direction (not supported yet) | ltr | | inset | boolean | Inline layout | false | | shallow | boolean | shallow context transfer | true | | feedbackLayout | `'loose' \|'terse' \|'popover' \|'none'` | feedback layout | true | | tooltipLayout | `"icon" \| "text"` | Ask the prompt layout | `"icon"` | | tooltipIcon | ReactNode | Ask the prompt icon | - | | bordered | boolean | Is there a border | true | | breakpoints | number[] | Container size breakpoints | - | | gridColumnGap | number | Grid Column Gap | 8 | | gridRowGap | number | Grid Row Gap | 4 | | spaceGap | number | Space Gap | 8 | ``` -------------------------------------------------------------------------------- /docs/guide/upgrade.md: -------------------------------------------------------------------------------- ```markdown # V2 Upgrade Guide It is important to mention here that Formily2 is very different from Formily1.x, and there are a lot of Break Changes. Therefore, for old users, they basically need to learn again, and V1 and V2 cannot be upgraded smoothly. But the original intention of the Formily2 project is to reduce everyone's learning costs, because the old users themselves have a certain understanding of Formily's core ideas. In order to help old users learn Formily2 more quickly, this article will list the core differences between V1 and V2. , and will not list the new capabilities. ## Kernel Difference > This mainly refers to the difference between @formily/core Because Formily1.x users mainly use setFieldState/setFormState and getFieldState/getFormState when using the core APIs, these APIs are retained in V2, but the internal model properties are semantically different. The differences are as follows: **modified** - V1: Represent whether the field has been changed, in fact, it is of no use, because the initialization of the field means that it has been changed. - V2: Indicates whether the field is manually modified, that is, it will be set to true when the component triggers the onChange event. **inputed** - V1: Represent Whether the field has been manually modified - V2: Remove, use modified uniformly **pristine** - V1:Represent whether the field value is equal to initialValue - V2: Remove, user manual judgment, this attribute will cause a lot of dirty checks **display** - V1: Represent whether the field is displayed, if it is false, the field value will not be removed - V2: Represent the field display mode, the value is `"none" | "visible" | "hidden"` **touched** - V1: Redundant field - V2: Remove **validating** - V1: Whether the representative field is being verified - V2: Remove, use validateStatus uniformly **effectErrors/effectWarnings** - V1: Errors and warnings that represent the manual operation of the user - V2: Remove, use feedbacks uniformly **ruleErrors/ruleWarnings** - V1: Errors and warnings representing the verification operation of the validator - V2: Remove, use feedbacks uniformly **values** - V1: Represent all the parameters returned by the onChange event - V2: Remove, use inputValues uniformly **rules** - V1: Represent verification rules - V2: Remove, use validator uniformly, because rules literally means rules, but the meaning of rules is very big, not limited to verification rules **props** - V1: Represent the extended attributes of the component, and the positioning is very unclear. In the pure JSX scenario, it represents the collection of component attributes and FormItem attributes. In the Schema scenario, it represents the attributes of the Schema field. - V2: Remove, use decorator and component uniformly **VirtualField** - V1: Represents a virtual field - V2: Renamed and use [VoidField](https://core.formilyjs.org/api/models/void-field) uniformly ## Bridge layer differences > This mainly refers to the difference between @formily/react and @formily/react-schema-renderer. **createFormActions/createAsyncFormActions** - V1 Create a Form operator, you can call the setFieldState/setFormState method. - V2 is removed, and the operation status of the Form instance created by [createForm](https://core.formilyjs.org/api/entry/create-form) in @formily/core is used uniformly. **Form** - V1 will create a Form instance inside, which can control the transfer of values/initialValues attributes, etc. - V2 removed, unified use of [FormProvider](https://react.formilyjs.org/api/components/form-provider) **SchemaForm** - V1 will parse the json-schema protocol internally, create a Form instance, support controlled mode, and render it. - V2 is removed, the SchemaField component created by [createSchemaField](https://react.formilyjs.org/api/components/schema-field) is used uniformly, and the controlled mode is not supported. **Field** - V1 supports controlled mode, which requires the use of render props for component state mapping. - V2 does not support controlled mode, you can quickly implement state mapping by passing in the decorator/component property. **VirtualField** - V1 supports controlled mode, which requires the use of render props for component state mapping. - V2 does not support controlled mode, renamed [VoidField](https://react.formilyjs.org/api/components/void-field), and passed in the decorator/component property to quickly implement state mapping. **FieldList** - V1 Represent auto-incremented field control component - V2 Renamed to [ArrayField](https://react.formilyjs.org/api/components/array-field) **FormSpy** - V1 Monitor all life cycle triggers and re-render - V2 Remove and use [FormConsumer](https://react.formilyjs.org/api/components/form-consumer) **SchemaMarkupField** - V1 Stands for Schema description label component - V2 Remove, unified use the description label component created by the [createSchemaField](https://react.formilyjs.org/api/components/schema-field) **useFormQuery** - V1 Fast Hook for realizing form query, supporting middleware mechanism - V2 Temporarily remove **useForm** - V1 Represents the creation of a Form instance - V2 Represents the Form instance in the consumption context, if you want to create it, please use [createForm](https://react.formilyjs.org/api/entry/create-form) **useField** - V1 Represents the creation of a Field instance - V2 Represents the Field instance in the consumption context, if you want to create it, please call [form.createField](https://core.formilyjs.org/api/models/form#createfield) **useVirtualField** - V1 Represents the creation of a VirtualField instance - V2 Remove, if you want to create, please call [form.createVoidField](https://core.formilyjs.org/api/models/form#createvoidfield) **useFormState** - V1 Form state in consumption context - V2 Remove, use [useForm](https://react.formilyjs.org/api/hooks/use-form) uniformly **useFieldState** - V1 consume Field status in context - V2 Remove, use [useField](https://react.formilyjs.org/api/hooks/use-field) **useFormSpy** - V1 Create a lifecycle listener and trigger a re-render - V2 Remove **useSchemaProps** - V1Cconsume rops of SchemaField in context - V2 Remove, use [useFieldSchema](https://react.formilyjs.org/api/hooks/use-field-schema) uniformly **connect** - V1 Standard HOC - V2 The higher-order function is changed to 1st order, and the properties have changed dramatically. See the [connect document](https://react.formilyjs.org/api/shared/connect) for details **registerFormField/registerVirtaulBox/registerFormComponent/registerFormItemComponent** - V1 Globally registered components - V2 Remove, global registration is no longer supported **FormEffectHooks** - V1 RxJS lifecycle hook - V2 Remove, export from @formily/core uniformly, and will not return RxJS Observable object **effects** - V1 Support callback function`$` selector - V2 Remove`$`selector ## Protocol layer differences > This mainly refers to the difference in the JSON Schema protocol **editable** - V1 is directly in the Schema description, indicating whether the field can be edited - V2 Renamed x-editable **visible** - V1 Indicates whether the field is displayed - V2 Renamed x-visible **display** - V1 Represent whether the field is displayed or not, if it is false, it represents the hidden behavior without deleting the value - V2 Renamed x-display, which represents the field display mode, and the value is`"none" | "visible" | "hidden"` **triggerType** - V1 Represent the field verification timing - V2 Remove, please use`x-validator:[{triggerType:"onBlur",validator:()=>...}]` **x-props** - V1 Represents the FormItem property - V2 Remove, please use x-decorator-props **x-rules** - V1 Represent field verification rules - V2 Renamed x-validator **x-linkages** - V1 Represent field linkage - V2 Remove, use x-reactions uniformly **x-mega-props** - V1 Represent the sub-component properties of the MegaLayout component - V2 Remove ## Component library differences In Formily 1.x, we mainly use @formily/antd and @formily/antd-components, or @formily/next and @formily/next-components. In V2, we have the following changes: - @formily/antd and @formily/antd-components were merged into @formily/antd, and the directory structure was changed to that of a pure component library. - The internal API of @formily/react @formily/core will no longer be exported. - Almost all components have been rewritten and cannot be smoothly upgraded. - Remove styled-components. ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Space.md: -------------------------------------------------------------------------------- ```markdown # Space > Super convenient Flex layout component, can help users quickly realize the layout of any element side by side next to each other ## Markup Schema example ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, Space, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <SchemaField> <SchemaField.Void title="name" x-decorator="FormItem" x-decorator-props={{ asterisk: true, feedbackLayout: 'none', }} x-component="Space" > <SchemaField.String name="firstName" x-decorator="FormItem" x-component="Input" required /> <SchemaField.String name="lastName" x-decorator="FormItem" x-component="Input" x-visible="{{$values.firstName === '123'}}" required /> <SchemaField.String name="kk" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> </SchemaField.Void> <SchemaField.Void title="Text concatenation" x-decorator="FormItem" x-decorator-props={{ asterisk: true, feedbackLayout: 'none', }} x-component="Space" > <SchemaField.String name="aa" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> <SchemaField.String name="bb" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> <SchemaField.String name="cc" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> </SchemaField.Void> <SchemaField.String name="textarea" title="text box" x-decorator="FormItem" required x-component="Input.TextArea" x-component-props={{ style: { width: 400, }, }} /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, Space, }, }) const form = createForm() const schema = { type: 'object', properties: { name: { type: 'void', title: 'Name', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { firstName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, lastName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, }, }, texts: { type: 'void', title: 'Text concatenation', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { aa: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, bb: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, cc: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, }, }, textarea: { type: 'string', title: 'Text box', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 400, }, }, required: true, }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field, VoidField } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <VoidField name="name" title="name" decorator={[ FormItem, { asterisk: true, feedbackLayout: 'none', }, ]} component={[Space]} > <Field name="firstName" decorator={[FormItem]} component={[Input]} required /> <Field name="lastName" decorator={[FormItem]} component={[Input]} required /> </VoidField> <VoidField name="texts" title="Text concatenation" decorator={[ FormItem, { asterisk: true, feedbackLayout: 'none', }, ]} component={[Space]} > <Field name="aa" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> <Field name="bb" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> <Field name="cc" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> </VoidField> <Field name="textarea" title="text box" decorator={[FormItem]} component={[ Input.TextArea, { style: { width: 400, }, }, ]} required /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API | Property name | Type | Description | Default value | | ------------- | ----------------------------------------- | --------------- | ------------- | | style | CSSProperties | Style | - | | className | string | class name | - | | prefix | string | style prefix | true | | size | `number \|'small' \|'large' \|'middle'` | interval size | 8px | | direction | `'horizontal' \|'vertical'` | direction | - | | align | `'start' \|'end' \|'center' \|'baseline'` | align | `'start'` | | wrap | boolean | Whether to wrap | false | ``` -------------------------------------------------------------------------------- /packages/next/src/form-item/main.scss: -------------------------------------------------------------------------------- ```scss @import '~@alifd/next/lib/core/index-noreset.scss'; @import './scss/variable.scss'; @import './grid.scss'; @import './animation.scss'; .#{$form-item-cls} { display: flex; margin-bottom: $form-item-m-margin-b + 2; position: relative; line-height: $form-element-medium-height; font-size: $form-element-medium-font-size; &-layout-vertical { display: block; } &-label-content { min-height: $form-element-medium-height; } &-control-content-component { line-height: $form-element-medium-height; } &-inset { padding: 0 8px; border: 1px solid $input-border-color; border-radius: $form-element-medium-corner; width: 100%; .#{$css-prefix}input { border: none !important; box-shadow: none !important; outline: none; } } &-bordered-none:not(.#{$form-item-cls}-inset) { .#{$css-prefix}input { border: none !important; box-shadow: none !important; outline: none; } } } .#{$form-item-cls}-label { position: relative; display: flex; &-content { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } &-tooltip { cursor: help; .#{$form-item-cls}-colon { display: flex; } * { cursor: help; } &-colon { cursor: help; } label { border-bottom: 1px dashed currentColor; } } } .#{$form-item-cls}-label label { cursor: text; color: rgba(0, 0, 0, 0.85); } .#{$form-item-cls}-label-align-left { > .#{$form-item-cls}-label { justify-content: flex-start; } } .#{$form-item-cls}-label-align-right { > .#{$form-item-cls}-label { justify-content: flex-end; } } .#{$form-item-cls}-label-wrap { .#{$form-item-cls}-label { label { white-space: pre-line; word-break: break-all; } } } .#{$form-item-cls}-feedback-layout-terse { margin-bottom: 8px; &.#{$form-item-cls}-feedback-has-text:not(.#{$form-item-cls}-inset) { margin-bottom: 0; } } .#{$form-item-cls}-feedback-layout-loose { margin-bottom: $form-item-m-margin-b + 4; &.#{$form-item-cls}-feedback-has-text:not(.#{$form-item-cls}-inset) { margin-bottom: 0; } } .#{$form-item-cls}-feedback-layout-none { margin-bottom: 0px; &.#{$form-item-cls}-feedback-has-text:not(.#{$form-item-cls}-inset) { margin-bottom: 0; } } .#{$form-item-cls}-control { flex: 1; max-width: 100%; .#{$form-item-cls}-control-content { display: flex; .#{$form-item-cls}-control-content-component { width: 100%; min-height: $form-element-medium-height; line-height: $form-element-medium-height; &-has-feedback-icon { flex: 1; position: relative; display: flex; align-items: center; .#{$css-prefix}input { height: 100%; border: none !important; box-shadow: none !important; outline: none; .#{$css-prefix}input-control { margin-right: 0; padding-right: 0; .#{$css-prefix}icon { display: none; } .#{$css-prefix}input-hint-wrap { .#{$css-prefix}icon { display: block; } } } } } .#{$css-prefix}range { height: 100%; padding-left: 2px; } .#{$css-prefix}transfer { display: flex; align-items: center; } } .#{$form-item-cls}-addon-before { margin-right: 8px; display: inline-flex; align-items: center; min-height: $form-element-medium-height; flex-shrink: 0; } .#{$form-item-cls}-addon-after { margin-left: 8px; display: inline-flex; align-items: center; min-height: $form-element-medium-height; flex-shrink: 0; } } } .#{$form-item-cls}-size-small { font-size: $form-element-small-font-size; line-height: $form-element-small-height; .#{$form-item-cls}-label-content { min-height: $form-element-small-height; } .#{$form-item-cls}-control-content { .#{$form-item-cls}-control-content-component { line-height: $form-element-small-height; min-height: $form-element-small-height; } .#{$form-item-cls}-addon-before { min-height: $form-element-small-height; } .#{$form-item-cls}-addon-after { min-height: $form-element-small-height; } } &-inset { border-radius: $form-element-small-corner; } &.#{$form-item-cls}-feedback-layout-terse { margin-bottom: 8px; &.#{$form-item-cls}-feedback-has-text:not(.#{$form-item-cls}-inset) { margin-bottom: 0; } } &.#{$form-item-cls}-feedback-layout-loose { margin-bottom: $form-item-s-margin-b + 8; &.#{$form-item-cls}-feedback-has-text:not(.#{$form-item-cls}-inset) { margin-bottom: 0; } } .#{$form-item-cls}-help, .#{$form-item-cls}-extra { min-height: $form-element-small-font-size + 2; } .#{$form-item-cls}-control-content { min-height: $form-element-small-height; } .#{$form-item-cls}-label > label { height: $form-element-small-height; } } .#{$form-item-cls}-size-large { font-size: $form-element-large-font-size; line-height: $form-element-large-height; .#{$form-item-cls}-label-content { min-height: $form-element-large-height; } .#{$form-item-cls}-control-content { .#{$form-item-cls}-control-content-component { line-height: $form-element-large-height; min-height: $form-element-large-height; } .#{$form-item-cls}-addon-before { min-height: $form-element-large-height; } .#{$form-item-cls}-addon-after { min-height: $form-element-large-height; } } &-inset { border-radius: $form-element-large-corner; } .#{$form-item-cls}-help, .#{$form-item-cls}-extra { min-height: $form-element-large-font-size + 2; } &.#{$form-item-cls}-feedback-layout-loose { margin-bottom: $form-item-l-margin-b; &.#{$form-item-cls}-feedback-has-text:not(.#{$form-item-cls}-inset) { margin-bottom: 0; } } .#{$form-item-cls}-control-content { min-height: $form-element-large-height; } .#{$form-item-cls}-label > label { height: $form-element-large-height; } } .#{$form-item-cls}-feedback-layout-popover { margin-bottom: 8px; } .#{$form-item-cls}-label-tooltip-icon { margin-left: 4px; color: #00000073; cursor: pointer; max-height: $form-element-medium-height; } .#{$form-item-cls}-control-align-left { .#{$form-item-cls}-control-content { justify-content: flex-start; } } .#{$form-item-cls}-control-align-right { .#{$form-item-cls}-control-content { justify-content: flex-end; } } .#{$form-item-cls}-control-wrap { .#{$form-item-cls}-control { white-space: pre-line; } } .#{$form-item-cls}-asterisk { color: $form-error-color; margin-right: 4px; display: inline-block; font-family: SimSun, sans-serif; } .#{$form-item-cls}-colon { margin-left: 2px; margin-right: 8px; } .#{$form-item-cls}-help, .#{$form-item-cls}-extra { clear: both; line-height: $form-item-m-margin-b + 4; min-height: $form-item-m-margin-b; color: rgba(0, 0, 0, 0.45); transition: color 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); padding-top: 0px; } .#{$form-item-cls}-fullness { > .#{$form-item-cls}-control { > .#{$form-item-cls}-control-content { > .#{$form-item-cls}-control-content-component { > *:first-child:not(.#{$css-prefix}switch):not(.#{$css-prefix}icon):not(.#{$css-prefix}formily-icon):not(.anticon):not(.#{$css-prefix}btn-text) { width: 100%; } } } } } .#{$form-item-cls}-control-content-component-has-feedback-icon { border-radius: 2px; border: 1px solid $input-border-color; transition: all 0.3s; touch-action: manipulation; outline: none; padding-right: 8px; .#{$css-prefix}input { border: none !important; } .#{$css-prefix}range-picker-trigger { border: none !important; } .#{$form-item-cls}-feedback-icon { display: flex; justify-content: center; align-items: center; margin-right: -2px; padding-left: 2px; .anticon { display: inline-block; color: inherit; font-style: normal; font-size: $form-element-medium-font-size; line-height: 0; text-align: center; text-transform: none; vertical-align: -0.125em; text-rendering: optimizeLegibility; } } } .#{$form-item-cls}-error-help { color: $form-error-color; } .#{$form-item-cls}-warning-help { color: $form-warning-color; } .#{$form-item-cls}-success-help { color: $form-success-color; } ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/annotations.spec.ts: -------------------------------------------------------------------------------- ```typescript import { observable, action, model, define } from '../' import { autorun, reaction } from '../autorun' import { observe } from '../observe' import { isObservable } from '../externals' import { untracked } from '../untracked' import { getObservableMaker } from '../internals' test('observable annotation', () => { const obs = observable<any>({ aa: 111, }) const handler = jest.fn() const handler1 = jest.fn() observe(obs, handler1) reaction(() => { handler(obs.aa) }) obs.aa = { bb: { cc: 123 } } obs.aa.bb = 333 expect(handler).toBeCalledTimes(2) expect(handler1).toBeCalledTimes(2) const handler2 = jest.fn() const handler3 = jest.fn() const obsAnno = getObservableMaker(observable)({ value: obs }) observe(obsAnno, handler2) reaction(() => { handler3(obsAnno.aa) }) obsAnno.aa = { bb: { cc: 123 } } obsAnno.aa.bb = 333 expect(handler2).toBeCalledTimes(2) expect(handler3).toBeCalledTimes(2) }) test('shallow annotation', () => { const obs = observable.shallow<any>({ aa: 111, }) const handler = jest.fn() const handler1 = jest.fn() observe(obs, handler1) reaction(() => { handler(obs.aa) }) obs.aa = { bb: { cc: 123 } } expect(isObservable(obs)).toBe(true) expect(isObservable(obs.aa)).toBe(false) expect(isObservable(obs.aa.bb)).toBe(false) obs.aa.bb = 333 obs.cc = 444 expect(handler).toBeCalledTimes(2) expect(handler1).toBeCalledTimes(2) }) test('box annotation', () => { const obs = observable.box(123) const handler = jest.fn() const handler1 = jest.fn() observe(obs, handler1) reaction(() => { handler(obs.get()) }) const boxValue = 333 obs.set(boxValue) expect(handler1).toBeCalledTimes(1) expect(handler1.mock.calls[0][0]).toMatchObject({ value: boxValue, }) expect(handler).toBeCalledTimes(2) expect(handler.mock.calls[0][0]).toBe(123) expect(handler.mock.calls[1][0]).toBe(boxValue) }) test('ref annotation', () => { const obs = observable.ref(123) const handler = jest.fn() const handler1 = jest.fn() observe(obs, handler1) reaction(() => { handler(obs.value) }) obs.value = 333 expect(handler).nthCalledWith(1, 123) expect(handler).nthCalledWith(2, 333) expect(handler1).toBeCalledTimes(1) }) test('action annotation', () => { const obs = observable<any>({}) const setData = action.bound(() => { obs.aa = 123 obs.bb = 321 }) const handler = jest.fn() reaction(() => { return [obs.aa, obs.bb] }, handler) setData() expect(handler).toBeCalledTimes(1) expect(handler).toBeCalledWith([123, 321], [undefined, undefined]) }) test('no action annotation', () => { const obs = observable<any>({}) const setData = () => { obs.aa = 123 obs.bb = 321 } const handler = jest.fn() reaction(() => { return [obs.aa, obs.bb] }, handler) setData() expect(handler).toBeCalledTimes(2) expect(handler).nthCalledWith(1, [123, undefined], [undefined, undefined]) expect(handler).nthCalledWith(2, [123, 321], [123, undefined]) }) test('computed annotation', () => { const obs = observable({ aa: 11, bb: 22, }) const handler = jest.fn(() => obs.aa + obs.bb) const runner1 = jest.fn() const runner2 = jest.fn() const runner3 = jest.fn() const compu = observable.computed(handler) expect(compu.value).toEqual(33) expect(handler).toBeCalledTimes(1) obs.aa = 22 expect(handler).toBeCalledTimes(1) const dispose = autorun(() => { compu.value runner1() }) const dispose2 = autorun(() => { compu.value runner2() }) expect(compu.value).toEqual(44) expect(handler).toBeCalledTimes(2) obs.bb = 33 expect(runner1).toBeCalledTimes(2) expect(runner2).toBeCalledTimes(2) expect(handler).toBeCalledTimes(3) expect(compu.value).toEqual(55) expect(handler).toBeCalledTimes(3) obs.aa = 11 expect(runner1).toBeCalledTimes(3) expect(runner2).toBeCalledTimes(3) expect(handler).toBeCalledTimes(4) expect(compu.value).toEqual(44) expect(handler).toBeCalledTimes(4) dispose() obs.aa = 22 expect(runner1).toBeCalledTimes(3) expect(runner2).toBeCalledTimes(4) expect(handler).toBeCalledTimes(5) expect(compu.value).toEqual(55) expect(handler).toBeCalledTimes(5) dispose2() obs.aa = 33 expect(runner1).toBeCalledTimes(3) expect(runner2).toBeCalledTimes(4) expect(handler).toBeCalledTimes(5) expect(compu.value).toEqual(66) expect(handler).toBeCalledTimes(6) expect(compu.value).toEqual(66) expect(handler).toBeCalledTimes(6) autorun(() => { compu.value runner3() }) expect(compu.value).toEqual(66) expect(handler).toBeCalledTimes(6) expect(compu.value).toEqual(66) expect(handler).toBeCalledTimes(6) obs.aa = 11 expect(handler).toBeCalledTimes(7) expect(compu.value).toEqual(44) expect(handler).toBeCalledTimes(7) }) test('computed chain annotation', () => { const obs = observable({ aa: 11, bb: 22, }) const handler = jest.fn(() => obs.aa + obs.bb) const compu1 = observable.computed(handler) const handler1 = jest.fn(() => compu1.value + 33) const compu2 = observable.computed(handler1) const dispose = autorun(() => { compu2.value }) expect(handler).toBeCalledTimes(1) expect(handler1).toBeCalledTimes(1) expect(compu2.value).toEqual(66) expect(handler).toBeCalledTimes(1) expect(handler1).toBeCalledTimes(1) obs.aa = 22 expect(handler).toBeCalledTimes(2) expect(handler1).toBeCalledTimes(2) expect(compu2.value).toEqual(77) expect(handler).toBeCalledTimes(2) expect(handler1).toBeCalledTimes(2) dispose() obs.aa = 11 expect(handler).toBeCalledTimes(2) expect(handler1).toBeCalledTimes(2) expect(compu2.value).toEqual(66) expect(handler).toBeCalledTimes(3) expect(handler1).toBeCalledTimes(3) }) test('computed with array length', () => { const obs = model({ arr: [], get isEmpty() { return this.arr.length === 0 }, get isNotEmpty() { return !this.isEmpty }, }) const handler = jest.fn() autorun(() => { handler(obs.isEmpty) handler(obs.isNotEmpty) }) expect(handler).toBeCalledTimes(2) obs.arr = ['1'] obs.arr = [] expect(handler).toBeCalledTimes(6) }) test('computed with computed array length', () => { const obs = model({ arr: [], get arr2() { return this.arr.map((item: number) => item + 1) }, get isEmpty() { return this.arr2.length === 0 }, get isNotEmpty() { return !this.isEmpty }, }) const handler = jest.fn() const handler2 = jest.fn() autorun(() => { handler(obs.isNotEmpty) handler2(obs.arr2) }) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(false) expect(handler2).toBeCalledTimes(1) expect(handler2.mock.calls[0][0]).toEqual([]) obs.arr.push(1) expect(handler).lastCalledWith(true) expect(handler2.mock.calls[1][0]).toEqual([2]) obs.arr = [] expect(handler).lastCalledWith(false) expect(handler2.mock.calls[2][0]).toEqual([]) }) test('computed recollect dependencies', () => { const computed = jest.fn() const obs = model({ aa: 'aaa', bb: 'bbb', cc: 'ccc', get compute() { computed() if (this.aa === 'aaa') { return this.bb } return this.cc }, }) const handler = jest.fn() autorun(() => { handler(obs.compute) }) obs.aa = '111' obs.bb = '222' expect(computed).toBeCalledTimes(2) }) test('computed no params', () => { observable.computed(null) }) test('computed object params', () => { observable.computed({ get: () => {} }) }) test('computed no track get', () => { const obs = observable({ aa: 123 }) const compu = observable.computed({ get: () => obs.aa }) untracked(() => { expect(compu.value).toBe(123) }) }) test('computed cache descriptor', () => { class A { _value = 0 constructor() { define(this, { _value: observable.ref, value: observable.computed, }) } get value() { return this._value } } const obs1 = new A() const obs2 = new A() const handler1 = jest.fn() const handler2 = jest.fn() autorun(() => { handler1(obs1.value) }) autorun(() => { handler2(obs2.value) }) expect(handler1).toBeCalledTimes(1) expect(handler2).toBeCalledTimes(1) obs1._value = 123 obs2._value = 123 expect(handler1).toBeCalledTimes(2) expect(handler2).toBeCalledTimes(2) }) test('computed normal object', () => { const obs = define( { _value: 0, get value() { return this._value }, }, { _value: observable.ref, value: observable.computed, } ) const handler = jest.fn() autorun(() => { handler(obs.value) }) expect(handler).toBeCalledTimes(1) obs._value = 123 expect(handler).toBeCalledTimes(2) }) ``` -------------------------------------------------------------------------------- /packages/react/src/__tests__/field.spec.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { act } from 'react-dom/test-utils' import { render, fireEvent, waitFor } from '@testing-library/react' import { createForm, onFieldUnmount, isArrayField } from '@formily/core' import { isField, Field as FieldType, isVoidField, onFieldChange, } from '@formily/core' import { FormProvider, ArrayField, ObjectField, VoidField, Field, useField, useFormEffects, observer, connect, mapProps, mapReadPretty, } from '..' import { ReactiveField } from '../components/ReactiveField' import { expectThrowError } from './shared' type InputProps = { value?: string onChange?: (...args: any) => void } type CustomProps = { list?: string[] } const Decorator = (props) => <div>{props.children}</div> const Input: React.FC<React.PropsWithChildren<InputProps>> = (props) => ( <input {...props} value={props.value || ''} data-testid={useField().path.toString()} /> ) const Normal = () => <div></div> test('render field', async () => { const form = createForm() const onChange = jest.fn() const { getByTestId, queryByTestId, unmount } = render( <FormProvider form={form}> <Field name="aa" decorator={[Decorator]} component={[Input, { onChange }]} /> <ArrayField name="bb" decorator={[Decorator]}> <div data-testid="bb-children"></div> </ArrayField> <ObjectField name="cc" decorator={[Decorator]}> <Field name="mm" decorator={[Decorator]} component={[Input]} /> <ObjectField name="pp" decorator={[Decorator]} /> <ArrayField name="tt" decorator={[Decorator]} /> <VoidField name="ww" /> </ObjectField> <VoidField name="dd" decorator={[Decorator]}> {() => ( <div data-testid="dd-children"> <Field name="oo" decorator={[Decorator]} component={[Input]} /> </div> )} </VoidField> <VoidField name="xx" decorator={[Decorator]} component={[Normal]} /> <Field name="ee" visible={false} decorator={[Decorator]} component={[Input]} /> <Field name="ff" decorator={[]} component={[]} /> <Field name="gg" decorator={null} component={null} /> <Field name="hh" decorator={[null]} component={[null, null]} /> <Field name="kk" decorator={[Decorator]} component={[Input, { onChange: null }]} /> </FormProvider> ) expect(form.mounted).toBeTruthy() expect(form.query('aa').take().mounted).toBeTruthy() expect(form.query('bb').take().mounted).toBeTruthy() expect(form.query('cc').take().mounted).toBeTruthy() expect(form.query('dd').take().mounted).toBeTruthy() fireEvent.change(getByTestId('aa'), { target: { value: '123', }, }) fireEvent.change(getByTestId('kk'), { target: { value: '123', }, }) expect(onChange).toBeCalledTimes(1) expect(getByTestId('bb-children')).not.toBeUndefined() expect(getByTestId('dd-children')).not.toBeUndefined() expect(queryByTestId('ee')).toBeNull() expect(form.query('aa').get('value')).toEqual('123') expect(form.query('kk').get('value')).toEqual('123') unmount() }) test('render field no context', () => { expectThrowError(() => { return ( <> <Field name="aa">{() => <div></div>}</Field> <ArrayField name="bb"> <div></div> </ArrayField> <ObjectField name="cc" /> <VoidField name="dd" /> </> ) }) }) test('ReactiveField', () => { render(<ReactiveField field={null} />) render(<ReactiveField field={null}>{() => <div></div>}</ReactiveField>) }) test('useAttach basic', async () => { const form = createForm() const MyComponent = (props: any) => { return ( <FormProvider form={form}> <Field name={props.name} decorator={[Decorator]} component={[Input]} /> </FormProvider> ) } const { rerender } = render(<MyComponent name="aa" />) expect(form.query('aa').take().mounted).toBeTruthy() rerender(<MyComponent name="bb" />) await waitFor(() => { expect(form.query('aa').take().mounted).toBeFalsy() expect(form.query('bb').take().mounted).toBeTruthy() }) }) test('useAttach with array field', async () => { const form = createForm() const MyComponent = () => { return ( <FormProvider form={form}> <ArrayField name="array" initialValue={[{ input: '11' }, { input: '22' }]} > {(field) => { return field.value.map((val, index) => { return ( <Field key={index} name={index + '.input'} decorator={[Decorator]} component={[Input]} /> ) }) }} </ArrayField> </FormProvider> ) } render(<MyComponent />) await waitFor(() => { expect(form.query('array.0.input').take().mounted).toBeTruthy() expect(form.query('array.1.input').take().mounted).toBeTruthy() }) form.query('array').take((field) => { if (isArrayField(field)) { field.moveDown(0) } }) await waitFor(() => { expect(form.query('array.0.input').take().mounted).toBeTruthy() expect(form.query('array.1.input').take().mounted).toBeTruthy() }) }) test('useFormEffects', async () => { const form = createForm() const CustomField = observer(() => { const field = useField<FieldType>() useFormEffects(() => { onFieldChange('aa', ['value'], (target) => { if (isVoidField(target)) return field.setValue(target.value) }) }) return <div data-testid="custom-value">{field.value}</div> }) act(async () => { const { queryByTestId, rerender } = render( <FormProvider form={form}> <Field name="aa" decorator={[Decorator]} component={[Input]} /> <Field name="bb" component={[CustomField, { tag: 'xxx' }]} /> </FormProvider> ) expect(queryByTestId('custom-value')?.textContent).toEqual('') form.query('aa').take((aa) => { if (isField(aa)) { aa.setValue('123') } }) await waitFor(() => { expect(queryByTestId('custom-value')?.textContent).toEqual('123') }) rerender( <FormProvider form={form}> <Field name="aa" decorator={[Decorator]} component={[Input]} /> <Field name="bb" component={[CustomField, { tag: 'yyy' }]} /> </FormProvider> ) }) }) test('connect', async () => { const CustomField = connect( (props: CustomProps) => { return <div>{props.list}</div> }, mapProps({ value: 'list', loading: true }, (props, field) => { return { ...props, mounted: field.mounted ? 1 : 2, } }), mapReadPretty(() => <div>read pretty</div>) ) const BaseComponent = (props: any) => { return <div>{props.value}</div> } BaseComponent.displayName = 'BaseComponent' const CustomField2 = connect( BaseComponent, mapProps({ value: true, loading: true }), mapReadPretty(() => <div>read pretty</div>) ) const form = createForm() const MyComponent = () => { return ( <FormProvider form={form}> <Field name="aa" decorator={[Decorator]} component={[CustomField]} /> <Field name="bb" decorator={[Decorator]} component={[CustomField2]} /> </FormProvider> ) } const { queryByText } = render(<MyComponent />) form.query('aa').take((field) => { field.setState((state) => { state.value = '123' }) }) await waitFor(() => { expect(queryByText('123')).toBeVisible() }) form.query('aa').take((field) => { if (!isField(field)) return field.readPretty = true }) await waitFor(() => { expect(queryByText('123')).toBeNull() expect(queryByText('read pretty')).toBeVisible() }) }) test('fields unmount and validate', async () => { const fn = jest.fn() const form = createForm({ initialValues: { parent: { type: 'mounted', }, }, effects: () => { onFieldUnmount('parent.child', () => { fn() }) }, }) const Parent = observer(() => { const field = useField<FieldType>() if (field.value.type === 'mounted') { return ( <Field name="child" component={[Input]} validator={{ required: true }} /> ) } return <div data-testid="unmounted"></div> }) const MyComponent = () => { return ( <FormProvider form={form}> <Field name="parent" component={[Parent]} /> </FormProvider> ) } render(<MyComponent />) try { await form.validate() } catch {} expect(form.invalid).toBeTruthy() form.query('parent').take((field) => { field.setState((state) => { state.value.type = 'unmounted' }) }) await waitFor(() => { expect(fn.mock.calls.length).toBe(1) }) try { await form.validate() } catch {} expect(form.invalid).toBeTruthy() }) ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormDialog.md: -------------------------------------------------------------------------------- ```markdown # FormDialog > Pop-up form, mainly used in simple event to open the form scene ## Markup Schema example ```tsx import React from 'react' import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' import { createSchemaField } from '@formily/react' import { Button } from '@alifd/next' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) export default () => { return ( <Button onClick={() => { FormDialog('Pop-up form', () => { return ( <FormLayout labelCol={6} wrapperCol={14}> <SchemaField> <SchemaField.String name="aaa" required title="input box 1" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="bbb" required title="input box 2" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="ccc" required title="input box 3" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="ddd" required title="input box 4" x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormDialog.Footer> <span style={{ marginLeft: 4 }}>Extended copywriting</span> </FormDialog.Footer> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > Click me to open the form </Button> ) } ``` ## JSON Schema case ```tsx import React from 'react' import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' import { createSchemaField } from '@formily/react' import { Button } from '@alifd/next' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const schema = { type: 'object', properties: { aaa: { type: 'string', title: 'input box 1', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, bbb: { type: 'string', title: 'input box 2', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ccc: { type: 'string', title: 'input box 3', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ddd: { type: 'string', title: 'input box 4', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, } export default () => { return ( <Button onClick={() => { FormDialog('Pop-up form', () => { return ( <FormLayout labelCol={6} wrapperCol={14}> <SchemaField schema={schema} /> <FormDialog.Footer> <span style={{ marginLeft: 4 }}>Extended copywriting</span> </FormDialog.Footer> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > Click me to open the form </Button> ) } ``` ## Pure JSX case ```tsx import React from 'react' import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' import { Field } from '@formily/react' import { Button } from '@alifd/next' export default () => { return ( <Button onClick={() => { FormDialog('Pop-up form', () => { return ( <FormLayout labelCol={6} wrapperCol={14}> <Field name="aaa" required title="input box 1" decorator={[FormItem]} component={[Input]} /> <Field name="bbb" required title="input box 2" decorator={[FormItem]} component={[Input]} /> <Field name="ccc" required title="input box 3" decorator={[FormItem]} component={[Input]} /> <Field name="ddd" required title="input box 4" decorator={[FormItem]} component={[Input]} /> <FormDialog.Footer> <span style={{ marginLeft: 4 }}>Extended copywriting</span> </FormDialog.Footer> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > Click me to open the form </Button> ) } ``` ## Use Fusion Context ```tsx import React from 'react' import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' import { Field } from '@formily/react' import { Button, ConfigProvider } from '@alifd/next' export default () => { return ( <ConfigProvider locale={{ Dialog: { ok: 'OK', cancel: 'Cancel', }, }} defaultPropsConfig={{ Dialog: { isFullScreen: true, footerActions: ['cancel', 'ok'], }, }} > <Button onClick={() => { FormDialog('Pop-up form', () => { return ( <FormLayout labelCol={6} wrapperCol={14}> <Field name="aaa" required title="input box 1" decorator={[FormItem]} component={[Input]} /> <Field name="bbb" required title="input box 2" decorator={[FormItem]} component={[Input]} /> <Field name="ccc" required title="input box 3" decorator={[FormItem]} component={[Input]} /> <Field name="ddd" required title="input box 4" decorator={[FormItem]} component={[Input]} /> <FormDialog.Footer> <span style={{ marginLeft: 4 }}>Extended copywriting</span> </FormDialog.Footer> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > Click me to open the form </Button> </ConfigProvider> ) } ``` ## API ### FormDialog ```ts pure import { IFormProps, Form } from '@formily/core' type FormDialogRenderer = | React.ReactElement | ((form: Form) => React.ReactElement) interface IFormDialog { forOpen( middleware: ( props: IFormProps, next: (props?: IFormProps) => Promise<any> ) => any ): any //Middleware interceptor, can intercept Dialog to open forConfirm( middleware: (props: Form, next: (props?: Form) => Promise<any>) => any ): any //Middleware interceptor, which can intercept Dialog confirmation forCancel( middleware: (props: Form, next: (props?: Form) => Promise<any>) => any ): any //Middleware interceptor, can intercept Dialog to cancel //Open the pop-up window to receive form attributes, you can pass in initialValues/values/effects etc. open(props: IFormProps): Promise<any> //return form data //Close the pop-up window close(): void } interface IDialogProps extends DialogProps { onOk?: (event: React.MouseEvent) => void | boolean // return false can prevent onOk onCancel?: (event: React.MouseEvent) => void | boolean // return false can prevent onCancel loadingText?: React.ReactText } interface FormDialog { (title: IDialogProps, id: string, renderer: FormDialogRenderer): IFormDialog (title: IDialogProps, renderer: FormDialogRenderer): IFormDialog (title: ModalTitle, id: string, renderer: FormDialogRenderer): IFormDialog (title: ModalTitle, renderer: FormDialogRenderer): IFormDialog } ``` `DialogProps` type definition reference fusion [Dialog API](https://fusion.design/pc/component/dialog?themeid=2#API) ### FormDialog.Footer No attributes, only child nodes are received ### FormDialog.Portal Receive the optional id attribute, the default value is `form-dialog`, if there are multiple prefixCls in an application, and the prefixCls in the pop-up window of different regions are different, then it is recommended to specify the id as the region-level id ``` -------------------------------------------------------------------------------- /packages/core/docs/api/models/VoidField.md: -------------------------------------------------------------------------------- ```markdown --- order: 4 --- # VoidField Call the VoidField model returned by [createVoidField](/api/models/form#createvoidfield). All model attributes are listed below. If the attribute is writable, then we can directly refer to it to modify the attribute, and @formily/reactive will respond to trigger the UI update. ## Attributes | Property | Description | Type | Read-only or not | Default value | | ----------- | ----------------------------------------- | --------------------------------------- | ---------------- | ------------- | | initialized | Whether the field has been initialized | Boolean | No | `false` | | mounted | Is the field mounted | Boolean | No | `false` | | unmounted | Is the field unmounted | Boolean | No | `false` | | address | Field node path | [FormPath](/api/entry/form-path) | Yes | | | path | Field data path | [FormPath](/api/entry/form-path) | Yes | | | title | Field Title | [FieldMessage](#fieldmessage) | No | `""` | | description | Field description | [FieldMessage](#fieldmessage) | No | `""` | | decorator | field decorator | Any[] | No | `null` | | component | Field component | Any[] | No | `null` | | parent | Parent field | [GeneralField](#generalfield) | yes | `null` | | display | Field display status | [FieldDisplayTypes](#fielddisplaytypes) | No | `"visible"` | | pattern | Field interaction mode | [FieldPatternTypes](#fieldpatterntypes) | No | `"editable"` | | hidden | Whether the field is hidden | Boolean | No | `false` | | visible | Whether the field is displayed | Boolean | No | `true` | | disabled | Whether the field is disabled | Boolean | No | `false` | | readOnly | Is the field read-only | Boolean | No | `false` | | readPretty | Whether the field is in the reading state | Boolean | No | `false` | | editable | Field is editable | Boolean | No | `true` | #### explain in detail **hidden** When true, display is hidden, when false, display is visible **visible** When true, display is visible, when false, display is none ## Method ### setTitle #### Description Set field title #### Signature ```ts interface setTitle { (title?: FieldMessage): void } ``` FieldMessage Reference [FieldMessage](#fieldmessage) ### setDescription #### Description Set field description information #### Signature ```ts interface setDescription { (title?: FieldMessage): void } ``` FieldMessage Reference [FieldMessage](#fieldmessage) ### setDisplay #### Description Set field display status #### Signature ```ts interface setDisplay { (display?: FieldDisplayTypes): void } ``` FieldDisplayTypes Reference [FieldDisplayTypes](#fielddisplaytypes) ### setPattern #### Description Set field interaction mode #### Signature ```ts interface setPattern { (pattern?: FieldPatternTypes): void } ``` FieldPatternTypes Reference [FieldPatternTypes](#fieldpatterntypes) ### setComponent #### Description Set field component #### Signature ```ts interface setComponent { (component?: FieldComponent, props?: any): void } ``` FieldComponent Reference [FieldComponent](#fieldcomponent) ### setComponentProps #### Description Set field component properties #### Signature ```ts interface setComponentProps { (props?: any): void } ``` ### setDecorator #### Description Set field decorator #### Signature ```ts interface setDecorator { (decorator?: FieldDecorator, props?: any): void } ``` FieldDecorator Reference [FieldDecorator](#fielddecorator) ### setDecoratorProps #### Description Set field decorator properties #### Signature ```ts interface setDecoratorProps { (props?: any): void } ``` ### setState #### Description Set field status #### Signature ```ts interface setState { (state: IVoidFieldState): void (callback: (state: IVoidFieldState) => void): void } ``` IVoidFieldState Reference [IVoidFieldState](#ifieldstate) ### getState #### Description Get field status #### Signature ```ts interface getState<T> { (): IVoidFieldState (callback: (state: IVoidFieldState) => T): T } ``` IVoidFieldState Reference [IVoidFieldState](#ifieldstate) ### setData #### Description set field data #### Signature ```ts interface setData { (data: any): void } ``` ### setContent #### Description set field content #### Signature ```ts interface setContent { (content: any): void } ``` ### onInit #### Description Trigger field initialization, no need to call manually #### Signature ```ts interface onInit { (): void } ``` ### onMount #### Description Trigger field mount #### Signature ```ts interface onMount { (): void } ``` ### onUnmount #### Description Trigger field unloading #### Signature ```ts interface onUnmount { (): void } ``` ### query #### Description Query field, you can query adjacent fields based on the current field #### Signature ```ts interface query { (pattern: FormPathPattern): Query } ``` FormPathPattern API Reference [FormPath](/api/entry/form-path#formpathpattern) Query object API reference [Query](/api/models/query) ### dispose #### Description Release observer, no need to release manually by default #### Signature ```ts interface dispose { (): void } ``` ### destroy #### Description Release observer, and remove current field model #### Signature ```ts interface destroy { (): void } ``` ### match #### Description Match fields based on path #### Signature ```ts interface match { (pattern: FormPathPattern): boolean } ``` FormPathPattern API Reference [FormPath](/api/entry/form-path#formpathpattern) ### inject #### Description Inject executable methods into field models #### Signature ```ts interface inject { (actions: Record<string, (...args: any[]) => any>): void } ``` ### invoke #### Description Invoke an executable method injected by the field model via inject #### Signature ```ts interface invoke { (name: string, ...args: any[]): any } ``` ## Types of <Alert> Note: If you want to manually consume the type, just export it directly from the package module </Alert> ### FieldMessage ```ts type FieldMessage = string | JSXElement ``` If under the UI framework that supports JSX, we can directly pass the Node of JSX, otherwise, we can only pass the string ### FieldComponent ```ts type FieldComponent = string | JSXComponentConstructor ``` Field component, if we use it in a framework that supports JSX, FieldComponent recommends to store the JSX component reference directly, otherwise it can store a component identification string and distribute it during actual rendering. ### FieldDecorator ```ts type FieldDecorator = string | JSXComponentConstructor ``` Field decorator, if we use it in a framework that supports JSX, FieldDecorator recommends to store the JSX component reference directly, otherwise it can store a component identification string and distribute it during actual rendering. ### FieldReaction ```ts type FieldReaction = (field: GeneralField) => void ``` ### FieldDisplayTypes ```ts type FieldDisplayTypes = 'none' | 'hidden' | 'visible' ``` ### FieldPatternTypes ```ts type FieldPatternTypes = 'editable' | 'disabled' | 'readOnly' | 'readPretty' ``` ### GeneralField ```ts type GeneralField = Field | VoidField | ArrayField | ObjectField ``` Field Reference [Field](/api/models/field) ArrayField Reference [ArrayField](/api/models/array-field) ObjectField Reference [ObjectField](/api/models/object-field) ### IVoidFieldState ```ts interface IVoidFieldState { hidden?: boolean visible?: boolean editable?: boolean readOnly?: boolean disabled?: boolean readPretty?: boolean title?: any description?: any modified?: boolean active?: boolean visited?: boolean initialized?: boolean mounted?: boolean unmounted?: boolean decorator?: FieldDecorator component?: FieldComponent readonly parent?: GeneralField display?: FieldDisplayTypes pattern?: FieldPatternTypes } ``` ### IGeneralFieldState ```ts type IGeneralFieldState = IVoidFieldState & IFieldState ``` IFieldState Reference [IFieldState](/api/models/field#ifieldstate) ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/transformer.spec.ts: -------------------------------------------------------------------------------- ```typescript import { Schema } from '../schema' import { createForm } from '@formily/core' import { isObservable } from '@formily/reactive' import { ISchema, ISchemaTransformerOptions } from '../types' const attach = <T extends { onMount: () => void }>(target: T): T => { target.onMount() return target } const getFormAndFields = ( field1SchemaProps: Omit<ISchema, 'name'> = {}, field2SchemaProps: Omit<ISchema, 'name'> = {}, options: ISchemaTransformerOptions = {} ) => { const filed1Schema = new Schema({ name: 'field1', ...field1SchemaProps, }).toFieldProps(options) const filed2Schema = new Schema({ name: 'field2', ...field2SchemaProps, }).toFieldProps(options) const form = createForm() const field1 = form.createField(filed1Schema) const field2 = form.createField(filed2Schema) return { form, field1, field2, } } test('baseReaction', () => { const { field1, field2 } = getFormAndFields( { title: 'field1Title', }, { title: 'field2Title', } ) expect(field1.title).toBe('field1Title') expect(field2.title).toBe('field2Title') }) test('baseReaction with scopes', () => { const scopeTitle = 'fieldTitle' const scopeDescription = 'fieldDescription' const { field1, field2 } = getFormAndFields( { title: '{{scopeTitle}}', }, { description: '{{scopeDescription}}', }, { scope: { scopeTitle, scopeDescription, }, } ) expect(field1.title).toBe(scopeTitle) expect(field2.description).toBe(scopeDescription) }) test('userReactions with target(state)', () => { const field2Title = 'field2Title' const { field2 } = getFormAndFields({ 'x-reactions': { target: 'field2', fulfill: { state: { title: field2Title, }, }, }, }) expect(field2.title).toBe(field2Title) }) test('userReactions with target(schema)', () => { const field2Data = 'fieldData' const { field2 } = getFormAndFields({ 'x-reactions': { target: 'field2', fulfill: { schema: { 'x-data': field2Data, }, }, }, }) expect(field2.data).toBe(field2Data) }) test('userReactions with target(runner)', () => { const mockFn = jest.fn() const field2Title = 'field2Title' const { field2 } = getFormAndFields( { 'x-reactions': { target: 'field2', fulfill: { run: `$target.title='${field2Title}';fn()`, }, }, }, {}, { scope: { fn: mockFn, }, } ) expect(mockFn).toBeCalledTimes(1) expect(field2.title).toBe(field2Title) }) test('userReactions without target(state)', () => { const field1Title = 'field1Title' const { field1 } = getFormAndFields({ 'x-reactions': { fulfill: { state: { title: field1Title, }, }, }, }) expect(field1.title).toBe(field1Title) }) test('userReactions without target(schema)', () => { const field1Data = 'fieldData' const { field1 } = getFormAndFields({ 'x-reactions': { fulfill: { schema: { 'x-data': field1Data, }, }, }, }) expect(field1.data).toBe(field1Data) }) test('userReactions without target(runner)', () => { const mockFn = jest.fn() const { field1 } = getFormAndFields( { 'x-reactions': { fulfill: { run: `$self.__target__=$target;fn()`, }, }, }, {}, { scope: { fn: mockFn, }, } ) expect(mockFn).toBeCalledTimes(1) expect((field1 as any).__target__).toBe(null) }) test('userReactions with condition', () => { const mockFn = jest.fn() const { field1 } = getFormAndFields( { 'x-value': true, 'x-reactions': { when: '$self.value===true', fulfill: { run: 'mockFn($self.value)', }, otherwise: { run: 'mockFn($self.value)', }, }, }, {}, { scope: { mockFn, }, } ) expect(mockFn).nthCalledWith(1, true) field1.value = false expect(mockFn).nthCalledWith(2, false) }) test('userReactions with condition(wrong type)', () => { const field1Value = 'field1Value' const mockFn = jest.fn() getFormAndFields( { 'x-value': field1Value, 'x-reactions': { dependencies: 'value', fulfill: { run: 'mockFn($deps, $dependencies)', }, }, }, {}, { scope: { mockFn, }, } ) expect(mockFn).nthCalledWith(1, [], []) }) test('userReactions with condition(array)', () => { const field1Value = 'field1Value' const field2Value = 'field2Value' const field1Title = 'field1Title' const field1Description = 'field1Description' const mockFn = jest.fn() getFormAndFields( { title: field1Title, description: field1Description, 'x-value': field1Value, }, { 'x-value': field2Value, 'x-reactions': { dependencies: [ 'field2', { name: 1, source: 'field1', }, { name: 2, source: 'field1#title', }, { name: 3, source: 'field1', property: 'description', }, ], fulfill: { run: `mockFn($deps)`, }, }, }, { scope: { mockFn, }, } ) expect(mockFn).nthCalledWith(1, [ field2Value, field1Value, field1Title, field1Description, ]) }) test('userReactions with condition(object)', () => { const field2Value = 'field2Value' const field1Title = 'field1Title' const mockFn = jest.fn() getFormAndFields( { title: field1Title, }, { 'x-value': field2Value, 'x-reactions': { dependencies: { key1: 'field1#title', key2: 'field2', }, fulfill: { run: `mockFn($deps)`, }, }, }, { scope: { mockFn, }, } ) expect(mockFn).nthCalledWith(1, { key1: field1Title, key2: field2Value, }) }) test('userReactions with user-defined effects', () => { const field2Value = 'field2Value' const field1Title = 'field1Title' const mockFn = jest.fn() const { field2 } = getFormAndFields( { title: field1Title, 'x-reactions': { target: 'field2', fulfill: { run: `mockFn($target.value)`, }, effects: ['onFieldInit'], }, }, { 'x-value': field2Value, }, { scope: { mockFn, }, } ) expect(mockFn).toBeCalledTimes(1) expect(mockFn).nthCalledWith(1, field2Value) field2.value = field1Title expect(mockFn).toBeCalledTimes(1) }) test('userReactions with function type', () => { const componentProps = { prop: 1, } let observable: any = {} const { field1 } = getFormAndFields({ 'x-reactions': (field, baseScope) => { baseScope.$props(componentProps) observable = baseScope.$observable({}) }, }) expect(field1.componentProps).toMatchObject(componentProps) expect(isObservable(observable)).toBe(true) }) test('userReactions with $lookup $record $records $index', () => { const initialValues = { array: [ { a: 1, b: 2 }, { a: 3, b: 4 }, ], } const form = attach( createForm({ initialValues, }) ) form.createArrayField({ name: 'array', }) form.createObjectField({ name: '0', basePath: 'array', }) form.createObjectField({ name: '1', basePath: 'array', }) const field0aSchema = new Schema({ name: 'array.0.a', 'x-reactions': `{{$self.title = $record.b}}`, }).toFieldProps({}) const field0bSchema = new Schema({ name: 'array.0.b', 'x-reactions': '{{$self.title = $lookup.array[0].a}}', }).toFieldProps({}) const field1aSchema = new Schema({ name: 'array.1.a', 'x-reactions': '{{$self.title = $records[$index].b}}', }).toFieldProps({}) const field1bSchema = new Schema({ name: 'array.1.b', 'x-reactions': `{{$self.title = $record.$lookup.array[$record.$index].a}}`, }).toFieldProps({}) const field0a = attach(form.createField(field0aSchema)) const field0b = attach(form.createField(field0bSchema)) const field1a = attach(form.createField(field1aSchema)) const field1b = attach(form.createField(field1bSchema)) expect(field0a.title).toEqual(2) expect(field0b.title).toEqual(1) expect(field1a.title).toEqual(4) expect(field1b.title).toEqual(3) }) test('userReactions with primary type record', () => { const initialValues = { array: [1, 2, 3], } const form = attach( createForm({ initialValues, }) ) const field0Schema = new Schema({ name: 'array.0', 'x-reactions': `{{$self.title = $record}}`, }).toFieldProps({}) const field1Schema = new Schema({ name: 'array.1', 'x-reactions': '{{$self.title = $record}}', }).toFieldProps({}) form.createArrayField({ name: 'array', }) const field0 = attach(form.createField(field0Schema)) const field1 = attach(form.createField(field1Schema)) expect(field0.title).toEqual(1) expect(field1.title).toEqual(2) }) ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Select.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Select > 下拉框组件 ## Markup Schema 同步数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="select" title="选择框" x-decorator="FormItem" x-component="Select" enum={[ { label: '选项1', value: 1 }, { label: '选项2', value: 2 }, ]} x-component-props={{ style: { width: 120, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Markup Schema 异步联动数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm, onFieldReact, FormPathPattern, Field } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { action } from '@formily/reactive' const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const useAsyncDataSource = ( pattern: FormPathPattern, service: (field: Field) => Promise<{ label: string; value: any }[]> ) => { onFieldReact(pattern, (field) => { field.loading = true service(field).then( action.bound((data) => { field.dataSource = data field.loading = false }) ) }) } const form = createForm({ effects: () => { useAsyncDataSource('select', async (field) => { const linkage = field.query('linkage').get('value') if (!linkage) return [] return new Promise((resolve) => { setTimeout(() => { if (linkage === 1) { resolve([ { label: 'AAA', value: 'aaa', }, { label: 'BBB', value: 'ccc', }, ]) } else if (linkage === 2) { resolve([ { label: 'CCC', value: 'ccc', }, { label: 'DDD', value: 'ddd', }, ]) } }, 1500) }) }) }, }) export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="linkage" title="联动选择框" x-decorator="FormItem" x-component="Select" enum={[ { label: '发请求1', value: 1 }, { label: '发请求2', value: 2 }, ]} x-component-props={{ style: { width: 120, }, }} /> <SchemaField.String name="select" title="异步选择框" x-decorator="FormItem" x-component="Select" x-component-props={{ style: { width: 120, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 同步数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { select: { type: 'string', title: '选择框', 'x-decorator': 'FormItem', 'x-component': 'Select', enum: [ { label: '选项1', value: 1 }, { label: '选项2', value: 2 }, ], 'x-component-props': { style: { width: 120, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 异步联动数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { action } from '@formily/reactive' const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const loadData = async (field) => { const linkage = field.query('linkage').get('value') if (!linkage) return [] return new Promise((resolve) => { setTimeout(() => { if (linkage === 1) { resolve([ { label: 'AAA', value: 'aaa', }, { label: 'BBB', value: 'ccc', }, ]) } else if (linkage === 2) { resolve([ { label: 'CCC', value: 'ccc', }, { label: 'DDD', value: 'ddd', }, ]) } }, 1500) }) } const useAsyncDataSource = (service) => (field) => { field.loading = true service(field).then( action.bound((data) => { field.dataSource = data field.loading = false }) ) } const form = createForm() const schema = { type: 'object', properties: { linkage: { type: 'string', title: '联动选择框', enum: [ { label: '发请求1', value: 1 }, { label: '发请求2', value: 2 }, ], 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: 120, }, }, }, select: { type: 'string', title: '异步选择框', 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: 120, }, }, 'x-reactions': ['{{useAsyncDataSource(loadData)}}'], }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} scope={{ useAsyncDataSource, loadData }} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 同步数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="select" title="选择框" dataSource={[ { label: '选项1', value: 1 }, { label: '选项2', value: 2 }, ]} decorator={[FormItem]} component={[ Select, { style: { width: 120, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 异步联动数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm, onFieldReact, FormPathPattern, FieldType, } from '@formily/core' import { FormProvider, Field } from '@formily/react' import { action } from '@formily/reactive' const useAsyncDataSource = ( pattern: FormPathPattern, service: (field: FieldType) => Promise<{ label: string; value: any }[]> ) => { onFieldReact(pattern, (field) => { field.loading = true service(field).then( action.bound((data) => { field.dataSource = data field.loading = false }) ) }) } const form = createForm({ effects: () => { useAsyncDataSource('select', async (field) => { const linkage = field.query('linkage').get('value') if (!linkage) return [] return new Promise((resolve) => { setTimeout(() => { if (linkage === 1) { resolve([ { label: 'AAA', value: 'aaa', }, { label: 'BBB', value: 'ccc', }, ]) } else if (linkage === 2) { resolve([ { label: 'CCC', value: 'ccc', }, { label: 'DDD', value: 'ddd', }, ]) } }, 1500) }) }) }, }) export default () => ( <FormProvider form={form}> <Field name="linkage" title="联动选择框" dataSource={[ { label: '发请求1', value: 1 }, { label: '发请求2', value: 2 }, ]} decorator={[FormItem]} component={[ Select, { style: { width: 120, }, }, ]} /> <Field name="select" title="异步选择框" decorator={[FormItem]} component={[ Select, { style: { width: 120, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/select ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormDialog.md: -------------------------------------------------------------------------------- ```markdown # FormDialog > Pop-up form, mainly used in simple event to open the form scene ## Markup Schema example ```tsx import React from 'react' import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd' import { createSchemaField } from '@formily/react' import { Button } from 'antd' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) export default () => { return ( <Button onClick={() => { const dialog = FormDialog('Pop-up form', () => { return ( <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String name="aaa" required title="input box 1" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="bbb" required title="input box 2" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="ccc" required title="input box 3" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="ddd" required title="input box 4" x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormDialog.Footer> <span onClick={() => { dialog.close() }} style={{ marginLeft: 4 }} > Extended copywriting(Click me to close the form) </span> </FormDialog.Footer> </FormLayout> ) }) dialog .forOpen((payload, next) => { setTimeout(() => { next({ initialValues: { aaa: '123', }, }) }, 1000) }) .forConfirm((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .forCancel((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > Click me to open the form </Button> ) } ``` ## JSON Schema case ```tsx import React from 'react' import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd' import { createSchemaField } from '@formily/react' import { Button } from 'antd' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const schema = { type: 'object', properties: { aaa: { type: 'string', title: 'input box 1', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, bbb: { type: 'string', title: 'input box 2', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ccc: { type: 'string', title: 'input box 3', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ddd: { type: 'string', title: 'input box 4', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, } export default () => { return ( <Button onClick={() => { const dialog = FormDialog('Pop-up form', () => { return ( <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} /> <FormDialog.Footer> <span onClick={() => { dialog.close() }} style={{ marginLeft: 4 }} > Extended copywriting(Click me to close the form) </span> </FormDialog.Footer> </FormLayout> ) }) dialog .forOpen((payload, next) => { setTimeout(() => { next({ initialValues: { aaa: '123', }, }) }, 1000) }) .forConfirm((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .forCancel((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > Click me to open the form </Button> ) } ``` ## Pure JSX case ```tsx import React from 'react' import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd' import { Field } from '@formily/react' import { Button } from 'antd' export default () => { return ( <Button onClick={() => { const dialog = FormDialog('Pop-up form', () => { return ( <FormLayout labelCol={6} wrapperCol={10}> <Field name="aaa" required title="input box 1" decorator={[FormItem]} component={[Input]} /> <Field name="bbb" required title="input box 2" decorator={[FormItem]} component={[Input]} /> <Field name="ccc" required title="input box 3" decorator={[FormItem]} component={[Input]} /> <Field name="ddd" required title="input box 4" decorator={[FormItem]} component={[Input]} /> <FormDialog.Footer> <span onClick={() => { dialog.close() }} style={{ marginLeft: 4 }} > Extended copywriting(Click me to close the form) </span> </FormDialog.Footer> </FormLayout> ) }) dialog .forOpen((payload, next) => { setTimeout(() => { next({ initialValues: { aaa: '123', }, }) }, 1000) }) .forConfirm((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .forCancel((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > Click me to open the form </Button> ) } ``` ## API ### FormDialog ```ts pure import { IFormProps, Form } from '@formily/core' type FormDialogRenderer = | React.ReactElement | ((form: Form) => React.ReactElement) type ModalTitle = string | number | React.ReactElement interface IFormDialog { forOpen( middleware: ( props: IFormProps, next: (props?: IFormProps) => Promise<any> ) => any ): any //Middleware interceptor, can intercept Dialog to open forConfirm( middleware: (props: Form, next: (props?: Form) => Promise<any>) => any ): any //Middleware interceptor, which can intercept Dialog confirmation forCancel( middleware: (props: Form, next: (props?: Form) => Promise<any>) => any ): any //Middleware interceptor, can intercept Dialog to cancel //Open the pop-up window to receive form attributes, you can pass in initialValues/values/effects etc. open(props: IFormProps): Promise<any> //return form data //Close the pop-up window close(): void } interface IModalProps extends ModalProps { onOk?: (event: React.MouseEvent<HTMLElement>) => void | boolean // return false can prevent onOk onCancel?: (event: React.MouseEvent<HTMLElement>) => void | boolean // return false can prevent onCancel loadingText?: React.ReactNode } interface FormDialog { (title: IModalProps, id: string, renderer: FormDialogRenderer): IFormDialog (title: IModalProps, renderer: FormDialogRenderer): IFormDialog (title: ModalTitle, id: string, renderer: FormDialogRenderer): IFormDialog (title: ModalTitle, renderer: FormDialogRenderer): IFormDialog } ``` `ModalProps` type definition reference ant design [Modal API](https://ant.design/components/modal-cn/#API) ### FormDialog.Footer No attributes, only child nodes are received ### FormDialog.Portal Receive the optional id attribute, the default value is `form-dialog`, if there are multiple prefixCls in an application, and the prefixCls in the pop-up window of different regions are different, then it is recommended to specify the id as the region-level id ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormDialog.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # FormDialog > 弹窗表单,主要用在简单的事件打开表单场景 ## Markup Schema 案例 ```tsx import React, { createContext, useContext } from 'react' import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' import { createSchemaField } from '@formily/react' import { Button } from '@alifd/next' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const Context = createContext() const PortalId = '可以传,也可以不传的ID,默认是form-dialog' export default () => { return ( <Context.Provider value="自定义上下文可以直接传到弹窗内部,只需要ID一致即可"> <FormDialog.Portal id={PortalId}> <Button onClick={() => { FormDialog('弹窗表单', PortalId, (form) => { console.log(useContext(Context)) return ( <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String name="aaa" required title="输入框1" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="bbb" required title="输入框2" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="ccc" required title="输入框3" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="ddd" required title="输入框4" x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormDialog.Footer> <span style={{ marginLeft: 4 }}> 扩展文案:{form.values.aaa} </span> </FormDialog.Footer> </FormLayout> ) }) .forOpen((payload, next) => { setTimeout(() => { next({ initialValues: { aaa: '123', }, }) }, 1000) }) .forConfirm((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .forCancel((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .open() .then(console.log) .catch(console.error) }} > 点我打开表单 </Button> </FormDialog.Portal> </Context.Provider> ) } ``` ## JSON Schema 案例 ```tsx import React from 'react' import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' import { createSchemaField } from '@formily/react' import { Button } from '@alifd/next' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const schema = { type: 'object', properties: { aaa: { type: 'string', title: '输入框1', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, bbb: { type: 'string', title: '输入框2', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ccc: { type: 'string', title: '输入框3', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ddd: { type: 'string', title: '输入框4', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, } export default () => { return ( <Button onClick={() => { FormDialog('弹窗表单', () => { return ( <FormLayout labelCol={6} wrapperCol={14}> <SchemaField schema={schema} /> <FormDialog.Footer> <span style={{ marginLeft: 4 }}>扩展文案</span> </FormDialog.Footer> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > 点我打开表单 </Button> ) } ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' import { Field } from '@formily/react' import { Button } from '@alifd/next' export default () => { return ( <Button onClick={() => { FormDialog('弹窗表单', () => { return ( <FormLayout labelCol={6} wrapperCol={14}> <Field name="aaa" required title="输入框1" decorator={[FormItem]} component={[Input]} /> <Field name="bbb" required title="输入框2" decorator={[FormItem]} component={[Input]} /> <Field name="ccc" required title="输入框3" decorator={[FormItem]} component={[Input]} /> <Field name="ddd" required title="输入框4" decorator={[FormItem]} component={[Input]} /> <FormDialog.Footer> <span style={{ marginLeft: 4 }}>扩展文案</span> </FormDialog.Footer> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > 点我打开表单 </Button> ) } ``` ## 使用 Fusion Context ```tsx import React from 'react' import { FormDialog, FormItem, Input, FormLayout } from '@formily/next' import { Field } from '@formily/react' import { Button, ConfigProvider } from '@alifd/next' export default () => { return ( <ConfigProvider locale={{ Dialog: { ok: 'OK', cancel: 'Cancel', }, }} defaultPropsConfig={{ Dialog: { isFullScreen: true, footerActions: ['cancel', 'ok'], }, }} > <Button onClick={() => { FormDialog('弹窗表单', () => { return ( <FormLayout labelCol={6} wrapperCol={14}> <Field name="aaa" required title="输入框1" decorator={[FormItem]} component={[Input]} /> <Field name="bbb" required title="输入框2" decorator={[FormItem]} component={[Input]} /> <Field name="ccc" required title="输入框3" decorator={[FormItem]} component={[Input]} /> <Field name="ddd" required title="输入框4" decorator={[FormItem]} component={[Input]} /> <FormDialog.Footer> <span style={{ marginLeft: 4 }}>扩展文案</span> </FormDialog.Footer> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > 点我打开表单 </Button> </ConfigProvider> ) } ``` ## API ### FormDialog ```ts pure import { IFormProps, Form } from '@formily/core' type FormDialogRenderer = | React.ReactElement | ((form: Form) => React.ReactElement) interface IFormDialog { forOpen( middleware: ( props: IFormProps, next: (props?: IFormProps) => Promise<any> ) => any ): any //中间件拦截器,可以拦截Dialog打开 forConfirm( middleware: (props: Form, next: (props?: Form) => Promise<any>) => any ): any //中间件拦截器,可以拦截Dialog确认 forCancel( middleware: (props: Form, next: (props?: Form) => Promise<any>) => any ): any //中间件拦截器,可以拦截Dialog取消 //打开弹窗,接收表单属性,可以传入initialValues/values/effects etc. open(props: IFormProps): Promise<any> //返回表单数据 //关闭弹窗 close(): void } interface IDialogProps extends DialogProps { onOk?: (event: React.MouseEvent) => void | boolean // return false can prevent onOk onCancel?: (event: React.MouseEvent) => void | boolean // return false can prevent onCancel loadingText?: React.ReactText } interface FormDialog { (title: IDialogProps, id: string, renderer: FormDialogRenderer): IFormDialog (title: IDialogProps, renderer: FormDialogRenderer): IFormDialog (title: ModalTitle, id: string, renderer: FormDialogRenderer): IFormDialog (title: ModalTitle, renderer: FormDialogRenderer): IFormDialog } ``` `DialogProps` 类型定义参考 fusion [Dialog API](https://fusion.design/pc/component/dialog?themeid=2#API) ### FormDialog.Footer 无属性,只接收子节点 ### FormDialog.Portal 接收可选的 id 属性,默认值为`form-dialog`,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id ```