This is page 31 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/core/docs/guide/field.md: -------------------------------------------------------------------------------- ```markdown 1 | # Field model 2 | 3 | Formily's field model core contains two types of field models: 4 | 5 | - Data type field 6 | - Dummy data type field 7 | 8 | Data type field (Field), the core is responsible for maintaining the form data (the value when the form is submitted). 9 | 10 | VoidField, you can understand that it is a Field that has castrated data maintenance capabilities, so it is more of a UI form that maintains a batch of fields as a container. 11 | 12 | Let's analyze these two types of fields in detail. 13 | 14 | ## Data field 15 | 16 | There are 3 data type fields in the field model: 17 | 18 | - Field 19 | - ArrayField 20 | - ObjectField 21 | 22 | ArrayField and ObjectField are both inherited from Field. The positioning of Field is to maintain non-incremental data fields. Compared with ArrayField/Object, it does not mean that Field cannot store data of array type or object type. Field can store data of any data type. However, if the user expects to realize the interaction of adding, deleting, and moving arrays, they need to use ArrayField, and for the interaction of adding and deleting object properties, they need to use ObjectField. If there is no such requirement, all data types can be unified with Field. 23 | 24 | Then let's look at the specific Field rules: 25 | 26 | - Path rules 27 | - Explicit and implicit rules 28 | - Data read and write rules 29 | - Data source rules 30 | - Field component rules 31 | - Field decorator rules 32 | - Validation rules 33 | 34 | ### Path Rules 35 | 36 | Because the form structure of our actual business itself is a tree structure, in Formily, each field will have an absolute path in the form model. This absolute path roughly describes the position of the field in the form data (why use roughly, later I will talk about it), any field can be found through the absolute path, and at the same time the parent-child relationship between the fields can be expressed. Therefore, in the field model, we define the address attribute to express the absolute path of the field, which is mainly described by dot syntax, such as abc The path of represents that the father of field c is field b, and the father of field b is a. 37 | 38 | Of course, things are not that simple, because we also have a VoidField, which is a dummy data field, and it also has its own absolute path, because it can be the father of the data field. If we only have an absolute path, we cannot make a data field correct. Write field data to the form data. Reading data will also read the wrong position. 39 | 40 | Therefore, we actually need a data path as a dedicated data field for writing data and reading data. Here we use path to describe the data path of the field. You can look at this picture for general rules: 41 | 42 |  43 | 44 | In summary, Address is always the absolute path representing the node, and Path is the node path that skips the VoidField, but if it is the Path of the VoidField, it will retain its own path position. 45 | 46 | Therefore, whether it is a Field or a VoidField, it will have its Address and Path, so when we use the query method to query the field, we can either use the Address rule to query, or use the Path rule to query, such as `query("bc")` The c field can be queried, and the c field can also be queried with `query("abc")`. 47 | 48 | ### Explicit and Implicit Rules 49 | 50 | The display and hiding of the fields are mainly expressed by the display attribute: 51 | 52 | - If display is none, it means that the field UI is hidden and the field data is not retained 53 | - display is hidden, which means that the field UI is hidden and the field data is preserved 54 | - display is visible, which means the field UI is displayed, and the field data is restored at the same time 55 | 56 | On top of the display property, we also provide two convenient properties 57 | 58 | 1. visible, if true, display is equal to visible, if false, display is equal to none 59 | 2. hidden, if true, display is equal to hidden, if false, display is equal to visible 60 | 61 | The above is about the writing rules of explicit and implicit attributes. The reading rules will be more complicated. Here is a default inheritance logic: 62 | 63 | If the parent node actively sets the display property, and the child node does not actively set the display property, then the child node will inherit the display of the parent node 64 | 65 | So what is the active setting of display? mainly includes: 66 | 67 | - Configure the initial attributes display/visible/hidden for the field 68 | - If there is no configuration during initialization, but display/visible/hidden is set to the field later 69 | 70 | So what if you want to change from no inheritance to inheritance? Just set display to null. 71 | 72 | ### Data read and write rules 73 | 74 | Because Field is a data-type field, it is responsible for maintaining the data of a certain node of the form data. The reading here is actually the form data read directly, which is addressed through the path attribute, which also guarantees the form data and field data. Absolutely idempotent, just read value/initialValue directly. 75 | 76 | The data writing rules are consistent with the reading rules. Field does not independently maintain a copy of data. It directly operates on the data of the specific form, which is addressed through the path attribute. The main writing methods are: 77 | 78 | - Modify the value/initialValue attribute directly 79 | - Calling onInput will write data, and at the same time, set the inputValue of the field as input parameter data, inputValues as multi-parameter data, and then set the modified attribute to true, which means that the field has been manually modified, and finally trigger the verification rule that triggerType is onInput 80 | - Call the setValue method 81 | 82 | ### Data source rules 83 | 84 | Considering that the value source of the field is not only input through the Input input box, but also selected from a data source, such as a drop-down box, the field model adds a data source attribute dataSource, which is dedicated to reading data source. Only a layer of mapping needs to be done on the component consumer side. The method of writing to the data source can directly modify the dataSource property, or call the setDataSource method 85 | 86 | ### Component Rules 87 | 88 | Field model, if there is no proxy UI component information, then more refined linkage control cannot be achieved. For example, if the value of A field changes to control the placeholder of B field, then the field attributes must be proxyed, so formily provides The component property is used to proxy UI component information. The component is an array `[Component,ComponentProps]`. The first element represents which component it is, and the second represents the properties of the component. Why use an array? This is convenient for type prompting, and the writing method is relatively simple. 89 | 90 | The way to read component information is to read the component property directly. 91 | 92 | The main ways to write component information are: 93 | 94 | - Modify the component property directly and pass in the array 95 | - Call the setComponent method, the first parameter is the component, the second is the component property 96 | - Call the setComponentProps method to directly set the component properties 97 | 98 | ### Decorator rules 99 | 100 | Similar to the field component rules, the field decorator is mainly used to maintain the package container of the field, such as FormItem, which is more partial to the control of the UI layout. Here we use the decorator attribute to describe the field decorator. 101 | 102 | The way to read the decorator information is to directly read the decorator attribute. 103 | 104 | The main ways to write decorator information are: 105 | 106 | - Modify the decorator property directly and pass in an array 107 | - Call the setDecorator method, the first parameter is the component, and the second is the component property 108 | - Call the setDecoratorProps method to directly set the component properties 109 | 110 | ### Validation rules 111 | 112 | The verification rules mainly include: 113 | 114 | - Verifier 115 | - Timing of calibration 116 | - Verification strategy 117 | - Verification result 118 | 119 | #### Validator 120 | 121 | The validator in the field model is mainly described by the validator attribute. The validator can be passed to the field when the field is initialized, and the validator can be modified again after initialization. 122 | 123 | A validator mainly has the following forms: 124 | 125 | - Pure string format verification, such as `"phone" | validator = "url" | validator= "email"`. This format verification is a short form of regular rules. Formily provides some standard regular rules. Of course Users can also manually create rules through registerValidateFormats to facilitate reuse 126 | - Custom function verification, there are 3 return value modes: 127 | - `(value)=>"message"`, a string returned means there is an error, and no string means no error 128 | - `(value)=>({type:"error",message:"message"})`, return object form, you can specify type as error or warning or success 129 | - `{validator:()=>false,message:"message"}`, returns a boolean form, the error message will reuse the message field of the object structure 130 | - Object structure verification is a more complete expression, such as: 131 | - `{format:"url"}` This can specify the regular format 132 | - `{required:true}` This can specify required fields 133 | - There are more rule attributes can refer to the API documentation, and we can also register similar validation rules through registerValidateRules 134 | - Object array structure verification is a combination of the previous three types. In fact, the first three types will all be converted into object array structures, such as: 135 | - `["url",{required:true},(value)=>"message"]` is actually equivalent to `[{format:"url"},{required:true},{validator:(value)=> "message"}]` 136 | 137 | #### Check timing 138 | 139 | Sometimes, we want certain verification rules to be triggered only when focusing or out of focus. We can add a triggerType to each verification rule object, such as `{validator:(value)=>"message",triggerType: "onBlur"}` In this way, you can precisely control a verification rule to perform verification only in a certain event. The triggerType here mainly includes `"onInput" | "onBlur" | "onFocus"`, if you call `form. validate` is a rule that verifies all triggerTypes at once. If you manually call `field.validate`, you can specify the triggerType in the input parameters, and all triggerTypes will be verified if you don’t pass them. 140 | 141 | #### Verification Strategy 142 | 143 | Sometimes, we hope that the verification strategy of a certain field is that when all the verification rules are executed, if a verification rule fails, the result will be returned immediately. We only need to pass the parameter validateFirst to true when the field is initialized. That is, the default is false, that is, the verification will continue if the verification fails, and the verification result obtained is an array. 144 | 145 | #### Read the verification result 146 | 147 | The verification results are mainly stored in the feedbacks property in the field model. Feedbacks is an array of Feedback objects. The structure of each Feedback is: 148 | 149 | ```ts 150 | interface Feedback { 151 | path: string //Field data path 152 | address: string //field absolute path 153 | type: 'error' | 'success' | 'warning' //Verification result type 154 | code: //Check result code 155 | | 'ValidateError' 156 | | 'ValidateSuccess' 157 | | 'ValidateWarning' 158 | | 'EffectError' 159 | | 'EffectSuccess' 160 | | 'EffectWarning' 161 | messages: string[] //Check the message 162 | } 163 | ``` 164 | 165 | There are four main ways to read: 166 | 167 | - Read feedbacks properties directly 168 | - Reading the errors attribute is equivalent to filtering out all verification results with type error from feedbacks 169 | - Reading the warnings attribute is equivalent to filtering out all the verification results whose type is warning from feedbacks 170 | - Reading the successes attribute is equivalent to filtering out all verification results with type success from feedbacks 171 | 172 | #### Write verification result 173 | 174 | There are 3 ways to write: 175 | 176 | - Call the validate method to trigger the field validator to perform the validation action, and the code of the validation result is uniformly Validate\*` 177 | - Calling onInput will trigger validate 178 | - Calling onFocus will trigger validate 179 | - Calling onBlur will trigger validate 180 | - Call reset and specify validate as true to trigger validate 181 | - Modify the feedbacks attribute directly 182 | - Modify the errors property directly, it will be converted into an array of feedbacks objects, and the code of Feedback will be forcibly overwritten as EffectError 183 | - Modify the warnings attribute directly, it will be converted into an array of feedbacks objects, and the code of Feedback will be forcibly overwritten as EffectWarning 184 | - Modify the successes property directly, it will be converted into an array of feedbacks objects, and the code of Feedback will be forcibly overwritten as EffectSuccess 185 | 186 | Such writing logic is mainly to prevent users from modifying the verification results from polluting the verification results of their own verifiers, strictly separating them, and easy to restore the scene. 187 | 188 | #### Verification result query 189 | 190 | The query of the verification result is mainly queried through queryFeedbacks. The query has the same participating Feedback objects, which can be filtered by type or code, or by path. 191 | 192 | ## ArrayField 193 | 194 | Compared with Field, ArrayField only extends array-related methods on the basis of inheriting Field, such as push/pop/insert/move. Why should these methods be provided? Its ability is not only to process the field data, it It also provides internal state transposition processing for the sub-nodes of ArrayField mainly to ensure that the order of the fields is consistent with the order of the data. Can cite an example: 195 | 196 |  197 | 198 | This is a move call process, the value of the array element will move, and the state of the corresponding field will also move. 199 | 200 | ## ObjectField 201 | 202 | Because the object type is disordered, there is no state transposition, so ObjectField provides addProperty/removeProperty/existProperty three APIs for users to use. 203 | 204 | ## VoidField 205 | 206 | Compared with Field, VoidField mainly castrates data read and write rules, data source rules, and verification rules. When users use it, they mainly use explicit and implicit rules, components, and decorator rules. 207 | 208 | <Alert> 209 | 210 | The series of field rules mentioned above did not mention the detailed API usage details. It is more to help users sort out formily from the perspective of ideas. If you are not familiar with the API, it is best to read the API documentation first. 211 | 212 | </Alert> 213 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormGrid.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormGrid 2 | 3 | > FormGrid 组件 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { FormItem, Input, FormGrid } from '@formily/antd' 10 | import { FormProvider, createSchemaField } from '@formily/react' 11 | import { createForm } from '@formily/core' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | Input, 17 | FormGrid, 18 | }, 19 | }) 20 | 21 | const form = createForm() 22 | 23 | export default () => { 24 | return ( 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.Void 28 | x-component="FormGrid" 29 | x-component-props={{ 30 | maxColumns: 3, 31 | minColumns: 2, 32 | }} 33 | > 34 | <SchemaField.String 35 | name="aaa" 36 | title="aaa" 37 | x-decorator="FormItem" 38 | x-decorator-props={{ gridSpan: 2 }} 39 | x-component="Input" 40 | /> 41 | <SchemaField.String 42 | name="bbb" 43 | title="bbb" 44 | x-decorator="FormItem" 45 | x-component="Input" 46 | /> 47 | <SchemaField.String 48 | name="ccc" 49 | title="ccc" 50 | x-decorator="FormItem" 51 | x-component="Input" 52 | /> 53 | <SchemaField.String 54 | name="ddd" 55 | title="ddd" 56 | x-decorator="FormItem" 57 | x-component="Input" 58 | /> 59 | <SchemaField.String 60 | name="eee" 61 | title="eee" 62 | x-decorator="FormItem" 63 | x-component="Input" 64 | /> 65 | <SchemaField.String 66 | name="fff" 67 | title="fff" 68 | x-decorator="FormItem" 69 | x-component="Input" 70 | /> 71 | <SchemaField.String 72 | name="ggg" 73 | title="ggg" 74 | x-decorator="FormItem" 75 | x-component="Input" 76 | /> 77 | </SchemaField.Void> 78 | </SchemaField> 79 | </FormProvider> 80 | ) 81 | } 82 | ``` 83 | 84 | ## JSON Schema 案例 85 | 86 | ```tsx 87 | import React from 'react' 88 | import { FormItem, Input, FormGrid } from '@formily/antd' 89 | import { FormProvider, createSchemaField } from '@formily/react' 90 | import { createForm } from '@formily/core' 91 | 92 | const SchemaField = createSchemaField({ 93 | components: { 94 | FormItem, 95 | Input, 96 | FormGrid, 97 | }, 98 | }) 99 | 100 | const form = createForm() 101 | 102 | const schema = { 103 | type: 'object', 104 | properties: { 105 | grid: { 106 | type: 'void', 107 | 'x-component': 'FormGrid', 108 | 'x-component-props': { 109 | minColumns: [4, 6, 10], 110 | }, 111 | properties: { 112 | aaa: { 113 | type: 'string', 114 | title: 'AAA', 115 | 'x-decorator': 'FormItem', 116 | 'x-component': 'Input', 117 | }, 118 | bbb: { 119 | type: 'string', 120 | title: 'BBB', 121 | 'x-decorator': 'FormItem', 122 | 'x-component': 'Input', 123 | }, 124 | ccc: { 125 | type: 'string', 126 | title: 'CCC', 127 | 'x-decorator': 'FormItem', 128 | 'x-component': 'Input', 129 | }, 130 | ddd: { 131 | type: 'string', 132 | title: 'DDD', 133 | 'x-decorator': 'FormItem', 134 | 'x-component': 'Input', 135 | }, 136 | eee: { 137 | type: 'string', 138 | title: 'EEE', 139 | 'x-decorator': 'FormItem', 140 | 'x-component': 'Input', 141 | }, 142 | fff: { 143 | type: 'string', 144 | title: 'FFF', 145 | 'x-decorator': 'FormItem', 146 | 'x-component': 'Input', 147 | }, 148 | ggg: { 149 | type: 'string', 150 | title: 'GGG', 151 | 'x-decorator': 'FormItem', 152 | 'x-component': 'Input', 153 | }, 154 | }, 155 | }, 156 | }, 157 | } 158 | 159 | export default () => { 160 | return ( 161 | <FormProvider form={form}> 162 | <SchemaField schema={schema} /> 163 | </FormProvider> 164 | ) 165 | } 166 | ``` 167 | 168 | ## 原生 案例 169 | 170 | ```tsx 171 | import React from 'react' 172 | import { FormGrid } from '@formily/antd' 173 | 174 | const { GridColumn } = FormGrid 175 | const Cell = ({ children }) => { 176 | return ( 177 | <div 178 | style={{ 179 | backgroundColor: '#AAA', 180 | color: '#FFF', 181 | height: 30, 182 | display: 'flex', 183 | alignItems: 'center', 184 | padding: '0 10px', 185 | }} 186 | > 187 | {children} 188 | </div> 189 | ) 190 | } 191 | export default () => { 192 | return ( 193 | <React.Fragment> 194 | <p>maxColumns 3 + minColumns 2</p> 195 | <FormGrid maxColumns={3} minColumns={2} columnGap={4}> 196 | <GridColumn gridSpan={4}> 197 | <Cell>1</Cell> 198 | </GridColumn> 199 | <GridColumn> 200 | <Cell>2</Cell> 201 | </GridColumn> 202 | <GridColumn> 203 | <Cell>3</Cell> 204 | </GridColumn> 205 | <GridColumn> 206 | <Cell>4</Cell> 207 | </GridColumn> 208 | <GridColumn> 209 | <Cell>5</Cell> 210 | </GridColumn> 211 | <GridColumn> 212 | <Cell>6</Cell> 213 | </GridColumn> 214 | </FormGrid> 215 | <p>maxColumns 3</p> 216 | <FormGrid maxColumns={3} columnGap={4}> 217 | <GridColumn gridSpan={2}> 218 | <Cell>1</Cell> 219 | </GridColumn> 220 | <GridColumn> 221 | <Cell>2</Cell> 222 | </GridColumn> 223 | <GridColumn> 224 | <Cell>3</Cell> 225 | </GridColumn> 226 | <GridColumn> 227 | <Cell>4</Cell> 228 | </GridColumn> 229 | <GridColumn> 230 | <Cell>5</Cell> 231 | </GridColumn> 232 | <GridColumn> 233 | <Cell>6</Cell> 234 | </GridColumn> 235 | </FormGrid> 236 | <p>minColumns 2</p> 237 | <FormGrid minColumns={2} columnGap={4}> 238 | <GridColumn gridSpan={2}> 239 | <Cell>1</Cell> 240 | </GridColumn> 241 | <GridColumn> 242 | <Cell>2</Cell> 243 | </GridColumn> 244 | <GridColumn> 245 | <Cell>3</Cell> 246 | </GridColumn> 247 | <GridColumn> 248 | <Cell>4</Cell> 249 | </GridColumn> 250 | <GridColumn> 251 | <Cell>5</Cell> 252 | </GridColumn> 253 | <GridColumn> 254 | <Cell>6</Cell> 255 | </GridColumn> 256 | </FormGrid> 257 | <p>Null</p> 258 | <FormGrid columnGap={4}> 259 | <GridColumn gridSpan={2}> 260 | <Cell>1</Cell> 261 | </GridColumn> 262 | <GridColumn> 263 | <Cell>2</Cell> 264 | </GridColumn> 265 | <GridColumn> 266 | <Cell>3</Cell> 267 | </GridColumn> 268 | <GridColumn> 269 | <Cell>4</Cell> 270 | </GridColumn> 271 | <GridColumn> 272 | <Cell>5</Cell> 273 | </GridColumn> 274 | <GridColumn> 275 | <Cell>6</Cell> 276 | </GridColumn> 277 | </FormGrid> 278 | <p>minWidth 150 +maxColumns 3</p> 279 | <FormGrid minWidth={150} maxColumns={3} columnGap={4}> 280 | <GridColumn gridSpan={2}> 281 | <Cell>1</Cell> 282 | </GridColumn> 283 | <GridColumn> 284 | <Cell>2</Cell> 285 | </GridColumn> 286 | <GridColumn> 287 | <Cell>3</Cell> 288 | </GridColumn> 289 | <GridColumn> 290 | <Cell>4</Cell> 291 | </GridColumn> 292 | <GridColumn> 293 | <Cell>5</Cell> 294 | </GridColumn> 295 | <GridColumn> 296 | <Cell>6</Cell> 297 | </GridColumn> 298 | </FormGrid> 299 | <p>maxWidth 120+minColumns 2</p> 300 | <FormGrid maxWidth={120} minColumns={2} columnGap={4}> 301 | <GridColumn gridSpan={2}> 302 | <Cell>1</Cell> 303 | </GridColumn> 304 | <GridColumn> 305 | <Cell>2</Cell> 306 | </GridColumn> 307 | <GridColumn> 308 | <Cell>3</Cell> 309 | </GridColumn> 310 | <GridColumn> 311 | <Cell>4</Cell> 312 | </GridColumn> 313 | <GridColumn> 314 | <Cell>5</Cell> 315 | </GridColumn> 316 | <GridColumn> 317 | <Cell>6</Cell> 318 | </GridColumn> 319 | </FormGrid> 320 | <p>maxWidth 120 + gridSpan -1</p> 321 | <FormGrid maxWidth={120} columnGap={4}> 322 | <GridColumn gridSpan={2}> 323 | <Cell>1</Cell> 324 | </GridColumn> 325 | <GridColumn> 326 | <Cell>2</Cell> 327 | </GridColumn> 328 | <GridColumn gridSpan={-1}> 329 | <Cell>3</Cell> 330 | </GridColumn> 331 | </FormGrid> 332 | </React.Fragment> 333 | ) 334 | } 335 | ``` 336 | 337 | ## 查询表单实现案例 338 | 339 | ```tsx 340 | import React, { useMemo, Fragment } from 'react' 341 | import { createForm } from '@formily/core' 342 | import { createSchemaField, FormProvider, observer } from '@formily/react' 343 | import { 344 | Form, 345 | Input, 346 | Select, 347 | DatePicker, 348 | FormItem, 349 | FormGrid, 350 | Submit, 351 | Reset, 352 | FormButtonGroup, 353 | } from '@formily/antd' 354 | 355 | const useCollapseGrid = (maxRows: number) => { 356 | const grid = useMemo( 357 | () => 358 | FormGrid.createFormGrid({ 359 | maxColumns: 4, 360 | maxWidth: 240, 361 | maxRows: maxRows, 362 | shouldVisible: (node, grid) => { 363 | if (node.index === grid.childSize - 1) return true 364 | if (grid.maxRows === Infinity) return true 365 | return node.shadowRow < maxRows + 1 366 | }, 367 | }), 368 | [] 369 | ) 370 | const expanded = grid.maxRows === Infinity 371 | const realRows = grid.shadowRows 372 | const computeRows = grid.fullnessLastColumn 373 | ? grid.shadowRows - 1 374 | : grid.shadowRows 375 | 376 | const toggle = () => { 377 | if (grid.maxRows === Infinity) { 378 | grid.maxRows = maxRows 379 | } else { 380 | grid.maxRows = Infinity 381 | } 382 | } 383 | const takeType = () => { 384 | if (realRows < maxRows + 1) return 'incomplete-wrap' 385 | if (computeRows > maxRows) return 'collapsible' 386 | return 'complete-wrap' 387 | } 388 | return { 389 | grid, 390 | expanded, 391 | toggle, 392 | type: takeType(), 393 | } 394 | } 395 | 396 | const QueryForm: React.FC = observer((props) => { 397 | const { grid, expanded, toggle, type } = useCollapseGrid(1) 398 | 399 | const renderActions = () => { 400 | return ( 401 | <Fragment> 402 | <Submit onSubmit={console.log}>查询</Submit> 403 | <Reset>重置</Reset> 404 | </Fragment> 405 | ) 406 | } 407 | 408 | const renderButtonGroup = () => { 409 | if (type === 'incomplete-wrap') { 410 | return ( 411 | <FormButtonGroup.FormItem> 412 | <FormButtonGroup>{renderActions()}</FormButtonGroup> 413 | </FormButtonGroup.FormItem> 414 | ) 415 | } 416 | if (type === 'collapsible') { 417 | return ( 418 | <Fragment> 419 | <FormButtonGroup> 420 | <a 421 | href="" 422 | onClick={(e) => { 423 | e.preventDefault() 424 | toggle() 425 | }} 426 | > 427 | {expanded ? '收起' : '展开'} 428 | </a> 429 | </FormButtonGroup> 430 | <FormButtonGroup align="right">{renderActions()}</FormButtonGroup> 431 | </Fragment> 432 | ) 433 | } 434 | return ( 435 | <FormButtonGroup align="right" style={{ display: 'flex', width: '100%' }}> 436 | {renderActions()} 437 | </FormButtonGroup> 438 | ) 439 | } 440 | 441 | return ( 442 | <Form {...props} layout="vertical" feedbackLayout="terse"> 443 | <FormGrid grid={grid}> 444 | {props.children} 445 | <FormGrid.GridColumn 446 | gridSpan={-1} 447 | style={{ display: 'flex', justifyContent: 'space-between' }} 448 | > 449 | {renderButtonGroup()} 450 | </FormGrid.GridColumn> 451 | </FormGrid> 452 | </Form> 453 | ) 454 | }) 455 | 456 | const SchemaField = createSchemaField({ 457 | components: { 458 | QueryForm, 459 | Input, 460 | Select, 461 | DatePicker, 462 | FormItem, 463 | }, 464 | }) 465 | 466 | export default () => { 467 | const form = useMemo(() => createForm(), []) 468 | return ( 469 | <FormProvider form={form}> 470 | <SchemaField> 471 | <SchemaField.Object x-component="QueryForm"> 472 | <SchemaField.String 473 | name="input1" 474 | title="Input 1" 475 | x-component="Input" 476 | x-decorator="FormItem" 477 | /> 478 | <SchemaField.String 479 | name="input2" 480 | title="Input 2" 481 | x-component="Input" 482 | x-decorator="FormItem" 483 | /> 484 | 485 | <SchemaField.String 486 | name="select1" 487 | title="Select 1" 488 | x-component="Select" 489 | x-decorator="FormItem" 490 | /> 491 | <SchemaField.String 492 | name="select2" 493 | title="Select 2" 494 | x-component="Select" 495 | x-decorator="FormItem" 496 | /> 497 | <SchemaField.String 498 | name="date" 499 | title="DatePicker" 500 | x-component="DatePicker" 501 | x-decorator="FormItem" 502 | /> 503 | <SchemaField.String 504 | name="dateRange" 505 | title="DatePicker.RangePicker" 506 | x-component="DatePicker.RangePicker" 507 | x-decorator="FormItem" 508 | x-decorator-props={{ 509 | gridSpan: 2, 510 | }} 511 | /> 512 | <SchemaField.String 513 | name="select3" 514 | title="Select 3" 515 | x-component="Select" 516 | x-decorator="FormItem" 517 | /> 518 | </SchemaField.Object> 519 | </SchemaField> 520 | </FormProvider> 521 | ) 522 | } 523 | ``` 524 | 525 | ## API 526 | 527 | ### FormGrid 528 | 529 | | 属性名 | 类型 | 描述 | 默认值 | 530 | | ------------- | ---------------------- | -------------------------------------------------------------- | ----------------- | 531 | | minWidth | `number \| number[]` | 元素最小宽度 | 100 | 532 | | maxWidth | `number \| number[]` | 元素最大宽度 | - | 533 | | minColumns | `number \| number[]` | 最小列数 | 0 | 534 | | maxColumns | `number \| number[]` | 最大列数 | - | 535 | | breakpoints | number[] | 容器尺寸断点 | `[720,1280,1920]` | 536 | | columnGap | number | 列间距 | 8 | 537 | | rowGap | number | 行间距 | 4 | 538 | | colWrap | boolean | 自动换行 | true | 539 | | strictAutoFit | boolean | GridItem 宽度是否严格受限于 maxWidth,不受限的话会自动占满容器 | false | 540 | | shouldVisible | `(node,grid)=>boolean` | 是否需要显示当前节点 | `()=>true` | 541 | | grid | `Grid` | 外部传入 Grid 实例,用于实现更复杂的布局逻辑 | - | 542 | 543 | 注意: 544 | 545 | - minWidth 生效优先级高于 minColumn 546 | - maxWidth 优先级高于 maxColumn 547 | - minWidth/maxWidth/minColumns/maxColumns 的数组格式代表与断点数组映射 548 | 549 | ### FormGrid.GridColumn 550 | 551 | | 属性名 | 类型 | 描述 | 默认值 | 552 | | -------- | ------ | ---------------------------------------------------- | ------ | 553 | | gridSpan | number | 元素所跨列数,如果为-1,那么会自动反向跨列填补单元格 | 1 | 554 | 555 | ### FormGrid.createFormGrid 556 | 557 | 从上下文中读取 Grid 实例 558 | 559 | ```ts 560 | interface createFormGrid { 561 | (props: IGridProps): Grid 562 | } 563 | ``` 564 | 565 | - IGridProps 参考 FormGrid 属性 566 | - Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid 567 | 568 | ### FormGrid.useFormGrid 569 | 570 | 从上下文中读取 Grid 实例 571 | 572 | ```ts 573 | interface useFormGrid { 574 | (): Grid 575 | } 576 | ``` 577 | 578 | - Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid 579 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormGrid.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormGrid 2 | 3 | > FormGrid 组件 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { FormItem, Input, FormGrid } from '@formily/next' 10 | import { FormProvider, createSchemaField } from '@formily/react' 11 | import { createForm } from '@formily/core' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | Input, 17 | FormGrid, 18 | }, 19 | }) 20 | 21 | const form = createForm() 22 | 23 | export default () => { 24 | return ( 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.Void 28 | x-component="FormGrid" 29 | x-component-props={{ 30 | maxColumns: 3, 31 | minColumns: 2, 32 | }} 33 | > 34 | <SchemaField.String 35 | name="aaa" 36 | title="aaa" 37 | x-decorator="FormItem" 38 | x-decorator-props={{ gridSpan: 2 }} 39 | x-component="Input" 40 | /> 41 | <SchemaField.String 42 | name="bbb" 43 | title="bbb" 44 | x-decorator="FormItem" 45 | x-component="Input" 46 | /> 47 | <SchemaField.String 48 | name="ccc" 49 | title="ccc" 50 | x-decorator="FormItem" 51 | x-component="Input" 52 | /> 53 | <SchemaField.String 54 | name="ddd" 55 | title="ddd" 56 | x-decorator="FormItem" 57 | x-component="Input" 58 | /> 59 | <SchemaField.String 60 | name="eee" 61 | title="eee" 62 | x-decorator="FormItem" 63 | x-component="Input" 64 | /> 65 | <SchemaField.String 66 | name="fff" 67 | title="fff" 68 | x-decorator="FormItem" 69 | x-component="Input" 70 | /> 71 | <SchemaField.String 72 | name="ggg" 73 | title="ggg" 74 | x-decorator="FormItem" 75 | x-component="Input" 76 | /> 77 | </SchemaField.Void> 78 | </SchemaField> 79 | </FormProvider> 80 | ) 81 | } 82 | ``` 83 | 84 | ## JSON Schema 案例 85 | 86 | ```tsx 87 | import React from 'react' 88 | import { FormItem, Input, FormGrid } from '@formily/next' 89 | import { FormProvider, createSchemaField } from '@formily/react' 90 | import { createForm } from '@formily/core' 91 | 92 | const SchemaField = createSchemaField({ 93 | components: { 94 | FormItem, 95 | Input, 96 | FormGrid, 97 | }, 98 | }) 99 | 100 | const form = createForm() 101 | 102 | const schema = { 103 | type: 'object', 104 | properties: { 105 | grid: { 106 | type: 'void', 107 | 'x-component': 'FormGrid', 108 | 'x-component-props': { 109 | minColumns: [4, 6, 10], 110 | }, 111 | properties: { 112 | aaa: { 113 | type: 'string', 114 | title: 'AAA', 115 | 'x-decorator': 'FormItem', 116 | 'x-component': 'Input', 117 | }, 118 | bbb: { 119 | type: 'string', 120 | title: 'BBB', 121 | 'x-decorator': 'FormItem', 122 | 'x-component': 'Input', 123 | }, 124 | ccc: { 125 | type: 'string', 126 | title: 'CCC', 127 | 'x-decorator': 'FormItem', 128 | 'x-component': 'Input', 129 | }, 130 | ddd: { 131 | type: 'string', 132 | title: 'DDD', 133 | 'x-decorator': 'FormItem', 134 | 'x-component': 'Input', 135 | }, 136 | eee: { 137 | type: 'string', 138 | title: 'EEE', 139 | 'x-decorator': 'FormItem', 140 | 'x-component': 'Input', 141 | }, 142 | fff: { 143 | type: 'string', 144 | title: 'FFF', 145 | 'x-decorator': 'FormItem', 146 | 'x-component': 'Input', 147 | }, 148 | ggg: { 149 | type: 'string', 150 | title: 'GGG', 151 | 'x-decorator': 'FormItem', 152 | 'x-component': 'Input', 153 | }, 154 | }, 155 | }, 156 | }, 157 | } 158 | 159 | export default () => { 160 | return ( 161 | <FormProvider form={form}> 162 | <SchemaField schema={schema} /> 163 | </FormProvider> 164 | ) 165 | } 166 | ``` 167 | 168 | ## 原生 案例 169 | 170 | ```tsx 171 | import React from 'react' 172 | import { FormGrid } from '@formily/next' 173 | 174 | const { GridColumn } = FormGrid 175 | const Cell = ({ children }) => { 176 | return ( 177 | <div 178 | style={{ 179 | backgroundColor: '#AAA', 180 | color: '#FFF', 181 | height: 30, 182 | display: 'flex', 183 | alignItems: 'center', 184 | padding: '0 10px', 185 | }} 186 | > 187 | {children} 188 | </div> 189 | ) 190 | } 191 | export default () => { 192 | return ( 193 | <React.Fragment> 194 | <p>maxColumns 3 + minColumns 2</p> 195 | <FormGrid maxColumns={3} minColumns={2} columnGap={4}> 196 | <GridColumn gridSpan={4}> 197 | <Cell>1</Cell> 198 | </GridColumn> 199 | <GridColumn> 200 | <Cell>2</Cell> 201 | </GridColumn> 202 | <GridColumn> 203 | <Cell>3</Cell> 204 | </GridColumn> 205 | <GridColumn> 206 | <Cell>4</Cell> 207 | </GridColumn> 208 | <GridColumn> 209 | <Cell>5</Cell> 210 | </GridColumn> 211 | <GridColumn> 212 | <Cell>6</Cell> 213 | </GridColumn> 214 | </FormGrid> 215 | <p>maxColumns 3</p> 216 | <FormGrid maxColumns={3} columnGap={4}> 217 | <GridColumn gridSpan={2}> 218 | <Cell>1</Cell> 219 | </GridColumn> 220 | <GridColumn> 221 | <Cell>2</Cell> 222 | </GridColumn> 223 | <GridColumn> 224 | <Cell>3</Cell> 225 | </GridColumn> 226 | <GridColumn> 227 | <Cell>4</Cell> 228 | </GridColumn> 229 | <GridColumn> 230 | <Cell>5</Cell> 231 | </GridColumn> 232 | <GridColumn> 233 | <Cell>6</Cell> 234 | </GridColumn> 235 | </FormGrid> 236 | <p>minColumns 2</p> 237 | <FormGrid minColumns={2} columnGap={4}> 238 | <GridColumn gridSpan={2}> 239 | <Cell>1</Cell> 240 | </GridColumn> 241 | <GridColumn> 242 | <Cell>2</Cell> 243 | </GridColumn> 244 | <GridColumn> 245 | <Cell>3</Cell> 246 | </GridColumn> 247 | <GridColumn> 248 | <Cell>4</Cell> 249 | </GridColumn> 250 | <GridColumn> 251 | <Cell>5</Cell> 252 | </GridColumn> 253 | <GridColumn> 254 | <Cell>6</Cell> 255 | </GridColumn> 256 | </FormGrid> 257 | <p>Null</p> 258 | <FormGrid columnGap={4}> 259 | <GridColumn gridSpan={2}> 260 | <Cell>1</Cell> 261 | </GridColumn> 262 | <GridColumn> 263 | <Cell>2</Cell> 264 | </GridColumn> 265 | <GridColumn> 266 | <Cell>3</Cell> 267 | </GridColumn> 268 | <GridColumn> 269 | <Cell>4</Cell> 270 | </GridColumn> 271 | <GridColumn> 272 | <Cell>5</Cell> 273 | </GridColumn> 274 | <GridColumn> 275 | <Cell>6</Cell> 276 | </GridColumn> 277 | </FormGrid> 278 | <p>minWidth 150 +maxColumns 3</p> 279 | <FormGrid minWidth={150} maxColumns={3} columnGap={4}> 280 | <GridColumn gridSpan={2}> 281 | <Cell>1</Cell> 282 | </GridColumn> 283 | <GridColumn> 284 | <Cell>2</Cell> 285 | </GridColumn> 286 | <GridColumn> 287 | <Cell>3</Cell> 288 | </GridColumn> 289 | <GridColumn> 290 | <Cell>4</Cell> 291 | </GridColumn> 292 | <GridColumn> 293 | <Cell>5</Cell> 294 | </GridColumn> 295 | <GridColumn> 296 | <Cell>6</Cell> 297 | </GridColumn> 298 | </FormGrid> 299 | <p>maxWidth 120+minColumns 2</p> 300 | <FormGrid maxWidth={120} minColumns={2} columnGap={4}> 301 | <GridColumn gridSpan={2}> 302 | <Cell>1</Cell> 303 | </GridColumn> 304 | <GridColumn> 305 | <Cell>2</Cell> 306 | </GridColumn> 307 | <GridColumn> 308 | <Cell>3</Cell> 309 | </GridColumn> 310 | <GridColumn> 311 | <Cell>4</Cell> 312 | </GridColumn> 313 | <GridColumn> 314 | <Cell>5</Cell> 315 | </GridColumn> 316 | <GridColumn> 317 | <Cell>6</Cell> 318 | </GridColumn> 319 | </FormGrid> 320 | <p>maxWidth 120 + gridSpan -1</p> 321 | <FormGrid maxWidth={120} columnGap={4}> 322 | <GridColumn gridSpan={2}> 323 | <Cell>1</Cell> 324 | </GridColumn> 325 | <GridColumn> 326 | <Cell>2</Cell> 327 | </GridColumn> 328 | <GridColumn gridSpan={-1}> 329 | <Cell>3</Cell> 330 | </GridColumn> 331 | </FormGrid> 332 | </React.Fragment> 333 | ) 334 | } 335 | ``` 336 | 337 | ## 查询表单实现案例 338 | 339 | ```tsx 340 | import React, { useMemo, Fragment } from 'react' 341 | import { createForm } from '@formily/core' 342 | import { createSchemaField, FormProvider, observer } from '@formily/react' 343 | import { 344 | Form, 345 | Input, 346 | Select, 347 | DatePicker, 348 | FormItem, 349 | FormGrid, 350 | Submit, 351 | Reset, 352 | FormButtonGroup, 353 | } from '@formily/next' 354 | 355 | const useCollapseGrid = (maxRows: number) => { 356 | const grid = useMemo( 357 | () => 358 | FormGrid.createFormGrid({ 359 | maxColumns: 4, 360 | maxWidth: 240, 361 | maxRows: maxRows, 362 | shouldVisible: (node, grid) => { 363 | if (node.index === grid.childSize - 1) return true 364 | if (grid.maxRows === Infinity) return true 365 | return node.shadowRow < maxRows + 1 366 | }, 367 | }), 368 | [] 369 | ) 370 | const expanded = grid.maxRows === Infinity 371 | const realRows = grid.shadowRows 372 | const computeRows = grid.fullnessLastColumn 373 | ? grid.shadowRows - 1 374 | : grid.shadowRows 375 | 376 | const toggle = () => { 377 | if (grid.maxRows === Infinity) { 378 | grid.maxRows = maxRows 379 | } else { 380 | grid.maxRows = Infinity 381 | } 382 | } 383 | const takeType = () => { 384 | if (realRows < maxRows + 1) return 'incomplete-wrap' 385 | if (computeRows > maxRows) return 'collapsible' 386 | return 'complete-wrap' 387 | } 388 | return { 389 | grid, 390 | expanded, 391 | toggle, 392 | type: takeType(), 393 | } 394 | } 395 | 396 | const QueryForm: React.FC = observer((props) => { 397 | const { grid, expanded, toggle, type } = useCollapseGrid(1) 398 | 399 | const renderActions = () => { 400 | return ( 401 | <Fragment> 402 | <Submit onSubmit={console.log}>查询</Submit> 403 | <Reset>重置</Reset> 404 | </Fragment> 405 | ) 406 | } 407 | 408 | const renderButtonGroup = () => { 409 | if (type === 'incomplete-wrap') { 410 | return ( 411 | <FormButtonGroup.FormItem> 412 | <FormButtonGroup>{renderActions()}</FormButtonGroup> 413 | </FormButtonGroup.FormItem> 414 | ) 415 | } 416 | if (type === 'collapsible') { 417 | return ( 418 | <Fragment> 419 | <FormButtonGroup> 420 | <a 421 | href="" 422 | onClick={(e) => { 423 | e.preventDefault() 424 | toggle() 425 | }} 426 | > 427 | {expanded ? '收起' : '展开'} 428 | </a> 429 | </FormButtonGroup> 430 | <FormButtonGroup align="right">{renderActions()}</FormButtonGroup> 431 | </Fragment> 432 | ) 433 | } 434 | return ( 435 | <FormButtonGroup align="right" style={{ display: 'flex', width: '100%' }}> 436 | {renderActions()} 437 | </FormButtonGroup> 438 | ) 439 | } 440 | 441 | return ( 442 | <Form {...props} layout="vertical" feedbackLayout="terse"> 443 | <FormGrid grid={grid}> 444 | {props.children} 445 | <FormGrid.GridColumn 446 | gridSpan={-1} 447 | style={{ display: 'flex', justifyContent: 'space-between' }} 448 | > 449 | {renderButtonGroup()} 450 | </FormGrid.GridColumn> 451 | </FormGrid> 452 | </Form> 453 | ) 454 | }) 455 | 456 | const SchemaField = createSchemaField({ 457 | components: { 458 | QueryForm, 459 | Input, 460 | Select, 461 | DatePicker, 462 | FormItem, 463 | }, 464 | }) 465 | 466 | export default () => { 467 | const form = useMemo(() => createForm(), []) 468 | return ( 469 | <FormProvider form={form}> 470 | <SchemaField> 471 | <SchemaField.Object x-component="QueryForm"> 472 | <SchemaField.String 473 | name="input1" 474 | title="Input 1" 475 | x-component="Input" 476 | x-decorator="FormItem" 477 | /> 478 | <SchemaField.String 479 | name="input2" 480 | title="Input 2" 481 | x-component="Input" 482 | x-decorator="FormItem" 483 | /> 484 | 485 | <SchemaField.String 486 | name="select1" 487 | title="Select 1" 488 | x-component="Select" 489 | x-decorator="FormItem" 490 | /> 491 | <SchemaField.String 492 | name="select2" 493 | title="Select 2" 494 | x-component="Select" 495 | x-decorator="FormItem" 496 | /> 497 | <SchemaField.String 498 | name="date" 499 | title="DatePicker" 500 | x-component="DatePicker" 501 | x-decorator="FormItem" 502 | /> 503 | <SchemaField.String 504 | name="dateRange" 505 | title="DatePicker.RangePicker" 506 | x-component="DatePicker.RangePicker" 507 | x-decorator="FormItem" 508 | x-decorator-props={{ 509 | gridSpan: 2, 510 | }} 511 | /> 512 | <SchemaField.String 513 | name="select3" 514 | title="Select 3" 515 | x-component="Select" 516 | x-decorator="FormItem" 517 | /> 518 | </SchemaField.Object> 519 | </SchemaField> 520 | </FormProvider> 521 | ) 522 | } 523 | ``` 524 | 525 | ## API 526 | 527 | ### FormGrid 528 | 529 | | 属性名 | 类型 | 描述 | 默认值 | 530 | | ------------- | ---------------------- | -------------------------------------------------------------- | ----------------- | 531 | | minWidth | `number \| number[]` | 元素最小宽度 | 100 | 532 | | maxWidth | `number \| number[]` | 元素最大宽度 | - | 533 | | minColumns | `number \| number[]` | 最小列数 | 0 | 534 | | maxColumns | `number \| number[]` | 最大列数 | - | 535 | | breakpoints | number[] | 容器尺寸断点 | `[720,1280,1920]` | 536 | | columnGap | number | 列间距 | 8 | 537 | | rowGap | number | 行间距 | 4 | 538 | | colWrap | boolean | 自动换行 | true | 539 | | strictAutoFit | boolean | GridItem 宽度是否严格受限于 maxWidth,不受限的话会自动占满容器 | false | 540 | | shouldVisible | `(node,grid)=>boolean` | 是否需要显示当前节点 | `()=>true` | 541 | | grid | `Grid` | 外部传入 Grid 实例,用于实现更复杂的布局逻辑 | - | 542 | 543 | 注意: 544 | 545 | - minWidth 生效优先级高于 minColumn 546 | - maxWidth 优先级高于 maxColumn 547 | - minWidth/maxWidth/minColumns/maxColumns 的数组格式代表与断点数组映射 548 | 549 | ### FormGrid.GridColumn 550 | 551 | | 属性名 | 类型 | 描述 | 默认值 | 552 | | -------- | ------ | ---------------------------------------------------- | ------ | 553 | | gridSpan | number | 元素所跨列数,如果为-1,那么会自动反向跨列填补单元格 | 1 | 554 | 555 | ### FormGrid.createFormGrid 556 | 557 | 从上下文中读取 Grid 实例 558 | 559 | ```ts 560 | interface createFormGrid { 561 | (props: IGridProps): Grid 562 | } 563 | ``` 564 | 565 | - IGridProps 参考 FormGrid 属性 566 | - Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid 567 | 568 | ### FormGrid.useFormGrid 569 | 570 | 从上下文中读取 Grid 实例 571 | 572 | ```ts 573 | interface useFormGrid { 574 | (): Grid 575 | } 576 | ``` 577 | 578 | - Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid 579 | ``` -------------------------------------------------------------------------------- /packages/vue/src/__tests__/field.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import Vue, { FunctionalComponentOptions } from 'vue' 2 | import { render, fireEvent, waitFor } from '@testing-library/vue' 3 | import { defineComponent, h, ref } from '@vue/composition-api' 4 | import { 5 | createForm, 6 | Field as FieldType, 7 | isField, 8 | isVoidField, 9 | onFieldChange, 10 | } from '@formily/core' 11 | import { useField, useFormEffects, connect, mapProps, mapReadPretty } from '../' 12 | import { 13 | FormProvider, 14 | ArrayField, 15 | ObjectField, 16 | VoidField, 17 | Field, 18 | } from '../vue2-components' 19 | import ReactiveField from '../components/ReactiveField' 20 | // import { expectThrowError } from './shared' 21 | 22 | Vue.component('FormProvider', FormProvider) 23 | Vue.component('ArrayField', ArrayField) 24 | Vue.component('ObjectField', ObjectField) 25 | Vue.component('VoidField', VoidField) 26 | Vue.component('Field', Field) 27 | Vue.component('ReactiveField', ReactiveField as unknown as Vue) 28 | 29 | const Decorator = defineComponent({ 30 | props: ['label'], 31 | render(h) { 32 | return h( 33 | 'div', 34 | { 35 | attrs: this.$attrs, 36 | }, 37 | [this.label, this.$slots.default] 38 | ) 39 | }, 40 | }) 41 | 42 | const Input = defineComponent({ 43 | props: ['value'], 44 | setup(props, { attrs, listeners }) { 45 | const fieldRef = useField() 46 | return () => { 47 | const field = fieldRef.value 48 | return h('input', { 49 | class: 'test-input', 50 | attrs: { 51 | ...attrs, 52 | value: props.value, 53 | 'data-testid': field.path.toString(), 54 | }, 55 | on: { 56 | ...listeners, 57 | input: listeners.change, 58 | }, 59 | }) 60 | } 61 | }, 62 | }) 63 | 64 | const Normal: FunctionalComponentOptions = { 65 | functional: true, 66 | render(h) { 67 | return h('div') 68 | }, 69 | } 70 | 71 | test('render field', async () => { 72 | const form = createForm() 73 | const onChange = jest.fn() 74 | const atChange = jest.fn() 75 | const atBlur = jest.fn() 76 | const atFocus = jest.fn() 77 | 78 | const { getByTestId, queryByTestId, queryByText } = render( 79 | defineComponent({ 80 | name: 'TestComponent', 81 | setup() { 82 | return { 83 | form, 84 | Normal, 85 | Input, 86 | Decorator, 87 | onChange, 88 | atChange, 89 | atFocus, 90 | atBlur, 91 | } 92 | }, 93 | template: `<FormProvider :form="form"> 94 | <Field 95 | name="aa" 96 | :decorator="[Decorator, {label: 'aa-decorator'}]" 97 | :component="[Input, { onChange }]" 98 | /> 99 | <ArrayField name="bb" :decorator="[Decorator]"> 100 | <div data-testid="bb-children"></div> 101 | </ArrayField> 102 | <ObjectField name="cc" :decorator="[Decorator]"> 103 | <Field name="mm" :decorator="[Decorator]" :component="[Input]" /> 104 | <ObjectField name="pp" :decorator="[Decorator]" /> 105 | <ArrayField name="tt" :decorator="[Decorator]" /> 106 | <VoidField name="ww" /> 107 | </ObjectField> 108 | <VoidField name="dd" :decorator="[Decorator]"> 109 | <div data-testid="dd-children"> 110 | <Field name="oo" :decorator="[Decorator]" :component="[Input]" /> 111 | </div> 112 | </VoidField> 113 | <VoidField name="xx" :decorator="[Decorator]" :component="[Normal]" /> 114 | <Field 115 | name="ee" 116 | :visible="false" 117 | :decorator="[Decorator]" 118 | :component="[Input]" 119 | /> 120 | <Field name="ff" :decorator="[]" :component="[]" /> 121 | <Field name="gg" :decorator="null" :component="null" /> 122 | <Field name="hh" :decorator="[null]" :component="[null, null]" /> 123 | <Field 124 | name="kk" 125 | :decorator="[Decorator]" 126 | :component="[Input, { onChange: null }]" 127 | /> 128 | <Field 129 | name="ll" 130 | :decorator="[Decorator]" 131 | :component="[Input, { '@change': atChange, '@focus': atFocus, '@blur': atBlur }]" 132 | /> 133 | <Field 134 | name="mm" 135 | :decorator="[Decorator]" 136 | ><div data-testid="mm-children"></div></Field> 137 | </FormProvider>`, 138 | }) 139 | ) 140 | expect(form.mounted).toBeTruthy() 141 | expect(form.query('aa').take().mounted).toBeTruthy() 142 | expect(form.query('bb').take().mounted).toBeTruthy() 143 | expect(form.query('cc').take().mounted).toBeTruthy() 144 | expect(form.query('dd').take().mounted).toBeTruthy() 145 | await fireEvent.update(getByTestId('aa'), '123') 146 | await fireEvent.update(getByTestId('kk'), '123') 147 | await fireEvent.focus(getByTestId('ll')) 148 | await fireEvent.blur(getByTestId('ll')) 149 | await fireEvent.update(getByTestId('ll'), '123') 150 | expect(onChange).toBeCalledTimes(1) 151 | expect(atChange).toBeCalledTimes(1) 152 | expect(atFocus).toBeCalledTimes(1) 153 | expect(atBlur).toBeCalledTimes(1) 154 | expect(getByTestId('bb-children')).not.toBeUndefined() 155 | expect(getByTestId('dd-children')).not.toBeUndefined() 156 | expect(queryByTestId('ee')).toBeNull() 157 | expect(form.query('aa').get('value')).toEqual('123') 158 | expect(form.query('kk').get('value')).toEqual('123') 159 | expect(getByTestId('mm-children')).not.toBeUndefined() 160 | expect(queryByText('aa-decorator')).not.toBeNull() 161 | }) 162 | 163 | const InputWithSlot = defineComponent({ 164 | props: ['value'], 165 | setup(props, { attrs, listeners, slots }) { 166 | const fieldRef = useField() 167 | return () => { 168 | const field = fieldRef.value 169 | return h('div', {}, [ 170 | h('input', { 171 | class: 'test-input', 172 | attrs: { 173 | ...attrs, 174 | value: props.value, 175 | 'data-testid': field.path.toString(), 176 | }, 177 | on: { 178 | ...listeners, 179 | input: listeners.change, 180 | }, 181 | }), 182 | [slots['append']?.({ path: field.path.toString() })], 183 | ]) 184 | } 185 | }, 186 | }) 187 | 188 | test('render in nesting slots with (ObjectField/ArrayField) no decorator', async () => { 189 | const form = createForm() 190 | 191 | const { getByTestId } = render( 192 | defineComponent({ 193 | name: 'TestComponent', 194 | setup() { 195 | return { 196 | form, 197 | Normal, 198 | InputWithSlot, 199 | Decorator, 200 | } 201 | }, 202 | template: `<FormProvider :form="form"> 203 | <ObjectField name="cc" :component="['div']"> 204 | <Field name="mm" :decorator="[Decorator]" :component="[InputWithSlot]"> 205 | <template #append="{ path }"> 206 | <span :data-testid="'slot-prop-' +path"></span> 207 | </template> 208 | </Field> 209 | </ObjectField> 210 | <VoidField name="dd" :component="['div']"> 211 | <Field name="oo" :decorator="[Decorator]" :component="[InputWithSlot]" /> 212 | </VoidField> 213 | 214 | </FormProvider>`, 215 | }) 216 | ) 217 | 218 | expect(getByTestId('oo')).not.toBeUndefined() 219 | expect(getByTestId('cc.mm')).not.toBeUndefined() 220 | expect(getByTestId('slot-prop-cc.mm')).not.toBeUndefined() 221 | }) 222 | 223 | test('render field with html attrs', async () => { 224 | const form = createForm() 225 | 226 | const { getByTestId, container } = render( 227 | defineComponent({ 228 | name: 'TestComponent', 229 | setup() { 230 | return { 231 | form, 232 | Input, 233 | Decorator, 234 | } 235 | }, 236 | template: `<FormProvider :form="form"> 237 | <Field 238 | name="aa" 239 | :decorator="[Decorator, { 240 | 'data-testid': 'decorator', 241 | class: { 242 | 'test-class': true 243 | }, 244 | style: { 245 | marginRight: '10px' 246 | } 247 | }]" 248 | :component="[Input, { 249 | class: { 250 | 'test-class': true 251 | }, 252 | style: { 253 | marginLeft: '10px' 254 | } 255 | }]" 256 | /> 257 | </FormProvider>`, 258 | }) 259 | ) 260 | expect(form.mounted).toBeTruthy() 261 | expect(form.query('aa').take().mounted).toBeTruthy() 262 | expect(getByTestId('aa').className.indexOf('test-input') !== -1).toBeTruthy() 263 | expect(getByTestId('aa').className.indexOf('test-class') !== -1).toBeTruthy() 264 | expect(getByTestId('aa').style.marginLeft).toEqual('10px') 265 | expect( 266 | getByTestId('decorator').className.indexOf('test-class') !== -1 267 | ).toBeTruthy() 268 | expect(getByTestId('decorator').style.marginRight).toEqual('10px') 269 | }) 270 | 271 | test('ReactiveField', () => { 272 | render({ 273 | template: `<ReactiveField />`, 274 | }) 275 | render({ 276 | template: `<ReactiveField> 277 | <div></div> 278 | </ReactiveField>`, 279 | }) 280 | }) 281 | 282 | test('useAttch', async () => { 283 | const form1 = createForm() 284 | const MyComponent = defineComponent({ 285 | props: ['form', 'name1', 'name2', 'name3', 'name4'], 286 | data() { 287 | return { Input, Decorator } 288 | }, 289 | template: `<FormProvider :form="form"> 290 | <Field :name="name1" :decorator="[Decorator]" :component="[Input]" /> 291 | <ArrayField :name="name2" :decorator="[Decorator]" :component="[Input]" /> 292 | <ObjectField :name="name3" :decorator="[Decorator]" :component="[Input]" /> 293 | <VoidField :name="name4" :decorator="[Decorator]" :component="[Input]" /> 294 | </FormProvider>`, 295 | }) 296 | const { updateProps } = render(MyComponent, { 297 | props: { 298 | form: form1, 299 | name1: 'aa', 300 | name2: 'bb', 301 | name3: 'cc', 302 | name4: 'dd', 303 | }, 304 | }) 305 | expect(form1.mounted).toBeTruthy() 306 | expect(form1.query('aa').take().mounted).toBeTruthy() 307 | expect(form1.query('bb').take().mounted).toBeTruthy() 308 | expect(form1.query('cc').take().mounted).toBeTruthy() 309 | expect(form1.query('dd').take().mounted).toBeTruthy() 310 | await updateProps({ 311 | name1: 'aaa', 312 | name2: 'bbb', 313 | name3: 'ccc', 314 | name4: 'ddd', 315 | }) 316 | await Vue.nextTick() 317 | expect(form1.query('aa').take().mounted).toBeFalsy() 318 | expect(form1.query('bb').take().mounted).toBeFalsy() 319 | expect(form1.query('cc').take().mounted).toBeFalsy() 320 | expect(form1.query('dd').take().mounted).toBeFalsy() 321 | expect(form1.query('aaa').take().mounted).toBeTruthy() 322 | expect(form1.query('bbb').take().mounted).toBeTruthy() 323 | expect(form1.query('ccc').take().mounted).toBeTruthy() 324 | expect(form1.query('ddd').take().mounted).toBeTruthy() 325 | const form2 = createForm() 326 | await updateProps({ 327 | form: form2, 328 | }) 329 | await Vue.nextTick() 330 | expect(form1.unmounted).toBeTruthy() 331 | expect(form2.mounted).toBeTruthy() 332 | }) 333 | 334 | test('useFormEffects', async () => { 335 | const form = createForm() 336 | const CustomField = defineComponent({ 337 | props: ['value'], 338 | setup(props) { 339 | const fieldRef = useField<FieldType>() 340 | useFormEffects(() => { 341 | onFieldChange('aa', ['value'], (target) => { 342 | if (isVoidField(target)) return 343 | fieldRef.value.setValue(target.value) 344 | }) 345 | }) 346 | return () => { 347 | return h('div', { attrs: { 'data-testid': 'custom-value' } }, [ 348 | props.value, 349 | ]) 350 | } 351 | }, 352 | }) 353 | const { queryByTestId } = render({ 354 | data() { 355 | return { form, Decorator, Input, CustomField } 356 | }, 357 | template: `<FormProvider :form="form"> 358 | <Field name="aa" :decorator="[Decorator]" :component="[Input]" /> 359 | <Field name="bb" :component="[CustomField]" /> 360 | </FormProvider>`, 361 | }) 362 | expect(queryByTestId('custom-value').textContent).toEqual('') 363 | form.query('aa').take((aa) => { 364 | if (isField(aa)) { 365 | const value = '123' as any 366 | aa.setValue(value) 367 | } 368 | }) 369 | await waitFor(() => { 370 | expect(queryByTestId('custom-value').textContent).toEqual('123') 371 | }) 372 | }) 373 | 374 | test('useFormEffects: should be reregister when formRef change', async () => { 375 | const CustomField = defineComponent({ 376 | setup() { 377 | const reactiveText = ref() 378 | useFormEffects(() => { 379 | onFieldChange('aa', ['value'], (target) => { 380 | if (isVoidField(target)) return 381 | reactiveText.value = target.value 382 | }) 383 | }) 384 | return () => 385 | h('div', { attrs: { 'data-testid': 'custom-value' } }, [ 386 | reactiveText.value, 387 | ]) 388 | }, 389 | }) 390 | 391 | const { queryByTestId } = render({ 392 | setup() { 393 | const formRef = ref(createForm()) 394 | return { 395 | formRef, 396 | Input, 397 | CustomField, 398 | changeForm() { 399 | // form change 400 | formRef.value = createForm() 401 | formRef.value.setValues({ aa: 'text' }) 402 | }, 403 | } 404 | }, 405 | template: `<FormProvider :form="formRef"> 406 | <Field name="aa" :decorator="[Decorator]" :component="[Input]" /> 407 | <VoidField name="bb" :component="[CustomField]" /> 408 | <button data-testid="btn" @click="changeForm()">Change</button> 409 | </FormProvider>`, 410 | }) 411 | 412 | expect(queryByTestId('custom-value').textContent).toEqual('') 413 | queryByTestId('btn').click() 414 | await waitFor(() => { 415 | expect(queryByTestId('custom-value').textContent).toEqual('text') 416 | }) 417 | }) 418 | 419 | test('connect', async () => { 420 | const CustomField = connect( 421 | { 422 | functional: true, 423 | props: ['list'], 424 | render(h, context) { 425 | return h('div', [context.props.list]) 426 | }, 427 | }, 428 | mapProps({ value: 'list', loading: true }, (props, field) => { 429 | return { 430 | ...props, 431 | mounted: field.mounted ? 1 : 2, 432 | } 433 | }), 434 | mapReadPretty({ 435 | render(h) { 436 | return h('div', 'read pretty') 437 | }, 438 | }) 439 | ) 440 | const BaseComponent = { 441 | functional: true, 442 | name: 'BaseComponent', 443 | render(h, context) { 444 | return h('div', [context.props.value]) 445 | }, 446 | } as FunctionalComponentOptions 447 | const CustomField2 = connect( 448 | BaseComponent, 449 | mapProps({ value: true, loading: true }), 450 | mapReadPretty({ 451 | render(h) { 452 | return h('div', 'read pretty') 453 | }, 454 | }) 455 | ) 456 | 457 | const CustomField3 = connect( 458 | Input, 459 | mapProps(), 460 | mapReadPretty({ 461 | render(h) { 462 | return h('div', 'read pretty') 463 | }, 464 | }) 465 | ) 466 | 467 | const CustomFormItem = connect( 468 | { 469 | functional: true, 470 | render(h, context) { 471 | return h('div', context.data, context.children) 472 | }, 473 | }, 474 | mapProps(), 475 | mapReadPretty({ 476 | render(h) { 477 | return h('div', 'read pretty') 478 | }, 479 | }) 480 | ) 481 | 482 | const form = createForm() 483 | const { queryByText, getByTestId } = render({ 484 | data() { 485 | return { 486 | form, 487 | Decorator, 488 | CustomField, 489 | CustomField2, 490 | CustomField3, 491 | CustomFormItem, 492 | } 493 | }, 494 | template: `<FormProvider :form="form"> 495 | <Field name="aa" :decorator="[Decorator]" :component="[CustomField]" /> 496 | <Field name="bb" :decorator="[Decorator]" :component="[CustomField2]" /> 497 | <Field name="cc" :decorator="[Decorator]" :component="[CustomField3]" /> 498 | <component :is="CustomFormItem">dd</component> 499 | </FormProvider>`, 500 | }) 501 | form.query('aa').take((field) => { 502 | field.setState((state) => { 503 | state.value = '123' 504 | }) 505 | }) 506 | 507 | expect(queryByText('dd')).toBeVisible() 508 | await waitFor(() => { 509 | expect(queryByText('123')).toBeVisible() 510 | }) 511 | 512 | fireEvent.update(getByTestId('cc'), '123') 513 | expect(queryByText('123')).toBeVisible() 514 | expect(form.query('cc').get('value')).toEqual('123') 515 | 516 | form.query('aa').take((field) => { 517 | if (!isField(field)) return 518 | field.readPretty = true 519 | }) 520 | await waitFor(() => { 521 | expect(queryByText('123')).toBeNull() 522 | expect(queryByText('read pretty')).toBeVisible() 523 | }) 524 | }) 525 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormGrid.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormGrid 2 | 3 | > FormGrid component 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { FormItem, Input, FormGrid } from '@formily/antd' 10 | import { FormProvider, createSchemaField } from '@formily/react' 11 | import { createForm } from '@formily/core' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | Input, 17 | FormGrid, 18 | }, 19 | }) 20 | 21 | const form = createForm() 22 | 23 | export default () => { 24 | return ( 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.Void 28 | x-component="FormGrid" 29 | x-component-props={{ 30 | maxColumns: 3, 31 | minColumns: 2, 32 | }} 33 | > 34 | <SchemaField.String 35 | name="aaa" 36 | title="aaa" 37 | x-decorator="FormItem" 38 | x-decorator-props={{ gridSpan: 2 }} 39 | x-component="Input" 40 | /> 41 | <SchemaField.String 42 | name="bbb" 43 | title="bbb" 44 | x-decorator="FormItem" 45 | x-component="Input" 46 | /> 47 | <SchemaField.String 48 | name="ccc" 49 | title="ccc" 50 | x-decorator="FormItem" 51 | x-component="Input" 52 | /> 53 | <SchemaField.String 54 | name="ddd" 55 | title="ddd" 56 | x-decorator="FormItem" 57 | x-component="Input" 58 | /> 59 | <SchemaField.String 60 | name="eee" 61 | title="eee" 62 | x-decorator="FormItem" 63 | x-component="Input" 64 | /> 65 | <SchemaField.String 66 | name="fff" 67 | title="fff" 68 | x-decorator="FormItem" 69 | x-component="Input" 70 | /> 71 | <SchemaField.String 72 | name="ggg" 73 | title="ggg" 74 | x-decorator="FormItem" 75 | x-component="Input" 76 | /> 77 | </SchemaField.Void> 78 | </SchemaField> 79 | </FormProvider> 80 | ) 81 | } 82 | ``` 83 | 84 | ## JSON Schema case 85 | 86 | ```tsx 87 | import React from 'react' 88 | import { FormItem, Input, FormGrid } from '@formily/antd' 89 | import { FormProvider, createSchemaField } from '@formily/react' 90 | import { createForm } from '@formily/core' 91 | 92 | const SchemaField = createSchemaField({ 93 | components: { 94 | FormItem, 95 | Input, 96 | FormGrid, 97 | }, 98 | }) 99 | 100 | const form = createForm() 101 | 102 | const schema = { 103 | type: 'object', 104 | properties: { 105 | grid: { 106 | type: 'void', 107 | 'x-component': 'FormGrid', 108 | 'x-component-props': { 109 | minColumns: [4, 6, 10], 110 | }, 111 | properties: { 112 | aaa: { 113 | type: 'string', 114 | title: 'AAA', 115 | 'x-decorator': 'FormItem', 116 | 'x-component': 'Input', 117 | }, 118 | bbb: { 119 | type: 'string', 120 | title: 'BBB', 121 | 'x-decorator': 'FormItem', 122 | 'x-component': 'Input', 123 | }, 124 | ccc: { 125 | type: 'string', 126 | title: 'CCC', 127 | 'x-decorator': 'FormItem', 128 | 'x-component': 'Input', 129 | }, 130 | ddd: { 131 | type: 'string', 132 | title: 'DDD', 133 | 'x-decorator': 'FormItem', 134 | 'x-component': 'Input', 135 | }, 136 | eee: { 137 | type: 'string', 138 | title: 'EEE', 139 | 'x-decorator': 'FormItem', 140 | 'x-component': 'Input', 141 | }, 142 | fff: { 143 | type: 'string', 144 | title: 'FFF', 145 | 'x-decorator': 'FormItem', 146 | 'x-component': 'Input', 147 | }, 148 | ggg: { 149 | type: 'string', 150 | title: 'GGG', 151 | 'x-decorator': 'FormItem', 152 | 'x-component': 'Input', 153 | }, 154 | }, 155 | }, 156 | }, 157 | } 158 | 159 | export default () => { 160 | return ( 161 | <FormProvider form={form}> 162 | <SchemaField schema={schema} /> 163 | </FormProvider> 164 | ) 165 | } 166 | ``` 167 | 168 | ## Native case 169 | 170 | ```tsx 171 | import React from 'react' 172 | import { FormGrid } from '@formily/antd' 173 | 174 | const { GridColumn } = FormGrid 175 | const Cell = ({ children }) => { 176 | return ( 177 | <div 178 | style={{ 179 | backgroundColor: '#AAA', 180 | color: '#FFF', 181 | height: 30, 182 | display: 'flex', 183 | alignItems: 'center', 184 | padding: '0 10px', 185 | }} 186 | > 187 | {children} 188 | </div> 189 | ) 190 | } 191 | export default () => { 192 | return ( 193 | <React.Fragment> 194 | <p>maxColumns 3 + minColumns 2</p> 195 | <FormGrid maxColumns={3} minColumns={2} columnGap={4}> 196 | <GridColumn gridSpan={4}> 197 | <Cell>1</Cell> 198 | </GridColumn> 199 | <GridColumn> 200 | <Cell>2</Cell> 201 | </GridColumn> 202 | <GridColumn> 203 | <Cell>3</Cell> 204 | </GridColumn> 205 | <GridColumn> 206 | <Cell>4</Cell> 207 | </GridColumn> 208 | <GridColumn> 209 | <Cell>5</Cell> 210 | </GridColumn> 211 | <GridColumn> 212 | <Cell>6</Cell> 213 | </GridColumn> 214 | </FormGrid> 215 | <p>maxColumns 3</p> 216 | <FormGrid maxColumns={3} columnGap={4}> 217 | <GridColumn gridSpan={2}> 218 | <Cell>1</Cell> 219 | </GridColumn> 220 | <GridColumn> 221 | <Cell>2</Cell> 222 | </GridColumn> 223 | <GridColumn> 224 | <Cell>3</Cell> 225 | </GridColumn> 226 | <GridColumn> 227 | <Cell>4</Cell> 228 | </GridColumn> 229 | <GridColumn> 230 | <Cell>5</Cell> 231 | </GridColumn> 232 | <GridColumn> 233 | <Cell>6</Cell> 234 | </GridColumn> 235 | </FormGrid> 236 | <p>minColumns 2</p> 237 | <FormGrid minColumns={2} columnGap={4}> 238 | <GridColumn gridSpan={2}> 239 | <Cell>1</Cell> 240 | </GridColumn> 241 | <GridColumn> 242 | <Cell>2</Cell> 243 | </GridColumn> 244 | <GridColumn> 245 | <Cell>3</Cell> 246 | </GridColumn> 247 | <GridColumn> 248 | <Cell>4</Cell> 249 | </GridColumn> 250 | <GridColumn> 251 | <Cell>5</Cell> 252 | </GridColumn> 253 | <GridColumn> 254 | <Cell>6</Cell> 255 | </GridColumn> 256 | </FormGrid> 257 | <p>Null</p> 258 | <FormGrid columnGap={4}> 259 | <GridColumn gridSpan={2}> 260 | <Cell>1</Cell> 261 | </GridColumn> 262 | <GridColumn> 263 | <Cell>2</Cell> 264 | </GridColumn> 265 | <GridColumn> 266 | <Cell>3</Cell> 267 | </GridColumn> 268 | <GridColumn> 269 | <Cell>4</Cell> 270 | </GridColumn> 271 | <GridColumn> 272 | <Cell>5</Cell> 273 | </GridColumn> 274 | <GridColumn> 275 | <Cell>6</Cell> 276 | </GridColumn> 277 | </FormGrid> 278 | <p>minWidth 150 +maxColumns 3</p> 279 | <FormGrid minWidth={150} maxColumns={3} columnGap={4}> 280 | <GridColumn gridSpan={2}> 281 | <Cell>1</Cell> 282 | </GridColumn> 283 | <GridColumn> 284 | <Cell>2</Cell> 285 | </GridColumn> 286 | <GridColumn> 287 | <Cell>3</Cell> 288 | </GridColumn> 289 | <GridColumn> 290 | <Cell>4</Cell> 291 | </GridColumn> 292 | <GridColumn> 293 | <Cell>5</Cell> 294 | </GridColumn> 295 | <GridColumn> 296 | <Cell>6</Cell> 297 | </GridColumn> 298 | </FormGrid> 299 | <p>maxWidth 120+minColumns 2</p> 300 | <FormGrid maxWidth={120} minColumns={2} columnGap={4}> 301 | <GridColumn gridSpan={2}> 302 | <Cell>1</Cell> 303 | </GridColumn> 304 | <GridColumn> 305 | <Cell>2</Cell> 306 | </GridColumn> 307 | <GridColumn> 308 | <Cell>3</Cell> 309 | </GridColumn> 310 | <GridColumn> 311 | <Cell>4</Cell> 312 | </GridColumn> 313 | <GridColumn> 314 | <Cell>5</Cell> 315 | </GridColumn> 316 | <GridColumn> 317 | <Cell>6</Cell> 318 | </GridColumn> 319 | </FormGrid> 320 | <p>maxWidth 120 + gridSpan -1</p> 321 | <FormGrid maxWidth={120} columnGap={4}> 322 | <GridColumn gridSpan={2}> 323 | <Cell>1</Cell> 324 | </GridColumn> 325 | <GridColumn> 326 | <Cell>2</Cell> 327 | </GridColumn> 328 | <GridColumn gridSpan={-1}> 329 | <Cell>3</Cell> 330 | </GridColumn> 331 | </FormGrid> 332 | </React.Fragment> 333 | ) 334 | } 335 | ``` 336 | 337 | ## Query Form case 338 | 339 | ```tsx 340 | import React, { useMemo, Fragment } from 'react' 341 | import { createForm } from '@formily/core' 342 | import { createSchemaField, FormProvider, observer } from '@formily/react' 343 | import { 344 | Form, 345 | Input, 346 | Select, 347 | DatePicker, 348 | FormItem, 349 | FormGrid, 350 | Submit, 351 | Reset, 352 | FormButtonGroup, 353 | } from '@formily/antd' 354 | 355 | const useCollapseGrid = (maxRows: number) => { 356 | const grid = useMemo( 357 | () => 358 | FormGrid.createFormGrid({ 359 | maxColumns: 4, 360 | maxWidth: 240, 361 | maxRows: maxRows, 362 | shouldVisible: (node, grid) => { 363 | if (node.index === grid.childSize - 1) return true 364 | if (grid.maxRows === Infinity) return true 365 | return node.shadowRow < maxRows + 1 366 | }, 367 | }), 368 | [] 369 | ) 370 | const expanded = grid.maxRows === Infinity 371 | const realRows = grid.shadowRows 372 | const computeRows = grid.fullnessLastColumn 373 | ? grid.shadowRows - 1 374 | : grid.shadowRows 375 | 376 | const toggle = () => { 377 | if (grid.maxRows === Infinity) { 378 | grid.maxRows = maxRows 379 | } else { 380 | grid.maxRows = Infinity 381 | } 382 | } 383 | const takeType = () => { 384 | if (realRows < maxRows + 1) return 'incomplete-wrap' 385 | if (computeRows > maxRows) return 'collapsible' 386 | return 'complete-wrap' 387 | } 388 | return { 389 | grid, 390 | expanded, 391 | toggle, 392 | type: takeType(), 393 | } 394 | } 395 | 396 | const QueryForm: React.FC = observer((props) => { 397 | const { grid, expanded, toggle, type } = useCollapseGrid(1) 398 | 399 | const renderActions = () => { 400 | return ( 401 | <Fragment> 402 | <Submit onSubmit={console.log}>Query</Submit> 403 | <Reset>Reset</Reset> 404 | </Fragment> 405 | ) 406 | } 407 | 408 | const renderButtonGroup = () => { 409 | if (type === 'incomplete-wrap') { 410 | return ( 411 | <FormButtonGroup.FormItem> 412 | <FormButtonGroup>{renderActions()}</FormButtonGroup> 413 | </FormButtonGroup.FormItem> 414 | ) 415 | } 416 | if (type === 'collapsible') { 417 | return ( 418 | <Fragment> 419 | <FormButtonGroup> 420 | <a 421 | href="" 422 | onClick={(e) => { 423 | e.preventDefault() 424 | toggle() 425 | }} 426 | > 427 | {expanded ? 'Fold' : 'UnFold'} 428 | </a> 429 | </FormButtonGroup> 430 | <FormButtonGroup align="right">{renderActions()}</FormButtonGroup> 431 | </Fragment> 432 | ) 433 | } 434 | return ( 435 | <FormButtonGroup align="right" style={{ display: 'flex', width: '100%' }}> 436 | {renderActions()} 437 | </FormButtonGroup> 438 | ) 439 | } 440 | 441 | return ( 442 | <Form {...props} layout="vertical" feedbackLayout="terse"> 443 | <FormGrid grid={grid}> 444 | {props.children} 445 | <FormGrid.GridColumn 446 | gridSpan={-1} 447 | style={{ display: 'flex', justifyContent: 'space-between' }} 448 | > 449 | {renderButtonGroup()} 450 | </FormGrid.GridColumn> 451 | </FormGrid> 452 | </Form> 453 | ) 454 | }) 455 | 456 | const SchemaField = createSchemaField({ 457 | components: { 458 | QueryForm, 459 | Input, 460 | Select, 461 | DatePicker, 462 | FormItem, 463 | }, 464 | }) 465 | 466 | export default () => { 467 | const form = useMemo(() => createForm(), []) 468 | return ( 469 | <FormProvider form={form}> 470 | <SchemaField> 471 | <SchemaField.Object x-component="QueryForm"> 472 | <SchemaField.String 473 | name="input1" 474 | title="Input 1" 475 | x-component="Input" 476 | x-decorator="FormItem" 477 | /> 478 | <SchemaField.String 479 | name="input2" 480 | title="Input 2" 481 | x-component="Input" 482 | x-decorator="FormItem" 483 | /> 484 | 485 | <SchemaField.String 486 | name="select1" 487 | title="Select 1" 488 | x-component="Select" 489 | x-decorator="FormItem" 490 | /> 491 | <SchemaField.String 492 | name="select2" 493 | title="Select 2" 494 | x-component="Select" 495 | x-decorator="FormItem" 496 | /> 497 | <SchemaField.String 498 | name="date" 499 | title="DatePicker" 500 | x-component="DatePicker" 501 | x-decorator="FormItem" 502 | /> 503 | <SchemaField.String 504 | name="dateRange" 505 | title="DatePicker.RangePicker" 506 | x-component="DatePicker.RangePicker" 507 | x-decorator="FormItem" 508 | x-decorator-props={{ 509 | gridSpan: 2, 510 | }} 511 | /> 512 | <SchemaField.String 513 | name="select3" 514 | title="Select 3" 515 | x-component="Select" 516 | x-decorator="FormItem" 517 | /> 518 | </SchemaField.Object> 519 | </SchemaField> 520 | </FormProvider> 521 | ) 522 | } 523 | ``` 524 | 525 | ## API 526 | 527 | ### FormGrid 528 | 529 | | Property name | Type | Description | Default value | 530 | | ------------- | ---------------------- | --------------------------------------------------------------------------------- | ----------------- | 531 | | minWidth | `number \| number[]` | Minimum element width | 100 | 532 | | maxWidth | `number \| number[]` | Maximum element width | - | 533 | | minColumns | `number \| number[]` | Minimum number of columns | 0 | 534 | | maxColumns | `number \| number[]` | Maximum number of columns | - | 535 | | breakpoints | number[] | Container size breakpoints | `[720,1280,1920]` | 536 | | columnGap | number | Column spacing | 8 | 537 | | rowGap | number | Row spacing | 4 | 538 | | colWrap | boolean | Wrap | true | 539 | | strictAutoFit | boolean | Is width strictly limited by maxWidth | false | 540 | | shouldVisible | `(node,grid)=>boolean` | Whether to show the current node | `()=>true` | 541 | | grid | `Grid` | Grid instance passed in from outside, used to implement more complex layout logic | - | 542 | 543 | note: 544 | 545 | - minWidth takes priority over minColumn 546 | - maxWidth has priority over maxColumn 547 | - The array format of minWidth/maxWidth/minColumns/maxColumns represents the mapping with the breakpoint array 548 | 549 | ### FormGrid.GridColumn 550 | 551 | | Property name | Type | Description | Default value | 552 | | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- | 553 | | gridSpan | number | The number of columns spanned by the element, if it is -1, it will automatically fill the cell across columns in reverse | 1 | 554 | 555 | ### FormGrid.createFormGrid 556 | 557 | Read the Grid instance from the context 558 | 559 | ```ts 560 | interface createFormGrid { 561 | (props: IGridProps): Grid 562 | } 563 | ``` 564 | 565 | - IGridProps reference FormGrid properties 566 | - Grid instance attribute method reference https://github.com/alibaba/formily/tree/formily_next/packages/grid 567 | 568 | ### FormGrid.useFormGrid 569 | 570 | Read the Grid instance from the context 571 | 572 | ```ts 573 | interface useFormGrid { 574 | (): Grid 575 | } 576 | ``` 577 | 578 | - Grid instance attribute method reference https://github.com/alibaba/formily/tree/formily_next/packages/grid 579 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormGrid.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormGrid 2 | 3 | > FormGrid component 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { FormItem, Input, FormGrid } from '@formily/next' 10 | import { FormProvider, createSchemaField } from '@formily/react' 11 | import { createForm } from '@formily/core' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | Input, 17 | FormGrid, 18 | }, 19 | }) 20 | 21 | const form = createForm() 22 | 23 | export default () => { 24 | return ( 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.Void 28 | x-component="FormGrid" 29 | x-component-props={{ 30 | maxColumns: 3, 31 | minColumns: 2, 32 | }} 33 | > 34 | <SchemaField.String 35 | name="aaa" 36 | title="aaa" 37 | x-decorator="FormItem" 38 | x-decorator-props={{ gridSpan: 2 }} 39 | x-component="Input" 40 | /> 41 | <SchemaField.String 42 | name="bbb" 43 | title="bbb" 44 | x-decorator="FormItem" 45 | x-component="Input" 46 | /> 47 | <SchemaField.String 48 | name="ccc" 49 | title="ccc" 50 | x-decorator="FormItem" 51 | x-component="Input" 52 | /> 53 | <SchemaField.String 54 | name="ddd" 55 | title="ddd" 56 | x-decorator="FormItem" 57 | x-component="Input" 58 | /> 59 | <SchemaField.String 60 | name="eee" 61 | title="eee" 62 | x-decorator="FormItem" 63 | x-component="Input" 64 | /> 65 | <SchemaField.String 66 | name="fff" 67 | title="fff" 68 | x-decorator="FormItem" 69 | x-component="Input" 70 | /> 71 | <SchemaField.String 72 | name="ggg" 73 | title="ggg" 74 | x-decorator="FormItem" 75 | x-component="Input" 76 | /> 77 | </SchemaField.Void> 78 | </SchemaField> 79 | </FormProvider> 80 | ) 81 | } 82 | ``` 83 | 84 | ## JSON Schema case 85 | 86 | ```tsx 87 | import React from 'react' 88 | import { FormItem, Input, FormGrid } from '@formily/next' 89 | import { FormProvider, createSchemaField } from '@formily/react' 90 | import { createForm } from '@formily/core' 91 | 92 | const SchemaField = createSchemaField({ 93 | components: { 94 | FormItem, 95 | Input, 96 | FormGrid, 97 | }, 98 | }) 99 | 100 | const form = createForm() 101 | 102 | const schema = { 103 | type: 'object', 104 | properties: { 105 | grid: { 106 | type: 'void', 107 | 'x-component': 'FormGrid', 108 | 'x-component-props': { 109 | minColumns: [4, 6, 10], 110 | }, 111 | properties: { 112 | aaa: { 113 | type: 'string', 114 | title: 'AAA', 115 | 'x-decorator': 'FormItem', 116 | 'x-component': 'Input', 117 | }, 118 | bbb: { 119 | type: 'string', 120 | title: 'BBB', 121 | 'x-decorator': 'FormItem', 122 | 'x-component': 'Input', 123 | }, 124 | ccc: { 125 | type: 'string', 126 | title: 'CCC', 127 | 'x-decorator': 'FormItem', 128 | 'x-component': 'Input', 129 | }, 130 | ddd: { 131 | type: 'string', 132 | title: 'DDD', 133 | 'x-decorator': 'FormItem', 134 | 'x-component': 'Input', 135 | }, 136 | eee: { 137 | type: 'string', 138 | title: 'EEE', 139 | 'x-decorator': 'FormItem', 140 | 'x-component': 'Input', 141 | }, 142 | fff: { 143 | type: 'string', 144 | title: 'FFF', 145 | 'x-decorator': 'FormItem', 146 | 'x-component': 'Input', 147 | }, 148 | ggg: { 149 | type: 'string', 150 | title: 'GGG', 151 | 'x-decorator': 'FormItem', 152 | 'x-component': 'Input', 153 | }, 154 | }, 155 | }, 156 | }, 157 | } 158 | 159 | export default () => { 160 | return ( 161 | <FormProvider form={form}> 162 | <SchemaField schema={schema} /> 163 | </FormProvider> 164 | ) 165 | } 166 | ``` 167 | 168 | ## Native case 169 | 170 | ```tsx 171 | import React from 'react' 172 | import { FormGrid } from '@formily/next' 173 | 174 | const { GridColumn } = FormGrid 175 | const Cell = ({ children }) => { 176 | return ( 177 | <div 178 | style={{ 179 | backgroundColor: '#AAA', 180 | color: '#FFF', 181 | height: 30, 182 | display: 'flex', 183 | alignItems: 'center', 184 | padding: '0 10px', 185 | }} 186 | > 187 | {children} 188 | </div> 189 | ) 190 | } 191 | export default () => { 192 | return ( 193 | <React.Fragment> 194 | <p>maxColumns 3 + minColumns 2</p> 195 | <FormGrid maxColumns={3} minColumns={2} columnGap={4}> 196 | <GridColumn gridSpan={4}> 197 | <Cell>1</Cell> 198 | </GridColumn> 199 | <GridColumn> 200 | <Cell>2</Cell> 201 | </GridColumn> 202 | <GridColumn> 203 | <Cell>3</Cell> 204 | </GridColumn> 205 | <GridColumn> 206 | <Cell>4</Cell> 207 | </GridColumn> 208 | <GridColumn> 209 | <Cell>5</Cell> 210 | </GridColumn> 211 | <GridColumn> 212 | <Cell>6</Cell> 213 | </GridColumn> 214 | </FormGrid> 215 | <p>maxColumns 3</p> 216 | <FormGrid maxColumns={3} columnGap={4}> 217 | <GridColumn gridSpan={2}> 218 | <Cell>1</Cell> 219 | </GridColumn> 220 | <GridColumn> 221 | <Cell>2</Cell> 222 | </GridColumn> 223 | <GridColumn> 224 | <Cell>3</Cell> 225 | </GridColumn> 226 | <GridColumn> 227 | <Cell>4</Cell> 228 | </GridColumn> 229 | <GridColumn> 230 | <Cell>5</Cell> 231 | </GridColumn> 232 | <GridColumn> 233 | <Cell>6</Cell> 234 | </GridColumn> 235 | </FormGrid> 236 | <p>minColumns 2</p> 237 | <FormGrid minColumns={2} columnGap={4}> 238 | <GridColumn gridSpan={2}> 239 | <Cell>1</Cell> 240 | </GridColumn> 241 | <GridColumn> 242 | <Cell>2</Cell> 243 | </GridColumn> 244 | <GridColumn> 245 | <Cell>3</Cell> 246 | </GridColumn> 247 | <GridColumn> 248 | <Cell>4</Cell> 249 | </GridColumn> 250 | <GridColumn> 251 | <Cell>5</Cell> 252 | </GridColumn> 253 | <GridColumn> 254 | <Cell>6</Cell> 255 | </GridColumn> 256 | </FormGrid> 257 | <p>Null</p> 258 | <FormGrid columnGap={4}> 259 | <GridColumn gridSpan={2}> 260 | <Cell>1</Cell> 261 | </GridColumn> 262 | <GridColumn> 263 | <Cell>2</Cell> 264 | </GridColumn> 265 | <GridColumn> 266 | <Cell>3</Cell> 267 | </GridColumn> 268 | <GridColumn> 269 | <Cell>4</Cell> 270 | </GridColumn> 271 | <GridColumn> 272 | <Cell>5</Cell> 273 | </GridColumn> 274 | <GridColumn> 275 | <Cell>6</Cell> 276 | </GridColumn> 277 | </FormGrid> 278 | <p>minWidth 150 +maxColumns 3</p> 279 | <FormGrid minWidth={150} maxColumns={3} columnGap={4}> 280 | <GridColumn gridSpan={2}> 281 | <Cell>1</Cell> 282 | </GridColumn> 283 | <GridColumn> 284 | <Cell>2</Cell> 285 | </GridColumn> 286 | <GridColumn> 287 | <Cell>3</Cell> 288 | </GridColumn> 289 | <GridColumn> 290 | <Cell>4</Cell> 291 | </GridColumn> 292 | <GridColumn> 293 | <Cell>5</Cell> 294 | </GridColumn> 295 | <GridColumn> 296 | <Cell>6</Cell> 297 | </GridColumn> 298 | </FormGrid> 299 | <p>maxWidth 120+minColumns 2</p> 300 | <FormGrid maxWidth={120} minColumns={2} columnGap={4}> 301 | <GridColumn gridSpan={2}> 302 | <Cell>1</Cell> 303 | </GridColumn> 304 | <GridColumn> 305 | <Cell>2</Cell> 306 | </GridColumn> 307 | <GridColumn> 308 | <Cell>3</Cell> 309 | </GridColumn> 310 | <GridColumn> 311 | <Cell>4</Cell> 312 | </GridColumn> 313 | <GridColumn> 314 | <Cell>5</Cell> 315 | </GridColumn> 316 | <GridColumn> 317 | <Cell>6</Cell> 318 | </GridColumn> 319 | </FormGrid> 320 | <p>maxWidth 120 + gridSpan -1</p> 321 | <FormGrid maxWidth={120} columnGap={4}> 322 | <GridColumn gridSpan={2}> 323 | <Cell>1</Cell> 324 | </GridColumn> 325 | <GridColumn> 326 | <Cell>2</Cell> 327 | </GridColumn> 328 | <GridColumn gridSpan={-1}> 329 | <Cell>3</Cell> 330 | </GridColumn> 331 | </FormGrid> 332 | </React.Fragment> 333 | ) 334 | } 335 | ``` 336 | 337 | ## Query Form case 338 | 339 | ```tsx 340 | import React, { useMemo, Fragment } from 'react' 341 | import { createForm } from '@formily/core' 342 | import { createSchemaField, FormProvider, observer } from '@formily/react' 343 | import { 344 | Form, 345 | Input, 346 | Select, 347 | DatePicker, 348 | FormItem, 349 | FormGrid, 350 | Submit, 351 | Reset, 352 | FormButtonGroup, 353 | } from '@formily/next' 354 | 355 | const useCollapseGrid = (maxRows: number) => { 356 | const grid = useMemo( 357 | () => 358 | FormGrid.createFormGrid({ 359 | maxColumns: 4, 360 | maxWidth: 240, 361 | maxRows: maxRows, 362 | shouldVisible: (node, grid) => { 363 | if (node.index === grid.childSize - 1) return true 364 | if (grid.maxRows === Infinity) return true 365 | return node.shadowRow < maxRows + 1 366 | }, 367 | }), 368 | [] 369 | ) 370 | const expanded = grid.maxRows === Infinity 371 | const realRows = grid.shadowRows 372 | const computeRows = grid.fullnessLastColumn 373 | ? grid.shadowRows - 1 374 | : grid.shadowRows 375 | 376 | const toggle = () => { 377 | if (grid.maxRows === Infinity) { 378 | grid.maxRows = maxRows 379 | } else { 380 | grid.maxRows = Infinity 381 | } 382 | } 383 | const takeType = () => { 384 | if (realRows < maxRows + 1) return 'incomplete-wrap' 385 | if (computeRows > maxRows) return 'collapsible' 386 | return 'complete-wrap' 387 | } 388 | return { 389 | grid, 390 | expanded, 391 | toggle, 392 | type: takeType(), 393 | } 394 | } 395 | 396 | const QueryForm: React.FC = observer((props) => { 397 | const { grid, expanded, toggle, type } = useCollapseGrid(1) 398 | 399 | const renderActions = () => { 400 | return ( 401 | <Fragment> 402 | <Submit onSubmit={console.log}>Query</Submit> 403 | <Reset>Reset</Reset> 404 | </Fragment> 405 | ) 406 | } 407 | 408 | const renderButtonGroup = () => { 409 | if (type === 'incomplete-wrap') { 410 | return ( 411 | <FormButtonGroup.FormItem> 412 | <FormButtonGroup>{renderActions()}</FormButtonGroup> 413 | </FormButtonGroup.FormItem> 414 | ) 415 | } 416 | if (type === 'collapsible') { 417 | return ( 418 | <Fragment> 419 | <FormButtonGroup> 420 | <a 421 | href="" 422 | onClick={(e) => { 423 | e.preventDefault() 424 | toggle() 425 | }} 426 | > 427 | {expanded ? 'Fold' : 'UnFold'} 428 | </a> 429 | </FormButtonGroup> 430 | <FormButtonGroup align="right">{renderActions()}</FormButtonGroup> 431 | </Fragment> 432 | ) 433 | } 434 | return ( 435 | <FormButtonGroup align="right" style={{ display: 'flex', width: '100%' }}> 436 | {renderActions()} 437 | </FormButtonGroup> 438 | ) 439 | } 440 | 441 | return ( 442 | <Form {...props} layout="vertical" feedbackLayout="terse"> 443 | <FormGrid grid={grid}> 444 | {props.children} 445 | <FormGrid.GridColumn 446 | gridSpan={-1} 447 | style={{ display: 'flex', justifyContent: 'space-between' }} 448 | > 449 | {renderButtonGroup()} 450 | </FormGrid.GridColumn> 451 | </FormGrid> 452 | </Form> 453 | ) 454 | }) 455 | 456 | const SchemaField = createSchemaField({ 457 | components: { 458 | QueryForm, 459 | Input, 460 | Select, 461 | DatePicker, 462 | FormItem, 463 | }, 464 | }) 465 | 466 | export default () => { 467 | const form = useMemo(() => createForm(), []) 468 | return ( 469 | <FormProvider form={form}> 470 | <SchemaField> 471 | <SchemaField.Object x-component="QueryForm"> 472 | <SchemaField.String 473 | name="input1" 474 | title="Input 1" 475 | x-component="Input" 476 | x-decorator="FormItem" 477 | /> 478 | <SchemaField.String 479 | name="input2" 480 | title="Input 2" 481 | x-component="Input" 482 | x-decorator="FormItem" 483 | /> 484 | 485 | <SchemaField.String 486 | name="select1" 487 | title="Select 1" 488 | x-component="Select" 489 | x-decorator="FormItem" 490 | /> 491 | <SchemaField.String 492 | name="select2" 493 | title="Select 2" 494 | x-component="Select" 495 | x-decorator="FormItem" 496 | /> 497 | <SchemaField.String 498 | name="date" 499 | title="DatePicker" 500 | x-component="DatePicker" 501 | x-decorator="FormItem" 502 | /> 503 | <SchemaField.String 504 | name="dateRange" 505 | title="DatePicker.RangePicker" 506 | x-component="DatePicker.RangePicker" 507 | x-decorator="FormItem" 508 | x-decorator-props={{ 509 | gridSpan: 2, 510 | }} 511 | /> 512 | <SchemaField.String 513 | name="select3" 514 | title="Select 3" 515 | x-component="Select" 516 | x-decorator="FormItem" 517 | /> 518 | </SchemaField.Object> 519 | </SchemaField> 520 | </FormProvider> 521 | ) 522 | } 523 | ``` 524 | 525 | ## API 526 | 527 | ### FormGrid 528 | 529 | | Property name | Type | Description | Default value | 530 | | ------------- | ---------------------- | --------------------------------------------------------------------------------- | ----------------- | 531 | | minWidth | `number \| number[]` | Minimum element width | 100 | 532 | | maxWidth | `number \| number[]` | Maximum element width | - | 533 | | minColumns | `number \| number[]` | Minimum number of columns | 0 | 534 | | maxColumns | `number \| number[]` | Maximum number of columns | - | 535 | | breakpoints | number[] | Container size breakpoints | `[720,1280,1920]` | 536 | | columnGap | number | Column spacing | 8 | 537 | | rowGap | number | Row spacing | 4 | 538 | | colWrap | boolean | Wrap | true | 539 | | strictAutoFit | boolean | Is width strictly limited by maxWidth | false | 540 | | shouldVisible | `(node,grid)=>boolean` | Whether to show the current node | `()=>true` | 541 | | grid | `Grid` | Grid instance passed in from outside, used to implement more complex layout logic | - | 542 | 543 | note: 544 | 545 | - minWidth takes priority over minColumn 546 | - maxWidth has priority over maxColumn 547 | - The array format of minWidth/maxWidth/minColumns/maxColumns represents the mapping with the breakpoint array 548 | 549 | ### FormGrid.GridColumn 550 | 551 | | Property name | Type | Description | Default value | 552 | | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- | 553 | | gridSpan | number | The number of columns spanned by the element, if it is -1, it will automatically fill the cell across columns in reverse | 1 | 554 | 555 | ### FormGrid.createFormGrid 556 | 557 | Read the Grid instance from the context 558 | 559 | ```ts 560 | interface createFormGrid { 561 | (props: IGridProps): Grid 562 | } 563 | ``` 564 | 565 | - IGridProps reference FormGrid properties 566 | - Grid instance attribute method reference https://github.com/alibaba/formily/tree/formily_next/packages/grid 567 | 568 | ### FormGrid.useFormGrid 569 | 570 | Read the Grid instance from the context 571 | 572 | ```ts 573 | interface useFormGrid { 574 | (): Grid 575 | } 576 | ``` 577 | 578 | - Grid instance attribute method reference https://github.com/alibaba/formily/tree/formily_next/packages/grid 579 | ```