This is page 29 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/shared/internals.ts: -------------------------------------------------------------------------------- ```typescript import { FormPath, FormPathPattern, each, pascalCase, isFn, isValid, isUndef, isEmpty, isPlainObj, isNumberLike, clone, toArr, } from '@formily/shared' import { ValidatorTriggerType, validate, parseValidatorDescriptions, } from '@formily/validator' import { autorun, batch, contains, toJS, isObservable, DataChange, reaction, untracked, } from '@formily/reactive' import { Field, ArrayField, Form, ObjectField } from '../models' import { ISpliceArrayStateProps, IExchangeArrayStateProps, IFieldResetOptions, ISearchFeedback, IFieldFeedback, INodePatch, GeneralField, IFormFeedback, LifeCycleTypes, FieldMatchPattern, FieldFeedbackTypes, } from '../types' import { isArrayField, isObjectField, isGeneralField, isDataField, isForm, isQuery, isVoidField, } from './externals' import { RESPONSE_REQUEST_DURATION, ReservedProperties, MutuallyExclusiveProperties, NumberIndexReg, GlobalState, ReadOnlyProperties, } from './constants' import { BaseField } from '../models/BaseField' const hasOwnProperty = Object.prototype.hasOwnProperty const notify = ( target: Form | Field, formType: LifeCycleTypes, fieldType: LifeCycleTypes ) => { if (isForm(target)) { target.notify(formType) } else { target.notify(fieldType) } } export const isHTMLInputEvent = (event: any, stopPropagation = true) => { if (event?.target) { if ( typeof event.target === 'object' && ('value' in event.target || 'checked' in event.target) ) return true if (stopPropagation) event.stopPropagation?.() } return false } export const getValuesFromEvent = (args: any[]) => { return args.map((event) => { if (event?.target) { if (isValid(event.target.value)) return event.target.value if (isValid(event.target.checked)) return event.target.checked return } return event }) } export const getTypedDefaultValue = (field: Field) => { if (isArrayField(field)) return [] if (isObjectField(field)) return {} } export const buildFieldPath = (field: GeneralField) => { return buildDataPath(field.form.fields, field.address) } export const buildDataPath = ( fields: Record<string, GeneralField>, pattern: FormPath ) => { let prevArray = false const segments = pattern.segments const path = segments.reduce((path: string[], key: string, index: number) => { const currentPath = path.concat(key) const currentAddress = segments.slice(0, index + 1) const current = fields[currentAddress.join('.')] if (prevArray) { if (!isVoidField(current)) { prevArray = false } return path } if (index >= segments.length - 1) { return currentPath } if (isVoidField(current)) { const parentAddress = segments.slice(0, index) const parent = fields[parentAddress.join('.')] if (isArrayField(parent) && isNumberLike(key)) { prevArray = true return currentPath } return path } else { prevArray = false } return currentPath }, []) return new FormPath(path) } export const locateNode = (field: GeneralField, address: FormPathPattern) => { field.address = FormPath.parse(address) field.path = buildFieldPath(field) field.form.indexes[field.path.toString()] = field.address.toString() return field } export const patchFieldStates = ( target: Record<string, GeneralField>, patches: INodePatch<GeneralField>[] ) => { patches.forEach(({ type, address, oldAddress, payload }) => { if (type === 'remove') { destroy(target, address, false) } else if (type === 'update') { if (payload) { target[address] = payload if (target[oldAddress] === payload) { target[oldAddress] = undefined } } if (address && payload) { locateNode(payload, address) } } }) } export const destroy = ( target: Record<string, GeneralField>, address: string, forceClear = true ) => { const field = target[address] field?.dispose() if (isDataField(field) && forceClear) { const form = field.form const path = field.path form.deleteValuesIn(path) form.deleteInitialValuesIn(path) } delete target[address] } export const patchFormValues = ( form: Form, path: Array<string | number>, source: any ) => { const update = (path: Array<string | number>, source: any) => { if (path.length) { form.setValuesIn(path, clone(source)) } else { Object.assign(form.values, clone(source)) } } const patch = (source: any, path: Array<string | number> = []) => { const targetValue = form.getValuesIn(path) const targetField = form.query(path).take() const isUnVoidField = targetField && !isVoidField(targetField) if (isUnVoidField && targetField.display === 'none') { targetField.caches.value = clone(source) return } if (allowAssignDefaultValue(targetValue, source)) { update(path, source) } else { if (isEmpty(source)) return if (GlobalState.initializing) return if (isPlainObj(targetValue) && isPlainObj(source)) { each(source, (value, key) => { patch(value, path.concat(key)) }) } else { if (targetField) { if (isUnVoidField && !targetField.selfModified) { update(path, source) } } else if (form.initialized) { update(path, source) } } } } patch(source, path) } export const matchFeedback = ( search?: ISearchFeedback, feedback?: IFormFeedback ) => { if (!search || !feedback) return false if (search.type && search.type !== feedback.type) return false if (search.code && search.code !== feedback.code) return false if (search.path && feedback.path) { if (!FormPath.parse(search.path).match(feedback.path)) return false } if (search.address && feedback.address) { if (!FormPath.parse(search.address).match(feedback.address)) return false } if (search.triggerType && search.triggerType !== feedback.triggerType) return false return true } export const queryFeedbacks = (field: Field, search?: ISearchFeedback) => { return field.feedbacks.filter((feedback) => { if (!feedback.messages?.length) return false return matchFeedback(search, { ...feedback, address: field.address?.toString(), path: field.path?.toString(), }) }) } export const queryFeedbackMessages = ( field: Field, search: ISearchFeedback ) => { if (!field.feedbacks.length) return [] return queryFeedbacks(field, search).reduce( (buf, info) => (isEmpty(info.messages) ? buf : buf.concat(info.messages)), [] ) } export const updateFeedback = (field: Field, feedback?: IFieldFeedback) => { if (!feedback) return return batch(() => { if (!field.feedbacks.length) { if (!feedback.messages?.length) { return } field.feedbacks = [feedback] } else { const searched = queryFeedbacks(field, feedback) if (searched.length) { field.feedbacks = field.feedbacks.reduce((buf, item) => { if (searched.includes(item)) { if (feedback.messages?.length) { item.messages = feedback.messages return buf.concat(item) } else { return buf } } else { return buf.concat(item) } }, []) return } else if (feedback.messages?.length) { field.feedbacks = field.feedbacks.concat(feedback) } } }) } export const validateToFeedbacks = async ( field: Field, triggerType: ValidatorTriggerType = 'onInput' ) => { const results = await validate(field.value, field.validator, { triggerType, validateFirst: field.props.validateFirst ?? field.form.props.validateFirst, context: { field, form: field.form }, }) batch(() => { each(results, (messages, type: FieldFeedbackTypes) => { field.setFeedback({ triggerType, type, code: pascalCase(`validate-${type}`), messages: messages, }) }) }) return results } export const setValidatorRule = (field: Field, name: string, value: any) => { if (!isValid(value)) return const validators = parseValidatorDescriptions(field.validator) const hasRule = validators.some((desc) => name in desc) const rule = { [name]: value, } if (hasRule) { field.validator = validators.map((desc: any) => { if (isPlainObj(desc) && hasOwnProperty.call(desc, name)) { desc[name] = value return desc } return desc }) } else { if (name === 'required') { field.validator = [rule].concat(validators) } else { field.validator = validators.concat(rule) } } } export const spliceArrayState = ( field: ArrayField, props?: ISpliceArrayStateProps ) => { const { startIndex, deleteCount, insertCount } = { startIndex: 0, deleteCount: 0, insertCount: 0, ...props, } const address = field.address.toString() const addrLength = address.length const form = field.form const fields = form.fields const fieldPatches: INodePatch<GeneralField>[] = [] const offset = insertCount - deleteCount const isArrayChildren = (identifier: string) => { return identifier.indexOf(address) === 0 && identifier.length > addrLength } const isAfterNode = (identifier: string) => { const afterStr = identifier.substring(addrLength) const number = afterStr.match(NumberIndexReg)?.[1] if (number === undefined) return false const index = Number(number) return index > startIndex + deleteCount - 1 } const isInsertNode = (identifier: string) => { const afterStr = identifier.substring(addrLength) const number = afterStr.match(NumberIndexReg)?.[1] if (number === undefined) return false const index = Number(number) return index >= startIndex && index < startIndex + insertCount } const isDeleteNode = (identifier: string) => { const preStr = identifier.substring(0, addrLength) const afterStr = identifier.substring(addrLength) const number = afterStr.match(NumberIndexReg)?.[1] if (number === undefined) return false const index = Number(number) return ( (index > startIndex && !fields[ `${preStr}${afterStr.replace(/^\.\d+/, `.${index + deleteCount}`)}` ]) || index === startIndex ) } const moveIndex = (identifier: string) => { if (offset === 0) return identifier const preStr = identifier.substring(0, addrLength) const afterStr = identifier.substring(addrLength) const number = afterStr.match(NumberIndexReg)?.[1] if (number === undefined) return identifier const index = Number(number) + offset return `${preStr}${afterStr.replace(/^\.\d+/, `.${index}`)}` } batch(() => { each(fields, (field, identifier) => { if (isArrayChildren(identifier)) { if (isAfterNode(identifier)) { const newIdentifier = moveIndex(identifier) fieldPatches.push({ type: 'update', address: newIdentifier, oldAddress: identifier, payload: field, }) } if (isInsertNode(identifier) || isDeleteNode(identifier)) { fieldPatches.push({ type: 'remove', address: identifier }) } } }) patchFieldStates(fields, fieldPatches) }) field.form.notify(LifeCycleTypes.ON_FORM_GRAPH_CHANGE) } export const exchangeArrayState = ( field: ArrayField, props: IExchangeArrayStateProps ) => { const { fromIndex, toIndex } = { fromIndex: 0, toIndex: 0, ...props, } const address = field.address.toString() const fields = field.form.fields const addrLength = address.length const fieldPatches: INodePatch<GeneralField>[] = [] const isArrayChildren = (identifier: string) => { return identifier.indexOf(address) === 0 && identifier.length > addrLength } const isDown = fromIndex < toIndex const isMoveNode = (identifier: string) => { const afterStr = identifier.slice(address.length) const number = afterStr.match(NumberIndexReg)?.[1] if (number === undefined) return false const index = Number(number) return isDown ? index > fromIndex && index <= toIndex : index < fromIndex && index >= toIndex } const isFromNode = (identifier: string) => { const afterStr = identifier.substring(addrLength) const number = afterStr.match(NumberIndexReg)?.[1] if (number === undefined) return false const index = Number(number) return index === fromIndex } const moveIndex = (identifier: string) => { const preStr = identifier.substring(0, addrLength) const afterStr = identifier.substring(addrLength) const number = afterStr.match(NumberIndexReg)[1] const current = Number(number) let index = current if (index === fromIndex) { index = toIndex } else { index += isDown ? -1 : 1 } return `${preStr}${afterStr.replace(/^\.\d+/, `.${index}`)}` } batch(() => { each(fields, (field, identifier) => { if (isArrayChildren(identifier)) { if (isMoveNode(identifier) || isFromNode(identifier)) { const newIdentifier = moveIndex(identifier) fieldPatches.push({ type: 'update', address: newIdentifier, oldAddress: identifier, payload: field, }) if (!fields[newIdentifier]) { fieldPatches.push({ type: 'remove', address: identifier, }) } } } }) patchFieldStates(fields, fieldPatches) }) field.form.notify(LifeCycleTypes.ON_FORM_GRAPH_CHANGE) } export const cleanupArrayChildren = (field: ArrayField, start: number) => { const address = field.address.toString() const fields = field.form.fields const isArrayChildren = (identifier: string) => { return ( identifier.indexOf(address) === 0 && identifier.length > address.length ) } const isNeedCleanup = (identifier: string) => { const afterStr = identifier.slice(address.length) const numStr = afterStr.match(NumberIndexReg)?.[1] if (numStr === undefined) return false const index = Number(numStr) return index >= start } batch(() => { each(fields, (field, identifier) => { if (isArrayChildren(identifier) && isNeedCleanup(identifier)) { field.destroy() } }) }) } export const cleanupObjectChildren = (field: ObjectField, keys: string[]) => { if (keys.length === 0) return const address = field.address.toString() const fields = field.form.fields const isObjectChildren = (identifier: string) => { return ( identifier.indexOf(address) === 0 && identifier.length > address.length ) } const isNeedCleanup = (identifier: string) => { const afterStr = identifier.slice(address.length) const key = afterStr.match(/^\.([^.]+)/)?.[1] if (key === undefined) return false return keys.includes(key) } batch(() => { each(fields, (field, identifier) => { if (isObjectChildren(identifier) && isNeedCleanup(identifier)) { field.destroy() } }) }) } export const initFieldUpdate = batch.scope.bound((field: GeneralField) => { const form = field.form const updates = FormPath.ensureIn(form, 'requests.updates', []) const indexes = FormPath.ensureIn(form, 'requests.updateIndexes', {}) for (let index = 0; index < updates.length; index++) { const { pattern, callbacks } = updates[index] let removed = false if (field.match(pattern)) { callbacks.forEach((callback) => { field.setState(callback) }) if (!pattern.isWildMatchPattern && !pattern.isMatchPattern) { updates.splice(index--, 1) removed = true } } if (!removed) { indexes[pattern.toString()] = index } else { delete indexes[pattern.toString()] } } }) export const subscribeUpdate = ( form: Form, pattern: FormPath, callback: (...args: any[]) => void ) => { const updates = FormPath.ensureIn(form, 'requests.updates', []) const indexes = FormPath.ensureIn(form, 'requests.updateIndexes', {}) const id = pattern.toString() const current = indexes[id] if (isValid(current)) { if ( updates[current] && !updates[current].callbacks.some((fn: any) => fn.toString() === callback.toString() ? fn === callback : false ) ) { updates[current].callbacks.push(callback) } } else { indexes[id] = updates.length updates.push({ pattern, callbacks: [callback], }) } } export const deserialize = (model: any, setter: any) => { if (!model) return if (isFn(setter)) { setter(model) } else { for (let key in setter) { if (!hasOwnProperty.call(setter, key)) continue if (ReadOnlyProperties[key] || ReservedProperties[key]) continue const MutuallyExclusiveKey = MutuallyExclusiveProperties[key] if ( MutuallyExclusiveKey && hasOwnProperty.call(setter, MutuallyExclusiveKey) && !isValid(setter[MutuallyExclusiveKey]) ) continue const value = setter[key] if (isFn(value)) continue model[key] = value } } return model } export const serialize = (model: any, getter?: any) => { if (isFn(getter)) { return getter(model) } else { const results = {} for (let key in model) { if (!hasOwnProperty.call(model, key)) continue if (ReservedProperties[key]) continue if (key === 'address' || key === 'path') { results[key] = model[key].toString() continue } const value = model[key] if (isFn(value)) continue results[key] = toJS(value) } return results } } export const createChildrenFeedbackFilter = (field: Field) => { const identifier = field.address?.toString() return ({ address }: IFormFeedback) => { return address === identifier || address.indexOf(identifier + '.') === 0 } } export const createStateSetter = (model: any) => { return batch.bound((setter?: any) => deserialize(model, setter)) } export const createStateGetter = (model: any) => { return (getter?: any) => serialize(model, getter) } export const createBatchStateSetter = (form: Form) => { return batch.bound((pattern: FieldMatchPattern, payload?: any) => { if (isQuery(pattern)) { pattern.forEach((field) => { field.setState(payload) }) } else if (isGeneralField(pattern)) { pattern.setState(payload) } else { let matchCount = 0, path = FormPath.parse(pattern) form.query(path).forEach((field) => { field.setState(payload) matchCount++ }) if (matchCount === 0 || path.isWildMatchPattern) { subscribeUpdate(form, path, payload) } } }) } export const createBatchStateGetter = (form: Form) => { return (pattern: FieldMatchPattern, payload?: any) => { if (isQuery(pattern)) { return pattern.take(payload) } else if (isGeneralField(pattern)) { return (pattern as any).getState(payload) } else { return form.query(pattern).take((field: any) => { return field.getState(payload) }) } } } export const triggerFormInitialValuesChange = ( form: Form, change: DataChange ) => { if (Array.isArray(change.object) && change.key === 'length') return if ( contains(form.initialValues, change.object) || form.initialValues === change.value ) { if (change.type === 'add' || change.type === 'set') { patchFormValues(form, change.path.slice(1), change.value) } if (form.initialized) { form.notify(LifeCycleTypes.ON_FORM_INITIAL_VALUES_CHANGE) } } } export const triggerFormValuesChange = (form: Form, change: DataChange) => { if (Array.isArray(change.object) && change.key === 'length') return if ( (contains(form.values, change.object) || form.values === change.value) && form.initialized ) { form.notify(LifeCycleTypes.ON_FORM_VALUES_CHANGE) } } export const setValidating = (target: Form | Field, validating: boolean) => { clearTimeout(target.requests.validate) if (validating) { target.requests.validate = setTimeout(() => { batch(() => { target.validating = validating notify( target, LifeCycleTypes.ON_FORM_VALIDATING, LifeCycleTypes.ON_FIELD_VALIDATING ) }) }, RESPONSE_REQUEST_DURATION) notify( target, LifeCycleTypes.ON_FORM_VALIDATE_START, LifeCycleTypes.ON_FIELD_VALIDATE_START ) } else { if (target.validating !== validating) { target.validating = validating } notify( target, LifeCycleTypes.ON_FORM_VALIDATE_END, LifeCycleTypes.ON_FIELD_VALIDATE_END ) } } export const setSubmitting = (target: Form | Field, submitting: boolean) => { clearTimeout(target.requests.submit) if (submitting) { target.requests.submit = setTimeout(() => { batch(() => { target.submitting = submitting notify( target, LifeCycleTypes.ON_FORM_SUBMITTING, LifeCycleTypes.ON_FIELD_SUBMITTING ) }) }, RESPONSE_REQUEST_DURATION) notify( target, LifeCycleTypes.ON_FORM_SUBMIT_START, LifeCycleTypes.ON_FIELD_SUBMIT_START ) } else { if (target.submitting !== submitting) { target.submitting = submitting } notify( target, LifeCycleTypes.ON_FORM_SUBMIT_END, LifeCycleTypes.ON_FIELD_SUBMIT_END ) } } export const setLoading = (target: Form | Field, loading: boolean) => { clearTimeout(target.requests.loading) if (loading) { target.requests.loading = setTimeout(() => { batch(() => { target.loading = loading notify( target, LifeCycleTypes.ON_FORM_LOADING, LifeCycleTypes.ON_FIELD_LOADING ) }) }, RESPONSE_REQUEST_DURATION) } else if (target.loading !== loading) { target.loading = loading } } export const batchSubmit = async <T>( target: Form | Field, onSubmit?: (values: any) => Promise<T> | void ): Promise<T> => { const getValues = (target: Form | Field) => { if (isForm(target)) { return toJS(target.values) } return toJS(target.value) } target.setSubmitting(true) try { notify( target, LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_START, LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_START ) await target.validate() notify( target, LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_SUCCESS, LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_SUCCESS ) } catch (e) { notify( target, LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_FAILED, LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_FAILED ) } notify( target, LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_END, LifeCycleTypes.ON_FIELD_SUBMIT_VALIDATE_END ) let results: any try { if (target.invalid) { throw target.errors } if (isFn(onSubmit)) { results = await onSubmit(getValues(target)) } else { results = getValues(target) } notify( target, LifeCycleTypes.ON_FORM_SUBMIT_SUCCESS, LifeCycleTypes.ON_FIELD_SUBMIT_SUCCESS ) } catch (e) { target.setSubmitting(false) notify( target, LifeCycleTypes.ON_FORM_SUBMIT_FAILED, LifeCycleTypes.ON_FIELD_SUBMIT_FAILED ) notify( target, LifeCycleTypes.ON_FORM_SUBMIT, LifeCycleTypes.ON_FIELD_SUBMIT ) throw e } target.setSubmitting(false) notify(target, LifeCycleTypes.ON_FORM_SUBMIT, LifeCycleTypes.ON_FIELD_SUBMIT) return results } const shouldValidate = (field: Field) => { const validatePattern = field.props.validatePattern ?? field.form.props.validatePattern ?? ['editable'] const validateDisplay = field.props.validateDisplay ?? field.form.props.validateDisplay ?? ['visible'] return ( validatePattern.includes(field.pattern) && validateDisplay.includes(field.display) ) } export const batchValidate = async ( target: Form | Field, pattern: FormPathPattern, triggerType?: ValidatorTriggerType ) => { if (isForm(target)) target.setValidating(true) else { if (!shouldValidate(target)) return } const tasks = [] target.query(pattern).forEach((field) => { if (!isVoidField(field)) { tasks.push(validateSelf(field, triggerType, field === target)) } }) await Promise.all(tasks) if (isForm(target)) target.setValidating(false) if (target.invalid) { notify( target, LifeCycleTypes.ON_FORM_VALIDATE_FAILED, LifeCycleTypes.ON_FIELD_VALIDATE_FAILED ) throw target.errors } notify( target, LifeCycleTypes.ON_FORM_VALIDATE_SUCCESS, LifeCycleTypes.ON_FIELD_VALIDATE_SUCCESS ) } export const batchReset = async ( target: Form | Field, pattern: FormPathPattern, options?: IFieldResetOptions ) => { const tasks = [] target.query(pattern).forEach((field) => { if (!isVoidField(field)) { tasks.push(resetSelf(field, options, target === field)) } }) if (isForm(target)) { target.modified = false } notify(target, LifeCycleTypes.ON_FORM_RESET, LifeCycleTypes.ON_FIELD_RESET) await Promise.all(tasks) } export const validateSelf = batch.bound( async (target: Field, triggerType?: ValidatorTriggerType, noEmit = false) => { const start = () => { setValidating(target, true) } const end = () => { setValidating(target, false) if (noEmit) return if (target.selfValid) { target.notify(LifeCycleTypes.ON_FIELD_VALIDATE_SUCCESS) } else { target.notify(LifeCycleTypes.ON_FIELD_VALIDATE_FAILED) } } if (!shouldValidate(target)) return {} start() if (!triggerType) { const allTriggerTypes = parseValidatorDescriptions( target.validator ).reduce( (types, desc) => types.indexOf(desc.triggerType) > -1 ? types : types.concat(desc.triggerType), [] ) const results = {} for (let i = 0; i < allTriggerTypes.length; i++) { const payload = await validateToFeedbacks(target, allTriggerTypes[i]) each(payload, (result, key) => { results[key] = results[key] || [] results[key] = results[key].concat(result) }) } end() return results } const results = await validateToFeedbacks(target, triggerType) end() return results } ) export const resetSelf = batch.bound( async (target: Field, options?: IFieldResetOptions, noEmit = false) => { const typedDefaultValue = getTypedDefaultValue(target) target.modified = false target.selfModified = false target.visited = false target.feedbacks = [] target.inputValue = typedDefaultValue target.inputValues = [] target.caches = {} if (!isUndef(target.value)) { if (options?.forceClear) { target.value = typedDefaultValue } else { const initialValue = target.initialValue target.value = toJS( !isUndef(initialValue) ? initialValue : typedDefaultValue ) } } if (!noEmit) { target.notify(LifeCycleTypes.ON_FIELD_RESET) } if (options?.validate) { return await validateSelf(target) } } ) export const modifySelf = (target: Field) => { if (target.selfModified) return target.selfModified = true target.modified = true let parent = target.parent while (parent) { if (isDataField(parent)) { if (parent.modified) return parent.modified = true } parent = parent.parent } target.form.modified = true } export const getValidFormValues = (values: any) => { if (isObservable(values)) return values return clone(values || {}) } export const getValidFieldDefaultValue = (value: any, initialValue: any) => { if (allowAssignDefaultValue(value, initialValue)) return clone(initialValue) return value } export const allowAssignDefaultValue = (target: any, source: any) => { const isValidTarget = !isUndef(target) const isValidSource = !isUndef(source) if (!isValidTarget) { return isValidSource } if (typeof target === typeof source) { if (target === '') return false if (target === 0) return false } const isEmptyTarget = target !== null && isEmpty(target, true) const isEmptySource = source !== null && isEmpty(source, true) if (isEmptyTarget) { return !isEmptySource } return false } export const createReactions = (field: GeneralField) => { const reactions = toArr(field.props.reactions) field.form.addEffects(field, () => { reactions.forEach((reaction) => { if (isFn(reaction)) { field.disposers.push( autorun( batch.scope.bound(() => { if (field.destroyed) return reaction(field) }) ) ) } }) }) } export const createReaction = <T>( tracker: () => T, scheduler?: (value: T) => void ) => { return reaction(tracker, untracked.bound(scheduler)) } export const initializeStart = () => { GlobalState.initializing = true } export const initializeEnd = () => { batch.endpoint(() => { GlobalState.initializing = false }) } export const getArrayParent = (field: BaseField, index = field.index) => { if (index > -1) { let parent: any = field.parent while (parent) { if (isArrayField(parent)) return parent if (parent === field.form) return parent = parent.parent } } } export const getObjectParent = (field: BaseField) => { let parent: any = field.parent while (parent) { if (isArrayField(parent)) return if (isObjectField(parent)) return parent if (parent === field.form) return parent = parent.parent } } ``` -------------------------------------------------------------------------------- /packages/vue/src/__tests__/schema.json.spec.ts: -------------------------------------------------------------------------------- ```typescript import { createForm, Field } from '@formily/core' import { observer } from '@formily/reactive-vue' import { Schema } from '@formily/json-schema' import { fireEvent, render, waitFor } from '@testing-library/vue' import { mount } from '@vue/test-utils' import Vue, { FunctionalComponentOptions } from 'vue' import { FormProvider, createSchemaField, RecursionField, } from '../vue2-components' import { connect, mapProps, mapReadPretty, useField, useFieldSchema } from '../' import { defineComponent, h } from 'vue-demi' Vue.component('FormProvider', FormProvider) const Input: FunctionalComponentOptions = { functional: true, render(h, context) { return h('input', { class: 'input', attrs: { value: context.props.value, 'data-testid': 'input', }, on: { input: context.listeners.change, }, }) }, } const Input2: FunctionalComponentOptions = { functional: true, render(h, context) { return h('input', { class: 'input2', attrs: { value: context.props.value, 'data-testid': 'input2', }, on: { input: context.listeners.change, }, }) }, } const FormItem: FunctionalComponentOptions = { functional: true, render(h, { props, slots, data }) { return h( 'div', { ...data, style: { width: '300px', height: '30px', background: 'yellow', }, attrs: { 'data-testid': 'formitem', ...data.attrs, }, }, [props.label || 'unknown ', slots().default] ) }, } const ArrayItems = observer( defineComponent({ setup() { const fieldRef = useField<Field>() const schemaRef = useFieldSchema() return () => { const field = fieldRef.value const schema = schemaRef.value const items = field.value?.map?.((item, index) => { return h(RecursionField, { props: { schema: schema.items, name: index }, }) }) return h('div', { attrs: { 'data-testid': 'array-items' } }, [items]) } }, }) ) const Previewer: FunctionalComponentOptions = { functional: true, render(h, context) { return h( 'div', { attrs: { 'data-testid': 'previewer', }, }, context.children ) }, } const Previewer2: FunctionalComponentOptions = { functional: true, render(h, context) { return h( 'div', { attrs: { 'data-testid': 'previewer2', }, }, [context.scopedSlots.content({})] ) }, } const Previewer3: FunctionalComponentOptions = { functional: true, render(h, context) { return h( 'div', { attrs: { 'data-testid': 'previewer3', }, }, [ context.scopedSlots.default({ slotProp: '123', }), ] ) }, } const Previewer4: FunctionalComponentOptions = { functional: true, render(h, context) { return h( 'div', { attrs: { 'data-testid': 'previewer4', }, }, [ context.scopedSlots.content({ slotProp: '123', }), ] ) }, } const Previewer5: FunctionalComponentOptions = { functional: true, render(h, context) { return h( 'div', { attrs: { 'data-testid': 'previewer5', }, }, context.slots()?.append ) }, } describe('json schema field', () => { test('string field', () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Input, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', default: '123', 'x-component': 'Input', }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('input')).toBeVisible() expect(queryByTestId('input').getAttribute('value')).toEqual('123') }) test('object field', () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Input, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'object', properties: { string: { type: 'string', 'x-component': 'Input', }, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('input')).toBeVisible() }) }) describe('x-content', () => { test('default slot', () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Previewer, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer', 'x-content': '123', }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer')).toBeVisible() expect(queryByTestId('previewer').textContent).toEqual('123') }) test('default slot with component', () => { const form = createForm() const Content = { render(h) { return h('span', '123') }, } const { SchemaField } = createSchemaField({ components: { Previewer, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer', 'x-content': Content, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer')).toBeVisible() expect(queryByTestId('previewer').textContent).toEqual('123') }) test('default slot with name default', () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Previewer, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer', 'x-content': { default: '123', }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer')).toBeVisible() expect(queryByTestId('previewer').textContent).toEqual('123') }) test('default slot with name default and component', () => { const form = createForm() const Content = { render(h) { return h('span', '123') }, } const { SchemaField } = createSchemaField({ components: { Previewer, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer', 'x-content': { default: Content, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer')).toBeVisible() expect(queryByTestId('previewer').textContent).toEqual('123') }) test('named slot', () => { const form = createForm() const Content = { render(h) { return h('span', '123') }, } const { SchemaField } = createSchemaField({ components: { Previewer2, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer2', 'x-content': { content: Content, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer2')).toBeVisible() expect(queryByTestId('previewer2').textContent).toEqual('123') }) test('named slot with scope', () => { const form = createForm() const Content = { render(h) { return h('span', '123') }, } const { SchemaField } = createSchemaField({ components: { Previewer2, }, scope: { Content, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer2', 'x-content': { content: '{{Content}}', }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer2')).toBeVisible() expect(queryByTestId('previewer2').textContent).toEqual('123') }) test('named slot in void field', () => { const form = createForm() const Content = { render(h) { return h('span', '123') }, } const { SchemaField } = createSchemaField({ components: { Previewer2, }, scope: { Content, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'void', 'x-component': 'Previewer2', 'x-content': { content: '{{Content}}', }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer2')).toBeVisible() expect(queryByTestId('previewer2').textContent).toEqual('123') }) test('scoped slot', () => { const form = createForm() const Content = { functional: true, render(h, context) { return h('span', context.props.slotProp) }, } const { SchemaField } = createSchemaField({ components: { Previewer3, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer3', 'x-content': Content, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer3')).toBeVisible() expect(queryByTestId('previewer3').textContent).toEqual('123') }) test('scoped slot with scope', () => { const form = createForm() const Content = { functional: true, render(h, context) { return h('span', context.props.slotProp) }, } const { SchemaField } = createSchemaField({ components: { Previewer3, }, scope: { Content, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer3', 'x-content': '{{Content}}', }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer3')).toBeVisible() expect(queryByTestId('previewer3').textContent).toEqual('123') }) test('scoped slot with name default', () => { const form = createForm() const Content = { functional: true, render(h, context) { return h('span', context.props.slotProp) }, } const { SchemaField } = createSchemaField({ components: { Previewer3, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer3', 'x-content': { default: Content, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer3')).toBeVisible() expect(queryByTestId('previewer3').textContent).toEqual('123') }) test('scoped slot with name other', () => { const form = createForm() const Content = { functional: true, render(h, context) { return h('span', context.props.slotProp) }, } const { SchemaField } = createSchemaField({ components: { Previewer4, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer4', 'x-content': { content: Content, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer4')).toBeVisible() expect(queryByTestId('previewer4').textContent).toEqual('123') }) test('scoped slot with connect', () => { const form = createForm() const ConnectedComponent = connect( defineComponent({ render(h) { return h( 'div', { attrs: { 'data-testid': 'ConnectedComponent', }, }, [ this.$scopedSlots.default({ slotProp: '123', }), ] ) }, }), mapProps((props, field) => { return { ...props, } }) ) const scopeSlotComponent = { functional: true, render(h, context) { return h('span', context.props.slotProp) }, } const { SchemaField } = createSchemaField({ components: { ConnectedComponent, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', name: 'ConnectedComponent', 'x-component': 'ConnectedComponent', 'x-content': { default: scopeSlotComponent, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('ConnectedComponent')).toBeVisible() expect(queryByTestId('ConnectedComponent').textContent).toEqual('123') }) test('scoped slot with connect and readPretty', () => { const form = createForm() const ConnectedWithMapReadPretty = connect( defineComponent({ render(h) { return h( 'div', { attrs: { 'data-testid': 'ConnectedWithMapReadPretty', }, }, [ this.$scopedSlots.withMapReadPretty({ slotProp: '123', }), ] ) }, }), mapProps((props, field) => { return { ...props, } }), mapReadPretty({ render(h) { return h('div', 'read pretty') }, }) ) const scopeSlotComponent = { functional: true, render(h, context) { return h('span', context.props.slotProp) }, } const { SchemaField } = createSchemaField({ components: { ConnectedWithMapReadPretty, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', name: 'ConnectedWithMapReadPretty', 'x-component': 'ConnectedWithMapReadPretty', 'x-content': { withMapReadPretty: scopeSlotComponent, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('ConnectedWithMapReadPretty')).toBeVisible() expect(queryByTestId('ConnectedWithMapReadPretty').textContent).toEqual( '123' ) }) test('slot compitible', () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Previewer5, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Previewer5', 'x-content': { append: '123', }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer5')).toBeVisible() expect(queryByTestId('previewer5').textContent).toEqual('123') }) test('wrong x-content will be ignore', () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Previewer, }, }) const { queryAllByTestId, container } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'object', properties: { input1: { type: 'string', 'x-component': 'Previewer', 'x-content': { default: { someAttr: '123', }, }, }, input2: { type: 'string', 'x-component': 'Previewer', 'x-content': { default: null, }, }, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) queryAllByTestId('previewer').forEach((el) => expect(el).toBeVisible()) queryAllByTestId('previewer').forEach((el) => expect(el.textContent).toEqual('') ) }) }) describe('x-slot', () => { test('x-slot works in void field properties', () => { const form = createForm() const Content = { render(h) { return h('span', '123') }, } const { SchemaField } = createSchemaField({ components: { Previewer4, Content, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'void', 'x-component': 'Previewer4', properties: { content: { type: 'void', 'x-component': 'Content', 'x-slot': 'content', }, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer4')).toBeVisible() expect(queryByTestId('previewer4').textContent).toEqual('123') }) test('x-slot works in object field properties', () => { const form = createForm() const Content = { render(h) { return h('span', '123') }, } const { SchemaField } = createSchemaField({ components: { Previewer4, Content, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'object', 'x-component': 'Previewer4', properties: { content: { type: 'void', 'x-component': 'Content', 'x-slot': 'content', }, }, }), } }, template: `<FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('previewer4')).toBeVisible() expect(queryByTestId('previewer4').textContent).toEqual('123') }) }) describe('scope', () => { test('scope in <SchemaField> prop', async () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Input, Input2, Previewer, }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: { type: 'object', properties: { input1: { type: 'string', 'x-component': 'Input', 'x-reactions': { target: 'input2', fulfill: { state: { value: '{{ test }}', }, }, }, }, input2: { type: 'string', 'x-component': 'Input2', }, }, }, } }, template: `<FormProvider :form="form"> <SchemaField :schema="schema" :scope="{ test: '123' }" /> </FormProvider>`, }) expect(queryByTestId('input2').getAttribute('value')).toEqual('123') }) test('scope in options of createSchemaField', async () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Input, Input2, Previewer, }, scope: { test: '123', }, }) const { queryByTestId } = render({ components: { SchemaField }, data() { return { form, schema: { type: 'object', properties: { input1: { type: 'string', 'x-component': 'Input', 'x-reactions': { target: 'input2', fulfill: { state: { value: '{{ test }}', }, }, }, }, input2: { type: 'string', 'x-component': 'Input2', }, }, }, } }, template: `<FormProvider :form="form"> <SchemaField :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('input2').getAttribute('value')).toEqual('123') }) }) describe('expression', () => { test('expression x-visible', async () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Input, Input2, Previewer, }, }) const wrapper = mount( { components: { SchemaField }, data() { return { form, schema: { type: 'object', properties: { input: { type: 'string', 'x-component': 'Input', }, input2: { type: 'string', 'x-component': 'Input2', 'x-visible': '{{$form.values.input === "123"}}', }, }, }, } }, template: `<FormProvider :form="form"> <SchemaField :schema="schema" /> </FormProvider>`, }, { attachToDocument: true, } ) expect(wrapper.find('.input').exists()).toBeTruthy() expect(wrapper.find('.input2').exists()).not.toBeTruthy() form.values.input = '123' await waitFor(() => expect(wrapper.find('.input2').exists()).toBeTruthy()) wrapper.destroy() }) test('expression x-value', async () => { const form = createForm({ values: { input: 1, }, }) const { SchemaField } = createSchemaField({ components: { Input, Input2, Previewer, }, }) const wrapper = mount( { components: { SchemaField }, data() { return { form, schema: { type: 'object', properties: { input: { type: 'string', 'x-component': 'Input', }, input2: { type: 'string', 'x-component': 'Input2', 'x-value': '{{$form.values.input * 10}}', }, }, }, } }, template: `<FormProvider :form="form"> <SchemaField :schema="schema" /> </FormProvider>`, }, { attachToDocument: true } ) expect(wrapper.find('.input2').attributes().value).toEqual('10') form.values.input = 10 await waitFor(() => expect(wrapper.find('.input2').attributes().value).toEqual('100') ) wrapper.destroy() }) }) describe('schema controlled', () => { test('view update correctly when schema changed', async () => { const form = createForm({}) const { SchemaField } = createSchemaField({ components: { Input, Input2, }, }) const component = defineComponent({ components: { SchemaField }, data() { return { form, schema: { type: 'object', properties: { input: { type: 'string', 'x-component': 'Input', }, input2: { type: 'string', 'x-component': 'Input2', }, }, }, } }, methods: { changeSchema() { this.schema = { type: 'object', properties: { input2: { type: 'string', 'x-component': 'Input2', }, }, } }, }, template: `<FormProvider :form="form"> <SchemaField :schema="schema" /> <button @click="changeSchema()">changeSchema</button> </FormProvider>`, }) const { queryByTestId, getByText } = render(component) expect(queryByTestId('input')).toBeVisible() expect(queryByTestId('input2')).toBeVisible() getByText('changeSchema').click() await waitFor(() => { expect(queryByTestId('input2')).toBeVisible() expect(queryByTestId('input')).toBeNull() }) }) test('view updated correctly with schema fragment changed', async () => { const form = createForm({}) const { SchemaField } = createSchemaField({ components: { Input, Input2, ArrayItems, }, }) const frag1 = { type: 'object', properties: { input1: { type: 'string', 'x-component': 'Input', }, }, } const frag2 = { type: 'array', 'x-component': 'ArrayItems', items: { type: 'object', properties: { input2: { type: 'string', 'x-component': 'Input2', }, }, }, } const component = defineComponent({ components: { SchemaField }, data() { return { form, schema: { type: 'object', properties: { input: frag1, }, }, } }, methods: { changeSchema() { this.form.clearFormGraph('input') this.form.deleteValuesIn('input') this.schema = { type: 'object', properties: { input: frag2, }, } }, }, template: `<FormProvider :form="form"> <SchemaField :schema="schema" /> <button @click="changeSchema()">changeSchema</button> </FormProvider>`, }) const { queryByTestId, getByText } = render(component) expect(queryByTestId('input')).toBeVisible() expect(queryByTestId('array-items')).toBeNull() getByText('changeSchema').click() await waitFor(() => { expect(queryByTestId('input')).toBeNull() expect(queryByTestId('array-items')).toBeVisible() }) }) }) describe('x-decorator', () => { test('x-decorator-props', async () => { const form = createForm() const { SchemaField } = createSchemaField({ components: { Input, FormItem, }, }) const atBlurFn = jest.fn() const onClickFn = jest.fn() const atClickFn = jest.fn() const { queryByTestId, getByText } = render({ components: { SchemaField }, data() { return { form, schema: new Schema({ type: 'string', 'x-component': 'Input', 'x-component-props': { '@blur': function atBlur() { atBlurFn() }, }, 'x-decorator': 'FormItem', 'x-decorator-props': { label: 'Label ', onClick: function onClick() { onClickFn() }, '@click': function atClick() { atClickFn() }, }, }), } }, template: ` <FormProvider :form="form"> <SchemaField name="string" :schema="schema" /> </FormProvider>`, }) expect(queryByTestId('formitem')).toBeVisible() await fireEvent.click(getByText('Label')) expect(atClickFn).toBeCalledTimes(1) expect(onClickFn).toBeCalledTimes(0) }) }) ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormItem.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # FormItem > 全新的 FormItem 组件,相比于 Antd 的 FormItem,它支持的功能更多,同时它的定位是纯样式组件,不管理表单状态,所以也会更轻量,更方便定制 ## Markup Schema 案例 ```tsx import React from 'react' import { Input, Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, Select, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="输入框" x-decorator="FormItem" x-component="Input" required /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { style: { width: 240, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="input" title="输入框" required decorator={[FormItem]} component={[ Input, { style: { width: 240, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 常用属性案例 ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Void x-component="Title" x-component-props={{ text: 'label为空时的展示' }} /> <SchemaField.String x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.String title="" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: '冒号' }} /> <SchemaField.String title="默认" x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="无冒号(colon=false)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ colon: false, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: '固定宽度设置' }} /> <SchemaField.String title="固定label宽度(labelWidth)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.String title="固定label宽度(labelWidth)溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出溢出" description="描述描述" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, tooltip: '提示提示', tooltipLayout: 'text', }} /> <SchemaField.String title="固定label宽度(labelWidth)换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行换行" description="描述描述" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelWrap: true, tooltip: '提示提示', }} /> <SchemaField.String title="固定内容宽度(wrapperWidth)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 300, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: '对齐方式设置' }} /> <SchemaField.String title="label左对齐(labelAlign=left)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelAlign: 'left', }} /> <SchemaField.String title="label右对齐(labelAlign=right默认)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelAlign: 'right', }} /> <SchemaField.String title="内容左对齐(wrapperAlign=left默认)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 240, wrapperAlign: 'left', }} /> <SchemaField.String title="内容右对齐(wrapperAlign=right)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 240, wrapperAlign: 'right', }} /> <SchemaField.String title="tooltip" x-decorator="FormItem" x-component="Input" x-decorator-props={{ tooltip: 'tooltip', }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: '是否撑满' }} /> <SchemaField.String title="默认不撑满(fullness=false)" x-decorator="FormItem" x-component="Select" /> <SchemaField.String title="撑满(fullness=true)" x-decorator="FormItem" x-component="Select" x-decorator-props={{ fullness: true, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: '辅助信息' }} /> <SchemaField.String title="必填星号" x-decorator="FormItem" x-component="Input" x-decorator-props={{ asterisk: true, labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="前缀" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonBefore: 'addonBefore', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="后缀" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'addonAfter', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="帮助信息feedbackText" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackText: 'feedbackText', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="额外信息extra" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackText: 'feedbackText', extra: 'extra', labelCol: 6, wrapperCol: 10, }} /> </SchemaField> </FormProvider> ) } ``` ## 必填样式 ```tsx import React, { useState } from 'react' import { Input, FormItem, FormLayout } from '@formily/antd' import { Radio, ConfigProvider } from 'antd' import zhCN from 'antd/es/locale/zh_CN' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => { const [requiredMark, setRequiredMark] = useState(true) return ( <ConfigProvider locale={zhCN}> <p> Required Mark: <Radio.Group value={requiredMark} onChange={(e) => setRequiredMark(e.target.value)} > <Radio.Button value="optional">optional</Radio.Button> <Radio.Button value={true}>true</Radio.Button> <Radio.Button value={false}>false</Radio.Button> </Radio.Group> </p> <FormProvider form={form}> <FormLayout requiredMark={requiredMark}> <SchemaField> <SchemaField.String title="我是必填项" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="我是选填项" x-decorator="FormItem" x-component="Input" /> <SchemaField.String default="字段不可编辑时,始终隐藏必填/选填标识" x-editable={false} x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="我是必填项" required default="不可编辑" x-editable={false} x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="我是选填项" default="不可编辑" x-editable={false} x-decorator="FormItem" x-component="Input" /> </SchemaField> </FormLayout> </FormProvider> </ConfigProvider> ) } ``` ## 无边框案例 设置去除组件边框 ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required x-decorator-props={{ bordered: false, }} /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required x-decorator-props={{ bordered: false, }} /> </SchemaField> </FormProvider> ) } ``` ## 内嵌模式案例 设置表单组件为内嵌模式 ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required x-decorator-props={{ inset: true, }} /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required x-decorator-props={{ inset: false, }} /> </SchemaField> </FormProvider> ) } ``` ## 反馈信息定制案例 可通过 `feedbackIcon` 传入指定反馈的按钮 ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, TimePicker, FormItem, FormLayout, NumberPicker, Switch, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { CheckCircleFilled, LoadingOutlined } from '@ant-design/icons' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, TimePicker, NumberPicker, Switch, Radio, FormItem, Title, FormLayout, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String title="错误状态(feedbackStatus=error)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'error', }} /> <SchemaField.String title="警告状态(feedbackStatus=warning)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'warning', }} /> <SchemaField.String title="成功状态(feedbackStatus=success)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="加载状态(feedbackStatus=pending)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'pending', feedbackIcon: <LoadingOutlined style={{ color: '#1890ff' }} />, }} /> <SchemaField.String title="禁用错误状态边框样式(feedbackStatus=error)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ enableOutlineFeedback: false, feedbackStatus: 'error', }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: '反馈信息的布局' }} /> <SchemaField.String title="紧凑模式required" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ feedbackLayout: 'terse', }} /> <SchemaField.String title="紧凑模式有feedback(feedbackLayout=terse)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'terse', }} /> <SchemaField.String title="紧凑模式无feedback(feedbackLayout=terse)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackLayout: 'terse', }} /> <SchemaField.String title="松散模式(feedbackLayout=loose)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'loose', }} /> <SchemaField.String title="弹出模式(feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'warning', feedbackText: 'warning message', feedbackLayout: 'popover', }} /> <SchemaField.String title="弹出模式(feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'popover', }} /> <SchemaField.String title="弹出模式(feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'success', feedbackText: 'success message', feedbackLayout: 'popover', }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: '组件的适配情况' }} /> <SchemaField.Void x-component="FormLayout" x-component-props={{ layout: 'vertical' }} > <SchemaField.String title="Select" x-decorator="FormItem" x-component="Select" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker" x-decorator="FormItem" x-component="DatePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.RangePicker" x-decorator="FormItem" x-component="DatePicker.RangePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.YearPicker" x-decorator="FormItem" x-component="DatePicker.YearPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.MonthPicker" x-decorator="FormItem" x-component="DatePicker.MonthPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.TimePicker" x-decorator="FormItem" x-component="TimePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="Cascader" x-decorator="FormItem" x-component="Cascader" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> </SchemaField.Void> </SchemaField> </FormProvider> ) } ``` ## 尺寸控制案例 ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/antd' import { createForm, onFieldChange } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Div = (props) => <div {...props} /> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Div, }, }) const form = createForm({ values: { size: 'default', }, effects: () => { onFieldChange('size', ['value'], (field, form) => { form.setFieldState('sizeWrap.*', (state) => { if (state.decorator[1]) { state.decorator[1].size = field.value } }) }) }, }) export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="size" title="Radio.Group" x-decorator="FormItem" x-component="Radio.Group" enum={[ { value: 'small', label: 'Small' }, { value: 'default', label: 'Default' }, { value: 'large', label: 'Large' }, ]} /> <SchemaField.Void name="sizeWrap" x-component="Div"> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required /> <SchemaField.String name="select1" title="Multiple Select" x-decorator="FormItem" x-component="Select" enum={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} x-component-props={{ mode: 'multiple', placeholder: '请选择', }} required /> <SchemaField.String name="select2" title="Select" x-decorator="FormItem" x-component="Select" enum={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} x-component-props={{ placeholder: '请选择', }} required /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required /> </SchemaField.Void> </SchemaField> </FormProvider> ) } ``` ## API ### FormItem | 属性名 | 类型 | 描述 | 默认值 | | --------------------- | ------------------------------------------------------ | ------------------------------------------------------------------- | ------------------- | | label | ReactNode | 标签 | - | | style | CSSProperties | 样式 | - | | labelStyle | CSSProperties | 标签样式 | - | | wrapperStyle | CSSProperties | 组件容器样式 | - | | className | string | 组件样式类名 | - | | colon | boolean | 冒号 | true | | tooltip | ReactNode | 问号提示 | - | | tooltipLayout | `"icon" \| "text"` | 问号提示布局 | `"icon"` | | tooltipIcon | ReactNode | 问号提示图标 | `?` | | labelAlign | `"left"` \| `"right"` | 标签文本对齐方式 | `"right"` | | labelWrap | boolean | 标签换⾏,否则出现省略号,hover 有 tooltip | false | | labelWidth | `number \| string` | 标签固定宽度 | - | | wrapperWidth | `number \| string` | 内容固定宽度 | - | | labelCol | number | 标签⽹格所占列数,和内容列数加起来总和为 24 | - | | wrapperCol | number | 内容⽹格所占列数,和标签列数加起来总和为 24 | - | | wrapperAlign | `"left"` \| `"right"` | 内容文本对齐方式⻬ | `"left"` | | wrapperWrap | boolean | 内容换⾏,否则出现省略号,hover 有 tooltip | false | | fullness | boolean | 内容撑满 | false | | addonBefore | ReactNode | 前缀内容 | - | | addonAfter | ReactNode | 后缀内容 | - | | size | `"small"` \| `"default"` \| `"large"` | 尺⼨ | - | | inset | boolean | 是否是内嵌布局 | false | | extra | ReactNode | 扩展描述⽂案 | - | | feedbackText | ReactNode | 反馈⽂案 | - | | feedbackLayout | `"loose"` \| `"terse"` \| `"popover" \| "none"` | 反馈布局 | - | | feedbackStatus | `"error"` \| `"warning"` \| `"success"` \| `"pending"` | 反馈布局 | - | | feedbackIcon | ReactNode | 反馈图标 | - | | enableOutlineFeedback | boolean | 开启异常状态的边框颜色样式,当自定义组件内存在子表单时建议关闭此项 | true | | getPopupContainer | function(triggerNode) | 当 feedbackLayout 为 popover 时,浮层渲染父节点,默认渲染到 body 上 | () => document.body | | asterisk | boolean | 星号提醒 | - | | gridSpan | number | ⽹格布局占宽 | - | | bordered | boolean | 是否有边框 | - | ### FormItem.BaseItem 纯样式组件,属性与 FormItem 一样,与 Formily Core 不做状态桥接,主要用于一些需要依赖 FormItem 的样式布局能力,但不希望接入 Field 状态的场景 ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormItem.md: -------------------------------------------------------------------------------- ```markdown # FormItem > The brand new FormItem component, compared to Fusion Next’s FormItem, it supports more functions. At the same time, it is positioned as a pure style component and does not manage form status, so it will be lighter and more convenient for customization ## Markup Schema example ```tsx import React from 'react' import { Input, Select, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, Select, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="input box" x-decorator="FormItem" x-component="Input" required /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JOSN Schema case ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { style: { width: 240, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="input" title="input box" required decorator={[FormItem]} component={[ Input, { style: { width: 240, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Commonly used attribute cases ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Display when label is empty' }} /> <SchemaField.String x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.String title="" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'colon' }} /> <SchemaField.String title="default" x-decorator="FormItem" x-component="Input" /> <SchemaField.String title="no colon (colon=false)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ colon: false, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Fixed width settings' }} /> <SchemaField.String title="Fixed label width (labelWidth)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, }} /> <SchemaField.String title="Fixed label width (labelWidth) overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow overflow" description="description description" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, tooltip: 'Prompt Tip', tooltipLayout: 'text', }} /> <SchemaField.String title="Fixed label width (labelWidth) newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline newline" description="description description" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelWrap: true, tooltip: 'Prompt Tip', }} /> <SchemaField.String title="fixed content width (wrapperWidth)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 300, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Alignment settings' }} /> <SchemaField.String title="labelLeft Alignment(labelAlign=left)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelAlign: 'left', }} /> <SchemaField.String title="label right alignment (labelAlign=right default)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, labelAlign: 'right', }} /> <SchemaField.String title="Content left aligned (wrapperAlign=left default)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 240, wrapperAlign: 'left', }} /> <SchemaField.String title="Content align right (wrapperAlign=right)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ labelWidth: 300, wrapperWidth: 240, wrapperAlign: 'right', }} /> <SchemaField.String title="tooltip" x-decorator="FormItem" x-component="Input" x-decorator-props={{ tooltip: 'tooltip', }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Is it full?' }} /> <SchemaField.String title="The default is fullness(fullness=true)" x-decorator="FormItem" x-component="Select" /> <SchemaField.String title="Not fullness(fullness=false)" x-decorator="FormItem" x-component="Select" x-decorator-props={{ fullness: false, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'auxiliary information' }} /> <SchemaField.String title="Required asterisk" x-decorator="FormItem" x-component="Input" x-decorator-props={{ asterisk: true, labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="prefix" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonBefore: 'addonBefore', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="suffix" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'addonAfter', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="Help information feedbackText" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackText: 'feedbackText', labelCol: 6, wrapperCol: 10, }} /> <SchemaField.String title="extra information extra" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackText: 'feedbackText', extra: 'extra', labelCol: 6, wrapperCol: 10, }} /> </SchemaField> </FormProvider> ) } ``` ## Borderless case Set to remove the component border ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required x-decorator-props={{ bordered: false, }} /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required x-decorator-props={{ bordered: false, }} /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required x-decorator-props={{ bordered: false, }} /> </SchemaField> </FormProvider> ) } ``` ## Embedded mode case Set the form component to inline mode ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Title, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required x-decorator-props={{ inset: true, }} /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required x-decorator-props={{ inset: true, }} /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required x-decorator-props={{ inset: false, }} /> </SchemaField> </FormProvider> ) } ``` ## Feedback Customization Case The button for specifying feedback can be passed in through `feedbackIcon` ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, TimePicker, FormItem, FormLayout, NumberPicker, Switch, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { CheckCircleFilled, LoadingOutlined } from '@ant-design/icons' const Title = (props) => <h3>{props.text}</h3> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, TimePicker, NumberPicker, Switch, Radio, FormItem, Title, FormLayout, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String title="error status (feedbackStatus=error)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'error', }} /> <SchemaField.String title="Warning Status(feedbackStatus=warning)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'warning', }} /> <SchemaField.String title="Success Status (feedbackStatus=success)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="Loading Status(feedbackStatus=pending)" x-decorator="FormItem" x-component="Input" description="description" x-decorator-props={{ feedbackStatus: 'pending', feedbackIcon: <LoadingOutlined style={{ color: '#1890ff' }} />, }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Layout of feedback information' }} /> <SchemaField.String title="Compact mode required" x-decorator="FormItem" x-component="Input" required x-decorator-props={{ feedbackLayout: 'terse', }} /> <SchemaField.String title="Compact mode has feedback(feedbackLayout=terse)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'terse', }} /> <SchemaField.String title="Compact mode without feedback(feedbackLayout=terse)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackLayout: 'terse', }} /> <SchemaField.String title="loose mode (feedbackLayout=loose)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'loose', }} /> <SchemaField.String title="Popup Mode (feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'warning', feedbackText: 'warning message', feedbackLayout: 'popover', }} /> <SchemaField.String title="Popup Mode (feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'popover', }} /> <SchemaField.String title="Popup Mode (feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" x-decorator-props={{ feedbackStatus: 'success', feedbackText: 'success message', feedbackLayout: 'popover', }} /> <SchemaField.Void x-component="Title" x-component-props={{ text: 'Component adaptation' }} /> <SchemaField.Void x-component="FormLayout" x-component-props={{ layout: 'vertical' }} > <SchemaField.String title="Select" x-decorator="FormItem" x-component="Select" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker" x-decorator="FormItem" x-component="DatePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.RangePicker" x-decorator="FormItem" x-component="DatePicker.RangePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.YearPicker" x-decorator="FormItem" x-component="DatePicker.YearPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.MonthPicker" x-decorator="FormItem" x-component="DatePicker.MonthPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="DatePicker.TimePicker" x-decorator="FormItem" x-component="TimePicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> <SchemaField.String title="Cascader" x-decorator="FormItem" x-component="Cascader" x-decorator-props={{ feedbackStatus: 'success', feedbackIcon: <CheckCircleFilled style={{ color: '#52c41a' }} />, }} /> </SchemaField.Void> </SchemaField> </FormProvider> ) } ``` ## Size control case ```tsx import React from 'react' import { Input, Radio, TreeSelect, Cascader, Select, DatePicker, FormItem, NumberPicker, Switch, } from '@formily/next' import { createForm, onFieldChange } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const Div = (props) => <div {...props} /> const SchemaField = createSchemaField({ components: { Input, Select, Cascader, TreeSelect, DatePicker, NumberPicker, Switch, Radio, FormItem, Div, }, }) const form = createForm({ values: { size: 'default', }, effects: () => { onFieldChange('size', ['value'], (field, form) => { form.setFieldState('sizeWrap.*', (state) => { if (state.decorator[1]) { state.decorator[1].size = field.value } }) }) }, }) export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="size" title="Radio.Group" x-decorator="FormItem" x-component="Radio.Group" enum={[ { value: 'small', label: 'Small' }, { value: 'default', label: 'Default' }, { value: 'large', label: 'Large' }, ]} /> <SchemaField.Void name="sizeWrap" x-component="Div"> <SchemaField.String name="input" title="Input" x-decorator="FormItem" x-component="Input" required /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" required /> <SchemaField.String name="Select" title="Select" x-decorator="FormItem" x-component="Select" enum={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} required /> <SchemaField.String name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required /> <SchemaField.String name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required /> <SchemaField.String name="NumberPicker" title="NumberPicker" x-decorator="FormItem" x-component="NumberPicker" required /> <SchemaField.String name="TreeSelect" title="TreeSelect" x-decorator="FormItem" x-component="TreeSelect" required /> <SchemaField.Boolean name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required /> </SchemaField.Void> </SchemaField> </FormProvider> ) } ``` ## API ### FormItem | Property name | Type | Description | Default value | | -------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ------------- | | label | ReactNode | label | - | | style | CSSProperties | Style | - | | labelStyle | CSSProperties | Label style | - | | wrapperStyle | CSSProperties | Component container style | - | | className | string | Component style class name | - | | colon | boolean | colon | true | | tooltip | ReactNode | Question mark prompt | - | | tooltipLayout | `"icon" \| "text"` | Ask the prompt layout | `"icon"` | | tooltipIcon | ReactNode | Ask the prompt icon | `?` | | labelAlign | `"left"` \| `"right"` | Label text alignment | `"right"` | | labelWrap | boolean | Label change, otherwise an ellipsis appears, hover has tooltip | false | | labelWidth | `number \| string` | Label fixed width | - | | wrapperWidth | `number \| string` | Content fixed width | - | | labelCol | number | The number of columns occupied by the label grid, and the number of content columns add up to 24 | - | | wrapperCol | number | The number of columns occupied by the content grid, and the number of label columns add up to 24 | - | | wrapperAlign | `"left"` \| `"right"` | Content text alignment ⻬ | `"left"` | | wrapperWrap | boolean | Change the content, otherwise an ellipsis appears, and hover has tooltip | false | | fullness | boolean | fullness | true | | addonBefore | ReactNode | Prefix content | - | | addonAfter | ReactNode | Suffix content | - | | size | `"small"` \| `"default"` \| `"large"` | 尺⼨ | - | | inset | boolean | Is it an inline layout | false | | extra | ReactNode | Extended description script | - | | feedbackText | ReactNode | Feedback Case | - | | feedbackLayout | `"loose"` \| `"terse"` \| `"popover" \| "none"` | Feedback layout | - | | feedbackStatus | `"error"` \| `"warning"` \| `"success"` \| `"pending"` | Feedback layout | - | | feedbackIcon | ReactNode | Feedback icon | - | | asterisk | boolean | Asterisk reminder | - | | gridSpan | number | Grid layout occupies width | - | | bordered | boolean | Is there a border | - | ### FormItem.BaseItem Pure style components, the properties are the same as FormItem, and Formily Core does not do state bridging. It is mainly used for scenarios that need to rely on the style layout capabilities of FormItem but do not want to access the Field state. ```