This is page 19 of 35. Use http://codebase.md/alibaba/formily?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/core/src/types.ts: -------------------------------------------------------------------------------- ```typescript import { IValidatorRules, Validator, ValidatorTriggerType, } from '@formily/validator' import { FormPath } from '@formily/shared' import { Form, Field, LifeCycle, ArrayField, VoidField, ObjectField, Query, } from './models' export type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends (...args: any) => any ? never : K }[keyof T] export type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>> export type AnyFunction = (...args: any[]) => any export type JSXComponent = any export type LifeCycleHandler<T> = (payload: T, context: any) => void export type LifeCyclePayload<T> = ( params: { type: string payload: T }, context: any ) => void export enum LifeCycleTypes { /** * Form LifeCycle **/ ON_FORM_INIT = 'onFormInit', ON_FORM_MOUNT = 'onFormMount', ON_FORM_UNMOUNT = 'onFormUnmount', ON_FORM_INPUT_CHANGE = 'onFormInputChange', ON_FORM_VALUES_CHANGE = 'onFormValuesChange', ON_FORM_INITIAL_VALUES_CHANGE = 'onFormInitialValuesChange', ON_FORM_SUBMIT = 'onFormSubmit', ON_FORM_RESET = 'onFormReset', ON_FORM_SUBMIT_START = 'onFormSubmitStart', ON_FORM_SUBMITTING = 'onFormSubmitting', ON_FORM_SUBMIT_END = 'onFormSubmitEnd', ON_FORM_SUBMIT_VALIDATE_START = 'onFormSubmitValidateStart', ON_FORM_SUBMIT_VALIDATE_SUCCESS = 'onFormSubmitValidateSuccess', ON_FORM_SUBMIT_VALIDATE_FAILED = 'onFormSubmitValidateFailed', ON_FORM_SUBMIT_VALIDATE_END = 'onFormSubmitValidateEnd', ON_FORM_SUBMIT_SUCCESS = 'onFormSubmitSuccess', ON_FORM_SUBMIT_FAILED = 'onFormSubmitFailed', ON_FORM_VALIDATE_START = 'onFormValidateStart', ON_FORM_VALIDATING = 'onFormValidating', ON_FORM_VALIDATE_SUCCESS = 'onFormValidateSuccess', ON_FORM_VALIDATE_FAILED = 'onFormValidateFailed', ON_FORM_VALIDATE_END = 'onFormValidateEnd', ON_FORM_GRAPH_CHANGE = 'onFormGraphChange', ON_FORM_LOADING = 'onFormLoading', /** * Field LifeCycle **/ ON_FIELD_INIT = 'onFieldInit', ON_FIELD_INPUT_VALUE_CHANGE = 'onFieldInputValueChange', ON_FIELD_VALUE_CHANGE = 'onFieldValueChange', ON_FIELD_INITIAL_VALUE_CHANGE = 'onFieldInitialValueChange', ON_FIELD_SUBMIT = 'onFieldSubmit', ON_FIELD_SUBMIT_START = 'onFieldSubmitStart', ON_FIELD_SUBMITTING = 'onFieldSubmitting', ON_FIELD_SUBMIT_END = 'onFieldSubmitEnd', ON_FIELD_SUBMIT_VALIDATE_START = 'onFieldSubmitValidateStart', ON_FIELD_SUBMIT_VALIDATE_SUCCESS = 'onFieldSubmitValidateSuccess', ON_FIELD_SUBMIT_VALIDATE_FAILED = 'onFieldSubmitValidateFailed', ON_FIELD_SUBMIT_VALIDATE_END = 'onFieldSubmitValidateEnd', ON_FIELD_SUBMIT_SUCCESS = 'onFieldSubmitSuccess', ON_FIELD_SUBMIT_FAILED = 'onFieldSubmitFailed', ON_FIELD_VALIDATE_START = 'onFieldValidateStart', ON_FIELD_VALIDATING = 'onFieldValidating', ON_FIELD_VALIDATE_SUCCESS = 'onFieldValidateSuccess', ON_FIELD_VALIDATE_FAILED = 'onFieldValidateFailed', ON_FIELD_VALIDATE_END = 'onFieldValidateEnd', ON_FIELD_LOADING = 'onFieldLoading', ON_FIELD_RESET = 'onFieldReset', ON_FIELD_MOUNT = 'onFieldMount', ON_FIELD_UNMOUNT = 'onFieldUnmount', } export type HeartSubscriber = ({ type, payload, }: { type: string payload: any }) => void export interface INodePatch<T> { type: 'remove' | 'update' address: string oldAddress?: string payload?: T } export interface IHeartProps<Context> { lifecycles?: LifeCycle[] context?: Context } export interface IFieldFeedback { triggerType?: FieldFeedbackTriggerTypes type?: FieldFeedbackTypes code?: FieldFeedbackCodeTypes messages?: FeedbackMessage } export type IFormFeedback = IFieldFeedback & { path?: string address?: string } export interface ISearchFeedback { triggerType?: FieldFeedbackTriggerTypes type?: FieldFeedbackTypes code?: FieldFeedbackCodeTypes address?: FormPathPattern path?: FormPathPattern messages?: FeedbackMessage } export type FeedbackMessage = any[] export type IFieldUpdate = { pattern: FormPath callbacks: ((...args: any[]) => any)[] } export interface IFormRequests { validate?: number submit?: number loading?: number updates?: IFieldUpdate[] updateIndexes?: Record<string, number> } export type IFormFields = Record<string, GeneralField> export type FieldFeedbackTypes = 'error' | 'success' | 'warning' export type FieldFeedbackTriggerTypes = ValidatorTriggerType export type FieldFeedbackCodeTypes = | 'ValidateError' | 'ValidateSuccess' | 'ValidateWarning' | 'EffectError' | 'EffectSuccess' | 'EffectWarning' | (string & {}) export type FormPatternTypes = | 'editable' | 'readOnly' | 'disabled' | 'readPretty' | ({} & string) export type FormDisplayTypes = 'none' | 'hidden' | 'visible' | ({} & string) export type FormPathPattern = | string | number | Array<string | number> | FormPath | RegExp | (((address: Array<string | number>) => boolean) & { path: FormPath }) type OmitState<P> = Omit< P, | 'selfDisplay' | 'selfPattern' | 'originValues' | 'originInitialValues' | 'id' | 'address' | 'path' | 'lifecycles' | 'disposers' | 'requests' | 'fields' | 'graph' | 'heart' | 'indexes' | 'props' | 'displayName' | 'setState' | 'getState' | 'getFormGraph' | 'setFormGraph' | 'setFormState' | 'getFormState' > export type IFieldState = Partial< Pick< Field, NonFunctionPropertyNames<OmitState<Field<any, any, string, string>>> > > export type IVoidFieldState = Partial< Pick< VoidField, NonFunctionPropertyNames<OmitState<VoidField<any, any, string>>> > > export type IFormState<T extends Record<any, any> = any> = Pick< Form<T>, NonFunctionPropertyNames<OmitState<Form<{ [key: string]: any }>>> > export type IFormGraph = Record<string, IGeneralFieldState | IFormState> export interface IFormProps<T extends object = any> { values?: Partial<T> initialValues?: Partial<T> pattern?: FormPatternTypes display?: FormDisplayTypes hidden?: boolean visible?: boolean editable?: boolean disabled?: boolean readOnly?: boolean readPretty?: boolean effects?: (form: Form<T>) => void validateFirst?: boolean validatePattern?: FormPatternTypes[] validateDisplay?: FormDisplayTypes[] designable?: boolean } export type IFormMergeStrategy = | 'overwrite' | 'merge' | 'deepMerge' | 'shallowMerge' export interface IFieldFactoryProps< Decorator extends JSXComponent, Component extends JSXComponent, TextType = any, ValueType = any > extends IFieldProps<Decorator, Component, TextType, ValueType> { name: FormPathPattern basePath?: FormPathPattern } export interface IVoidFieldFactoryProps< Decorator extends JSXComponent, Component extends JSXComponent, TextType = any > extends IVoidFieldProps<Decorator, Component, TextType> { name: FormPathPattern basePath?: FormPathPattern } export interface IFieldRequests { validate?: number submit?: number loading?: number batch?: () => void } export interface IFieldCaches { value?: any initialValue?: any inputting?: boolean } export type FieldDisplayTypes = 'none' | 'hidden' | 'visible' | ({} & string) export type FieldPatternTypes = | 'editable' | 'readOnly' | 'disabled' | 'readPretty' | ({} & string) export type FieldValidatorContext = IValidatorRules & { field?: Field form?: Form value?: any } export type FieldValidator = Validator<FieldValidatorContext> export type FieldDataSource = { label?: any value?: any title?: any key?: any text?: any children?: FieldDataSource [key: string]: any }[] export type FieldComponent< Component extends JSXComponent, ComponentProps = any > = [Component] | [Component, ComponentProps] | boolean | any[] export type FieldDecorator< Decorator extends JSXComponent, ComponentProps = any > = [Decorator] | [Decorator, ComponentProps] | boolean | any[] export type FieldReaction = (field: Field) => void export interface IFieldProps< Decorator extends JSXComponent = any, Component extends JSXComponent = any, TextType = any, ValueType = any > { name: FormPathPattern basePath?: FormPathPattern title?: TextType description?: TextType value?: ValueType initialValue?: ValueType required?: boolean display?: FieldDisplayTypes pattern?: FieldPatternTypes hidden?: boolean visible?: boolean editable?: boolean disabled?: boolean readOnly?: boolean readPretty?: boolean dataSource?: FieldDataSource validateFirst?: boolean validatePattern?: FieldPatternTypes[] validateDisplay?: FieldDisplayTypes[] validator?: FieldValidator decorator?: FieldDecorator<Decorator> component?: FieldComponent<Component> reactions?: FieldReaction[] | FieldReaction content?: any data?: any } export interface IVoidFieldProps< Decorator extends JSXComponent = any, Component extends JSXComponent = any, TextType = any > { name: FormPathPattern basePath?: FormPathPattern title?: TextType description?: TextType display?: FieldDisplayTypes pattern?: FieldPatternTypes hidden?: boolean visible?: boolean editable?: boolean disabled?: boolean readOnly?: boolean readPretty?: boolean decorator?: FieldDecorator<Decorator> component?: FieldComponent<Component> reactions?: FieldReaction[] | FieldReaction content?: any data?: any } export interface IFieldResetOptions { forceClear?: boolean validate?: boolean } export type IGeneralFieldState = IFieldState & IVoidFieldState export type GeneralField = Field | VoidField | ArrayField | ObjectField export type DataField = Field | ArrayField | ObjectField export interface ISpliceArrayStateProps { startIndex?: number deleteCount?: number insertCount?: number } export interface IExchangeArrayStateProps { fromIndex?: number toIndex?: number } export interface IQueryProps { pattern: FormPathPattern base: FormPathPattern form: Form } export interface IModelSetter<P = any> { (setter: (state: P) => void): void (setter: Partial<P>): void (): void } export interface IModelGetter<P = any> { <Getter extends (state: P) => any>(getter: Getter): ReturnType<Getter> (): P } export type FieldMatchPattern = FormPathPattern | Query | GeneralField export interface IFieldStateSetter { (pattern: FieldMatchPattern, setter: (state: IFieldState) => void): void (pattern: FieldMatchPattern, setter: Partial<IFieldState>): void } export interface IFieldStateGetter { <Getter extends (state: IGeneralFieldState) => any>( pattern: FieldMatchPattern, getter: Getter ): ReturnType<Getter> (pattern: FieldMatchPattern): IGeneralFieldState } export interface IFieldActions { [key: string]: (...args: any[]) => any } ``` -------------------------------------------------------------------------------- /packages/core/docs/guide/form.md: -------------------------------------------------------------------------------- ```markdown # Form model Earlier I talked about the overall architecture of the Formily kernel, and also talked about MVVM. You should also be able to roughly understand what Formily's form model is. Let's take a deeper look at the specific domain logic of the form model, which is mainly biased. Concluding content, if you don't understand it the first time, you can go directly to the API documentation, and come back after reading it, you can deepen your understanding of formily. ## Combing The entire form model is very large and complicated. In fact, the core of the decomposition is the following sub-models: - Field management model - Field model - Data model - Linkage model - Path system Let's talk about how the form model is managed in detail below. ## Field Management Model The field management model mainly includes: - Field addition - Field query - Import field set - Export field set - Clear the field set #### Field addition The field is created mainly through the createField/createArrayField/createObjectField/createVoidField method. If the field already exists, it will not be created repeatedly #### Field query The query method is mainly used to query the field. The query method can pass in the path of the field or regular expression to match the field. Because the detailed rules of the field path are still more complicated, they will be explained in detail in the following [Path System](/api/entry/form-path) article. Then calling the query method will return a Query object. The Query object can have a forEach/map/reduce method that traverses all fields in batches, or a take method that takes only the first field that is queried, as well as direct reading of fields. The get method of properties, and the getIn method that can read field properties in depth, the difference between the two methods is that the former can have smart prompts, and the latter has no prompts, so it is recommended that users use the get method. #### Import field set The field set is imported mainly through setFormGraph. The input parameter format is a flat object format, the key is the absolute path of the field, and the value is the state of the field. Use this API to import the Immutable field state into the form in some scenarios that require time travel. In the model. #### Export field set The field set is mainly exported through getFormGraph. The export format is a flat object format, the key is the absolute path of the field, and the value is the state of the field, which is consistent with the imported field set input parameters. Because the returned data is an Immutable data, it is OK Completely persistent storage, convenient for time travel. #### Clear the field set The field set is cleared mainly through clearFormGraph. ## Field Model The field model mainly includes: - Field model, which is mainly responsible for managing the state of non-incremental fields, such as Input/Select/NumberPicker/DatePicker components - The ArrayField model is mainly responsible for managing the state of the auto-increment list field, and can add, delete, and move list items. - ObjectField model, which is mainly responsible for managing the state of auto-incremented object fields, and can add or delete the key of the object. - The VoidField model is mainly responsible for managing the state of the virtual field. The virtual field is a node that does not pollute the form data, but it can control the display and hiding of its child nodes, and the interactive mode. Because the field model is very complicated, it will be explained in detail in the following [Field Model](/guide/field) article. ## Data Model For the form data model, the previous version of Formily will more or less have some boundary problems. After reorganizing a version in 2.x, it really broke through the previous legacy problems. The data model mainly includes: - Form values (values) management - Form default value (initialValues) management - Field value (value) management - Field default value (initialValue) management - Value and default value selection merge strategy Form value management is actually the values attribute of an object structure, but it is an @formily/reactive observable attribute. At the same time, with the help of @formily/reactive's deep observer capability, it monitors any attribute changes, and if it changes, it will trigger The life cycle hook of onFormValuesChange. In the same way, the default value management is actually the initialValues property of an object structure. It will also deeply monitor property changes and trigger the onFormInitialValues life cycle hook. Field value management is reflected in the value attribute of each data type field. Formily will maintain a data path attribute called path for each field, and then read and write values are all read and write the values of the top-level form. This ensures that the value of the field and the value of the form are absolutely idempotent, and the default value of the field is the same. To sum up, the management of **values is all managed on the top-level form, and the value of the field and the value of the form are absolutely idempotent through path. ** <Alert> The difference between the value and the default value is actually whether the field will be reset to the default value state when the form is reset </Alert> #### Value and default value selection merge strategy Usually, in the process of business development, there is always a need for data echo. This data is generally used as an asynchronous default value. If it is used as a detail page, it is okay, but as an editing page, there will be some problems. : **There is a conflict** For example, the form value is `{xx:123}`, and the default form value is `{xx:321}`. The strategy here is: - If `xx` does not have a corresponding field model, it means it is just redundant data and cannot be modified by the user - If the form value is assigned first, and the default value is assigned later, then the default value directly overrides the form value. This scenario is suitable for asynchronous data echo scenarios. Different business states have different default data echoes, and the data is finally submitted.` {xx:321}` - If the default value is assigned first, and the form value is assigned later, the form value directly overrides the default value. This scenario is suitable for synchronizing the default value and finally submitting the data `{xx:123}` - If `xx` has a field model - If the form value is assigned first, the default value is assigned later - If the current field has been modified by the user (modified is true), then the default value cannot overwrite the form value, and finally submit the data `{xx:123}` - If the current field has not been modified by the user (modified is false), then the default value will directly override the field value. This scenario is suitable for asynchronous data echo scenarios. Different business states have different default data echoes, and the data is finally submitted `{xx:321}` - If the default value is assigned first, and the form value is assigned later, the form value directly overrides the default value. This scenario is suitable for synchronizing the default value and finally submitting the data `{xx:123}` **No conflicts** For example, the form value is `{xx:123}`, and the default form value is `{yy:321}`. The strategy here is to merge directly. To sum up, the selection and merging strategy of the value and the default value, the core is to see whether the field has been modified by the user, everything is subject to the user, if it has not been modified by the user, the order of assignment shall prevail.\*\* <Alert> The default value mentioned here can be assigned repeatedly, and it is also the question of whether to discard the value in the process of repeated assignment. </Alert> ## Validation model The core of the form verification model is to verify the validity of the data, and then manage the verification results, so the verification model mainly includes: - Validation rule management - Calibration result management Because the verification model belongs to the field model, it will be explained in detail in the following [Field Model](/guide/field#Verification Rules) ## Linkage model The core of the linkage model in formily1.x is the active linkage model, which is roughly expressed in one sentence: ```ts setFieldState(Subscribe(FormLifeCycle, Selector(Path)), TargetState) ``` The explanation is that any linkage is based on a certain life cycle hook of the form to trigger the state of the field under the specified path. Such a model can solve many problems, but it also has an obvious problem, which is the many-to-one linkage. In the scenario where you need to monitor changes in multiple fields at the same time to control the state of a field, the implementation cost is still relatively high for users, especially to achieve some calculator linkage requirements, and the amount of code increases sharply. Of course, for one-to-many scenarios, this model is the most efficient. Therefore, in formily 2.x, a passive linkage model is added to the active linkage model, which is also an expression: ```ts subscribe(Dependencies, Reactions) ``` Simplified a lot, the core is to respond to dependent data changes. The dependent data can be form model attributes or attributes of any field model. The response action can be to change the attributes of any field model or do other asynchronous actions. . Such a model is also a complete linkage model, but in a one-to-many scenario, the implementation cost will be higher than the active model. Therefore, the two linkage models require users to choose according to their own needs. ## Path system The path system is very important. The path system is used everywhere in almost the entire form model. It mainly provides the following capabilities for the form model: - It can be used to find any field from the field set, and it also supports batch search according to the rules - It can be used to express the model of the relationship between the fields. With the help of the path system, we can find the father of a certain field, can find the father, and can also realize the data inheritance ability of the tree level. Similarly, we can also find the data of a certain field. Adjacent node - It can be used to read and write field data, read and write data with deconstruction The entire path system is actually implemented based on the path DSL of @formily/path. If you want to know more about the path system, you can take a look at [FormPath API](/api/entry/form-path) in detail ``` -------------------------------------------------------------------------------- /packages/react/docs/guide/concept.md: -------------------------------------------------------------------------------- ```markdown # Core idea The architecture of @formily/react itself is not complicated, because it only provides a series of components and Hooks for users to use, but we still need to understand the following concepts: - Form context - Field context - Protocol context - Model binding - Protocol driven - Three development modes ## Form context From the [architecture diagram](/guide/architecture) we can see that FormProvider exists as a unified context for forms, and its position is very important. It is mainly used to create [Form](//core. formilyjs.org/api/models/form) instances are distributed to all sub-components, whether in built-in components or user-extended components, can be read through [useForm](/api/hooks/use-form) [ Form](//core.formilyjs.org/api/models/form) instance ## Field context From the [architecture diagram](/guide/architecture) we can see that whether it is Field/ArrayField/ObjectField/VoidField, a FieldContext will be issued to the subtree. We can read the current field model in the custom component, mainly Use [useField](/api/hooks/use-field) to read, which is very convenient for model mapping ## Protocol context From the [architecture diagram](/guide/architecture) we can see that [RecursionField](/api/components/recursion-field) will send a FieldSchemaContext to the subtree, and we can read the current field in the custom component The Schema description is mainly read using [useFieldSchema](/api/hooks/useFieldSchema). Note that this Hook can only be used in the [SchemaField](/api/components/SchemaField) and [RecursionField](/api/components/recursion-field) subtrees ## Model binding To understand model binding, you need to understand what [MVVM](//core.formilyjs.org/guide/mvvm) is. After understanding, let’s take a look at this picture:  In Formily, @formily/core is ViewModel, Component and Decorator are View, @formily/react is the glue layer that binds ViewModel and View, and the binding of ViewModel and View is called model binding, which implements model binding. The main methods are [useField](/api/hooks/use-field), and [connect](/api/shared/connect) and [mapProps](/api/shared/map-props) can also be used. Note that Component only needs to support the value/onChange property to automatically realize the two-way binding of the data layer. ## JSON Schema Driver Protocol-driven rendering is the most expensive part of @formily/react, but after learning it, the benefits it brings to the business are also very high. A total of 4 core concepts need to be understood: - Schema - Recursive rendering - Protocol binding - Three development modes ### Schema Formily’s protocol driver is mainly based on the standard JSON Schema to drive rendering. At the same time, we have extended some `x-*` attributes to express the UI on top of the standard, so that the entire protocol can fully describe a complex form. Schema protocol, refer to [Schema](/api/shared/schema) API document ### Recursive rendering What is recursive rendering? Recursive rendering means that component A will continue to use component A to render content under certain conditions. Take a look at the following pseudo code: ```json {<---- RecursionField (condition: object; rendering right: RecursionField) "type":"object", "properties":{ "username":{ <---- RecursionField (condition: string; rendering right: RecursionField) "type":"string", "x-component":"Input" }, "phone":{ <---- RecursionField (condition: string; rendering right: RecursionField) "type":"string", "x-component":"Input", "x-validator":"phone" }, "email":{ <---- RecursionField (condition: string; rendering right: RecursionField) "type":"string", "x-component":"Input", "x-validator":"email" }, "contacts":{ <---- RecursionField (condition: array; rendering right: RecursionField) "type":"array", "x-component":"ArrayTable", "items":{ <---- RecursionField (condition: object; rendering rights: ArrayTable component) "type":"object", "properties":{ "username":{ <---- RecursionField (condition: string; rendering right: RecursionField) "type":"string", "x-component":"Input" }, "phone":{ <---- RecursionField (condition: string; rendering right: RecursionField) "type":"string", "x-component":"Input", "x-validator":"phone" }, "email":{ <---- RecursionField (condition: string; rendering right: RecursionField) "type":"string", "x-component":"Input", "x-validator":"email" }, } } } } } ``` @formily/react The entry point for recursive rendering is [SchemaField](/api/components/schema-field), but it actually uses [RecursionField](/api/components/recursion-field) to render internally, because of JSON-Schema It is a recursive structure, so [RecursionField](/api/components/recursion-field) will be parsed from the top-level Schema node when rendering. If it is a non-object and array type, it will directly render the specific component. If it is an object, it will traverse. properties Continue to use [RecursionField](/api/components/recursion-field) to render child Schema nodes. A special case here is the rendering of the array type auto-increment list, which requires the user to use [RecursionField](/api/components/recursion-field) in the custom component for recursive rendering, because the UI of the auto-increment list is very customized High, so the recursive rendering rights are handed over to the user to render, so the design can also make protocol-driven rendering more flexible. What is the difference between SchemaField and RecursionField? There are two main points: - SchemaField supports Markup grammar, it will parse Markup grammar in advance to generate [JSON Schema](/api/shared/schema) and transfer it to RecursionField for rendering, so RecursionField can only be rendered based on [JSON Schema](/api/shared/schema) - SchemaField renders the overall Schema protocol, while RecursionField renders the partial Schema protocol ### Protocol binding I talked about model binding, and protocol binding is the process of converting Schema protocol into model binding, because JSON-Schema protocol is a JSON string and can be stored offline, while model binding is a binding between memory The relationship is at the Runtime layer. For example, `x-component` is the string identifier of the component in the Schema, but the component in the model requires component reference, so the JSON string and the Runtime layer need to be converted. Then we can continue to improve the above model binding diagram:  To sum up, in @formily/react, there are mainly two layers of binding relationships, Schema binding model, model binding component, the glue layer that realizes the binding is @formily/react, it should be noted that Schema binds the field model After that, the Schema is not perceptible in the field model. For example, if you want to modify the `enum`, you need to modify the `dataSource` attribute in the field model. In short, if you want to update the field model, refer to [Field](//core.formilyjs. org/api/models/field), you can refer to [Schema](/api/shared/schema) document if you want to understand the mapping relationship between Schema and field model ## Three development models From the [architecture diagram](/guide/architecture), we have actually seen that the entire @formily/react has three development modes, corresponding to different users: - JSX development model - JSON Schema development mode - Markup Schema development mode We can look at specific examples #### JSX development model This mode mainly uses Field/ArrayField/ObjectField/VoidField components ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' import { Input } from 'antd' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="input" component={[Input, { placeholder: 'Please enter' }]} /> </FormProvider> ) ``` #### JSON Schema Development Mode This mode is to pass JSON Schema to the schema attribute of SchemaField ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Input } from 'antd' const form = createForm() const SchemaField = createSchemaField({ components: { Input, }, }) export default () => ( <FormProvider form={form}> <SchemaField schema={{ type: 'object', properties: { input: { type: 'string', 'x-component': 'Input', 'x-component-props': { placeholder: 'Please enter', }, }, }, }} /> </FormProvider> ) ``` #### Markup Schema Development Mode This mode can be regarded as a Schema development mode that is more friendly to source code development, and it also uses the SchemaField component. Because it is difficult to get the best smart prompt experience in the JSX environment with JSON Schema, and it is inconvenient to maintain, the maintainability in the form of tags will be better, and the smart prompt is also very strong. Markup Schema mode mainly has the following characteristics: - Mainly rely on description tags such as SchemaField.String/SchemaField.Array/SchemaField.Object... to express Schema - Each description tag represents a Schema node, which is equivalent to JSON-Schema - SchemaField child nodes cannot insert UI elements at will, because SchemaField will only parse all the Schema description tags of the child nodes, and then convert them into JSON Schema, and finally give it to [RecursionField](/api/components/recursion-field) for rendering, if you want Insert UI elements, you can upload the `x-content` attribute in VoidDield to insert UI elements ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Input } from 'antd' const form = createForm() const SchemaField = createSchemaField({ components: { Input, }, }) export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String x-component="Input" x-component-props={{ placeholder: 'Please enter' }} /> <div>I will not be rendered</div> <SchemaField.Void x-content={<div>I will be rendered</div>} /> </SchemaField> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/element/src/array-collapse/index.ts: -------------------------------------------------------------------------------- ```typescript import { ArrayField } from '@formily/core' import { ISchema } from '@formily/json-schema' import { observer } from '@formily/reactive-vue' import { Fragment, h, RecursionField, useField, useFieldSchema, } from '@formily/vue' import type { Collapse as CollapseProps, CollapseItem as CollapseItemProps, } from 'element-ui' import { Badge, Card, Collapse, CollapseItem, Empty, Row } from 'element-ui' import { defineComponent, ref, Ref, watchEffect } from 'vue-demi' import { ArrayBase } from '../array-base' import { stylePrefix } from '../__builtins__/configs' import { composeExport } from '../__builtins__/shared' export interface IArrayCollapseProps extends CollapseProps { defaultOpenPanelCount?: number } const isAdditionComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Addition') > -1 } const isIndexComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Index') > -1 } const isRemoveComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Remove') > -1 } const isMoveUpComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveUp') > -1 } const isMoveDownComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveDown') > -1 } const isOperationComponent = (schema: ISchema) => { return ( isAdditionComponent(schema) || isRemoveComponent(schema) || isMoveDownComponent(schema) || isMoveUpComponent(schema) ) } const range = (count: number) => Array.from({ length: count }).map((_, i) => i) const takeDefaultActiveKeys = ( dataSourceLength: number, defaultOpenPanelCount: number, accordion = false ) => { if (accordion) { return 0 } if (dataSourceLength < defaultOpenPanelCount) return range(dataSourceLength) return range(defaultOpenPanelCount) } const insertActiveKeys = ( activeKeys: number[] | number, index: number, accordion = false ) => { if (accordion) return index if ((activeKeys as number[]).length <= index) return (activeKeys as number[]).concat(index) return (activeKeys as number[]).reduce((buf, key) => { if (key < index) return buf.concat(key) if (key === index) return buf.concat([key, key + 1]) return buf.concat(key + 1) }, []) } export const ArrayCollapseInner = observer( defineComponent<IArrayCollapseProps>({ name: 'FArrayCollapse', props: { defaultOpenPanelCount: { type: Number, default: 5, }, }, setup(props, { attrs }) { const fieldRef = useField<ArrayField>() const schemaRef = useFieldSchema() const prefixCls = `${stylePrefix}-array-collapse` const activeKeys: Ref<number[] | number> = ref([]) watchEffect(() => { const field = fieldRef.value const dataSource = Array.isArray(field.value) ? field.value.slice() : [] if (!field.modified && dataSource.length) { activeKeys.value = takeDefaultActiveKeys( dataSource.length, props.defaultOpenPanelCount, attrs.accordion as boolean ) } }) const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value) return () => { const field = fieldRef.value const schema = schemaRef.value const dataSource = Array.isArray(field.value) ? field.value.slice() : [] if (!schema) throw new Error('can not found schema object') const renderItems = () => { if (!dataSource.length) { return null } const items = dataSource?.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items const key = getKey(item, index) const panelProps = field .query(`${field.address}.${index}`) .get('componentProps') const props: CollapseItemProps = items['x-component-props'] const headerTitle = panelProps?.title || props.title || field.title const path = field.address.concat(index) const errors = field.form.queryFeedbacks({ type: 'error', address: `${path}.**`, }) const title = h( ArrayBase.Item, { props: { index, record: item, }, }, { default: () => [ h( RecursionField, { props: { schema: items, name: index, filterProperties: (schema) => { if (!isIndexComponent(schema)) return false return true }, onlyRenderProperties: true, }, }, {} ), errors.length ? h( Badge, { class: [`${prefixCls}-errors-badge`], props: { value: errors.length, }, }, { default: () => headerTitle } ) : headerTitle, ], } ) const extra = h( ArrayBase.Item, { props: { index, record: item, }, }, { default: () => [ h( RecursionField, { props: { schema: items, name: index, filterProperties: (schema) => { if (!isOperationComponent(schema)) return false return true }, onlyRenderProperties: true, }, }, {} ), ], } ) const content = h( RecursionField, { props: { schema: items, name: index, filterProperties: (schema) => { if (isIndexComponent(schema)) return false if (isOperationComponent(schema)) return false return true }, }, }, {} ) return h( CollapseItem, { attrs: { ...props, ...panelProps, name: index, }, key, }, { default: () => [ h( ArrayBase.Item, { props: { index, record: item, }, }, { default: () => [content], } ), ], title: () => h( Row, { style: { flex: 1 }, props: { type: 'flex', justify: 'space-between', }, }, { default: () => [ h('span', {}, { default: () => title }), h('span', {}, { default: () => extra }), ], } ), } ) }) return h( Collapse, { class: [`${prefixCls}-item`], attrs: { ...attrs, value: activeKeys.value, }, on: { change: (keys: number[] | number) => { activeKeys.value = keys }, }, }, { default: () => [items], } ) } const renderAddition = () => { return schema.reduceProperties((addition, schema) => { if (isAdditionComponent(schema)) { return h( RecursionField, { props: { schema, name: 'addition', }, }, {} ) } return addition }, null) } const renderEmpty = () => { if (dataSource?.length) return return h( Card, { class: [`${prefixCls}-item`], attrs: { shadow: 'never', ...attrs, header: attrs.title || field.title, }, }, { default: () => h( Empty, { props: { description: 'No Data', imageSize: 100 } }, {} ), } ) } return h( 'div', { class: [prefixCls], }, { default: () => h( ArrayBase, { props: { keyMap, }, on: { add: (index: number) => { activeKeys.value = insertActiveKeys( activeKeys.value, index, attrs.accordion as boolean ) }, }, }, { default: () => [ renderEmpty(), renderItems(), renderAddition(), ], } ), } ) } }, }) ) export const ArrayCollapseItem = defineComponent<CollapseItemProps>({ name: 'FArrayCollapseItem', setup(_props, { slots }) { return () => h(Fragment, {}, slots) }, }) export const ArrayCollapse = composeExport(ArrayCollapseInner, { Item: ArrayCollapseItem, Index: ArrayBase.Index, SortHandle: ArrayBase.SortHandle, Addition: ArrayBase.Addition, Remove: ArrayBase.Remove, MoveDown: ArrayBase.MoveDown, MoveUp: ArrayBase.MoveUp, useArray: ArrayBase.useArray, useIndex: ArrayBase.useIndex, useRecord: ArrayBase.useRecord, }) export default ArrayCollapse ``` -------------------------------------------------------------------------------- /packages/next/src/select-table/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useState, useMemo } from 'react' import { observer, useFieldSchema, useField, Schema, RecursionField, } from '@formily/react' import cls from 'classnames' import { GeneralField, FieldDisplayTypes } from '@formily/core' import { isArr, isBool, isFn } from '@formily/shared' import { Search, Table } from '@alifd/next' import { TableProps, ColumnProps } from '@alifd/next/types/table' import { SearchProps } from '@alifd/next/types/search' import { useFilterOptions } from './useFilterOptions' import { useFlatOptions } from './useFlatOptions' import { useSize } from './useSize' import { useTitleAddon } from './useTitleAddon' import { useCheckSlackly, getIndeterminate } from './useCheckSlackly' import { getUISelected, getOutputData } from './utils' import { usePrefixCls } from '../__builtins__' interface ObservableColumnSource { field: GeneralField columnProps: ColumnProps schema: Schema display: FieldDisplayTypes name: string } type IFilterOption = boolean | ((option: any, keyword: string) => boolean) type IFilterSort = (optionA: any, optionB: any) => number export interface ISelectTableColumnProps extends ColumnProps { key: React.ReactText } export interface ISelectTableProps extends Omit<TableProps, 'primaryKey' | 'onChange'> { mode?: 'multiple' | 'single' dataSource?: any[] optionAsValue?: boolean valueType?: 'all' | 'parent' | 'child' | 'path' showSearch?: boolean searchProps?: SearchProps primaryKey?: string | ((record: any) => string) filterOption?: IFilterOption filterSort?: IFilterSort onSearch?: (keyword: string) => void onChange?: (value: any, options: any) => void value?: any rowSelection?: TableProps['rowSelection'] & { checkStrictly?: boolean } } type ComposedSelectTable = React.FC< React.PropsWithChildren<ISelectTableProps> > & { Column?: React.FC<React.PropsWithChildren<ISelectTableColumnProps>> } const isColumnComponent = (schema: Schema) => { return schema['x-component']?.indexOf('Column') > -1 } const useSources = () => { const arrayField = useField() const schema = useFieldSchema() const parseSources = (schema: Schema): ObservableColumnSource[] => { if (isColumnComponent(schema)) { if (!schema['x-component-props']?.['dataIndex'] && !schema['name']) return [] const name = schema['x-component-props']?.['dataIndex'] || schema['name'] const field = arrayField.query(arrayField.address.concat(name)).take() const columnProps = field?.component?.[1] || schema['x-component-props'] || {} const display = field?.display || schema['x-display'] return [ { name, display, field, schema, columnProps: { title: field?.title || columnProps.title, ...columnProps, }, }, ] } else if (schema.properties) { return schema.reduceProperties((buf, schema) => { return buf.concat(parseSources(schema)) }, []) } } const parseArrayItems = (schema: Schema['items']) => { if (!schema) return [] const sources: ObservableColumnSource[] = [] const items = isArr(schema) ? schema : [schema] return items.reduce((columns, schema) => { const item = parseSources(schema) if (item) { return columns.concat(item) } return columns }, sources) } const validSchema = ( schema?.type === 'array' && schema?.items ? schema.items : schema ) as Schema return parseArrayItems(validSchema) } const useColumns = ( sources: ObservableColumnSource[] ): TableProps['columns'] => { return sources.reduce((buf, { name, columnProps, schema, display }, key) => { if (display !== 'visible') return buf if (!isColumnComponent(schema)) return buf return buf.concat({ ...columnProps, key, dataIndex: name, }) }, []) } const addPrimaryKey = (dataSource, rowKey, primaryKey) => dataSource.map((item) => { const children = isArr(item.children) ? addPrimaryKey(item.children, rowKey, primaryKey) : {} return { ...item, ...children, [primaryKey]: rowKey(item), } }) export const SelectTable: ComposedSelectTable = observer((props) => { const { mode = 'multiple', dataSource: propsDataSource, optionAsValue, valueType = 'all', showSearch = false, filterOption, filterSort, onSearch, searchProps, className, value, onChange, rowSelection, primaryKey: rowKey = 'key', ...otherTableProps } = props const prefixCls = usePrefixCls('formily-select-table', props) const [searchValue, setSearchValue] = useState<string>() const field = useField() as any const loading = isBool(props.loading) ? props.loading : field.loading const disabled = field.disabled const readOnly = field.readOnly const readPretty = field.readPretty const { searchSize, tableSize } = useSize( field.decoratorProps?.size, searchProps?.size, props?.size ) const primaryKey = isFn(rowKey) ? '__formily_key__' : rowKey const sources = useSources() const columns = useColumns(sources) // dataSource let dataSource = isArr(propsDataSource) ? propsDataSource : field.dataSource dataSource = isFn(rowKey) ? addPrimaryKey(dataSource, rowKey, primaryKey) : dataSource // Filter dataSource By Search const filteredDataSource = useFilterOptions( dataSource, searchValue, filterOption, rowSelection?.checkStrictly ) // Order dataSource By filterSort const orderedFilteredDataSource = useMemo(() => { if (!filterSort) { return filteredDataSource } return [...filteredDataSource].sort((a, b) => filterSort(a, b)) }, [filteredDataSource, filterSort]) const flatDataSource = useFlatOptions(dataSource) const flatFilteredDataSource = useFlatOptions(filteredDataSource) // 分页或异步查询时,dataSource会丢失已选数据,配置optionAsValue则无法获取已选数据,需要进行合并 const getWholeDataSource = () => { if (optionAsValue && mode === 'multiple' && value?.length) { const map = new Map() const arr = [...flatDataSource, ...value] arr.forEach((item) => { if (!map.has(item[primaryKey])) { map.set(item[primaryKey], item) } }) return [...map.values()] } return flatDataSource } // selected keys for Table UI const selected = getUISelected( value, flatDataSource, primaryKey, valueType, optionAsValue, mode, rowSelection?.checkStrictly, rowKey ) // readPretty Value const readPrettyDataSource = useFilterOptions( orderedFilteredDataSource, selected, (value, item) => value.includes(item[primaryKey]) ) const onInnerSearch = (searchText) => { const formatted = (searchText || '').trim() setSearchValue(searchText) onSearch?.(formatted) } const onInnerChange = (selectedRowKeys: any[]) => { if (readOnly) { return } // 筛选后onChange默认的records数据不完整,此处需使用完整数据过滤 const wholeRecords = getWholeDataSource().filter((item) => selectedRowKeys.includes(item?.[primaryKey]) ) const { outputValue, outputOptions } = getOutputData( selectedRowKeys, wholeRecords, dataSource, primaryKey, valueType, optionAsValue, mode, rowSelection?.checkStrictly ) onChange?.(outputValue, outputOptions) } const onRowClick = (record) => { if (readPretty || disabled || readOnly || record?.disabled) { return } const selectedRowKey = record?.[primaryKey] const isSelected = selected?.includes(selectedRowKey) let selectedRowKeys = [] if (mode === 'single') { selectedRowKeys = [selectedRowKey] } else { if (isSelected) { selectedRowKeys = selected.filter((item) => item !== selectedRowKey) } else { selectedRowKeys = [...selected, selectedRowKey] } } if (rowSelection?.checkStrictly !== false) { onInnerChange(selectedRowKeys) } else { onSlacklyChange(selectedRowKeys) } } // TreeData SlacklyChange const onSlacklyChange = (currentSelected: any[]) => { let { selectedRowKeys } = useCheckSlackly( currentSelected, selected, flatDataSource, flatFilteredDataSource, primaryKey, rowSelection?.checkStrictly ) onInnerChange(selectedRowKeys) } // Table All Checkbox const titleAddon = useTitleAddon( selected, flatDataSource, flatFilteredDataSource, primaryKey, mode, disabled, readOnly, rowSelection?.checkStrictly, onInnerChange ) return ( <div className={prefixCls}> {showSearch ? ( <Search {...searchProps} className={cls(`${prefixCls}-search`, searchProps?.className)} style={{ width: '100%', ...searchProps?.style }} onSearch={onInnerSearch} onChange={onInnerSearch} disabled={disabled} readOnly={readOnly} size={searchSize} buttonProps={{ ...searchProps?.buttonProps, loading }} // fusion /> ) : null} <Table {...otherTableProps} className={cls(`${prefixCls}-table`, className)} dataSource={ readPretty ? readPrettyDataSource : orderedFilteredDataSource } rowSelection={ readPretty ? undefined : { ...rowSelection, ...titleAddon, getProps: (record, index) => ({ ...(rowSelection?.getProps?.(record, index) as any), ...(rowSelection?.checkStrictly !== false ? {} : { indeterminate: getIndeterminate( record, flatDataSource, selected, primaryKey ), }), // 父子关联模式indeterminate值 disabled: disabled || record?.disabled, }), // fusion selectedRowKeys: selected, onChange: rowSelection?.checkStrictly !== false ? onInnerChange : onSlacklyChange, mode, } } columns={props.columns || columns} primaryKey={primaryKey} loading={loading} size={tableSize} onRowClick={(record, index, e) => { // fusion onRowClick(record) props.onRowClick?.(record, index, e) }} > {''} </Table> {sources.map((column, key) => { //专门用来承接对Column的状态管理 if (!isColumnComponent(column.schema)) return return React.createElement(RecursionField, { name: column.name, schema: column.schema, onlyRenderSelf: true, key, }) })} </div> ) }) const TableColumn: React.FC< React.PropsWithChildren<ISelectTableColumnProps> > = () => <></> SelectTable.Column = TableColumn export default SelectTable ``` -------------------------------------------------------------------------------- /packages/element/src/array-base/index.ts: -------------------------------------------------------------------------------- ```typescript import { ArrayField } from '@formily/core' import { clone, isValid, uid } from '@formily/shared' import { ExpressionScope, Fragment, h, useField, useFieldSchema, } from '@formily/vue' import { defineComponent, inject, InjectionKey, onBeforeUnmount, PropType, provide, Ref, ref, toRefs, } from 'vue-demi' import { stylePrefix } from '../__builtins__/configs' import type { Schema } from '@formily/json-schema' import type { Button as ButtonProps } from 'element-ui' import { Button } from 'element-ui' import { HandleDirective } from 'vue-slicksort' import { composeExport } from '../__builtins__/shared' export interface IArrayBaseAdditionProps extends ButtonProps { title?: string method?: 'push' | 'unshift' defaultValue?: any } export type ArrayBaseMixins = { Addition?: typeof ArrayBaseAddition Remove?: typeof ArrayBaseRemove MoveUp?: typeof ArrayBaseMoveUp MoveDown?: typeof ArrayBaseMoveDown SortHandle?: typeof ArrayBaseSortHandle Index?: typeof ArrayBaseIndex useArray?: typeof useArray useIndex?: typeof useIndex useRecord?: typeof useRecord } export interface IArrayBaseProps { disabled?: boolean keyMap?: WeakMap<Object, String> | String[] | null } export interface IArrayBaseItemProps { index: number record: any } export interface IArrayBaseContext { field: Ref<ArrayField> schema: Ref<Schema> props: IArrayBaseProps listeners: { [key in string]?: Function } keyMap?: WeakMap<Object, String> | String[] | null } const ArrayBaseSymbol: InjectionKey<IArrayBaseContext> = Symbol('ArrayBaseContext') const ItemSymbol: InjectionKey<IArrayBaseItemProps> = Symbol('ItemContext') const useArray = () => { return inject(ArrayBaseSymbol, null) } const useIndex = (index?: number) => { const { index: indexRef } = toRefs(inject(ItemSymbol)) return indexRef ?? ref(index) } const useRecord = (record?: number) => { const { record: recordRef } = toRefs(inject(ItemSymbol)) return recordRef ?? ref(record) } const isObjectValue = (schema: Schema) => { if (Array.isArray(schema?.items)) return isObjectValue(schema.items[0]) if (schema?.items?.type === 'array' || schema?.items?.type === 'object') { return true } return false } const useKey = (schema: Schema) => { const isObject = isObjectValue(schema) let keyMap: WeakMap<Object, String> | String[] | null = null if (isObject) { keyMap = new WeakMap() } else { keyMap = [] } onBeforeUnmount(() => { keyMap = null }) return { keyMap, getKey: (record: any, index?: number) => { if (keyMap instanceof WeakMap) { if (!keyMap.has(record)) { keyMap.set(record, uid()) } return `${keyMap.get(record)}-${index}` } if (!keyMap[index]) { keyMap[index] = uid() } return `${keyMap[index]}-${index}` }, } } const getDefaultValue = (defaultValue: any, schema: Schema): any => { if (isValid(defaultValue)) return clone(defaultValue) if (Array.isArray(schema?.items)) return getDefaultValue(defaultValue, schema.items[0]) if (schema?.items?.type === 'array') return [] if (schema?.items?.type === 'boolean') return true if (schema?.items?.type === 'date') return '' if (schema?.items?.type === 'datetime') return '' if (schema?.items?.type === 'number') return 0 if (schema?.items?.type === 'object') return {} if (schema?.items?.type === 'string') return '' return null } const ArrayBaseInner = defineComponent<IArrayBaseProps>({ name: 'ArrayBase', props: { disabled: { type: Boolean, default: false, }, keyMap: { type: [WeakMap, Array] as PropType<WeakMap<Object, String> | String[]>, }, }, setup(props, { slots, listeners }) { const field = useField<ArrayField>() const schema = useFieldSchema() provide(ArrayBaseSymbol, { field, schema, props, listeners, keyMap: props.keyMap, }) return () => { return h(Fragment, {}, slots) } }, }) const ArrayBaseItem = defineComponent({ name: 'ArrayBaseItem', props: ['index', 'record'], setup(props: IArrayBaseItemProps, { slots }) { provide(ItemSymbol, props) return () => { return h( ExpressionScope, { props: { value: { $record: props.record, $index: props.index } } }, { default: () => h(Fragment, {}, slots), } ) } }, }) const ArrayBaseSortHandle = defineComponent({ name: 'ArrayBaseSortHandle', props: ['index'], directives: { handle: HandleDirective, }, setup(props, { attrs }) { const array = useArray() const prefixCls = `${stylePrefix}-array-base` return () => { if (!array) return null if (array.field.value?.pattern !== 'editable') return null return h( Button, { directives: [{ name: 'handle' }], class: [`${prefixCls}-sort-handle`], attrs: { size: 'mini', type: 'text', icon: 'el-icon-rank', ...attrs, }, }, {} ) } }, }) const ArrayBaseIndex = defineComponent({ name: 'ArrayBaseIndex', setup(props, { attrs }) { const index = useIndex() const prefixCls = `${stylePrefix}-array-base` return () => { return h( 'span', { class: `${prefixCls}-index`, attrs, }, { default: () => [`#${index.value + 1}.`], } ) } }, }) const ArrayBaseAddition = defineComponent({ name: 'ArrayBaseAddition', props: ['title', 'method', 'defaultValue'], setup(props: IArrayBaseAdditionProps, { listeners }) { const self = useField() const array = useArray() const prefixCls = `${stylePrefix}-array-base` return () => { if (!array) return null if (array?.field.value.pattern !== 'editable') return null return h( Button, { class: `${prefixCls}-addition`, attrs: { type: 'ghost', icon: 'qax-icon-Alone-Plus', ...props, }, on: { ...listeners, click: (e) => { if (array.props?.disabled) return const defaultValue = getDefaultValue( props.defaultValue, array?.schema.value ) if (props.method === 'unshift') { array?.field?.value.unshift(defaultValue) array.listeners?.add?.(0) } else { array?.field?.value.push(defaultValue) array.listeners?.add?.(array?.field?.value?.value?.length - 1) } if (listeners.click) { listeners.click(e) } }, }, }, { default: () => [self.value.title || props.title], } ) } }, }) const ArrayBaseRemove = defineComponent< ButtonProps & { title?: string; index?: number } >({ name: 'ArrayBaseRemove', props: ['title', 'index'], setup(props, { attrs, listeners }) { const indexRef = useIndex(props.index) const base = useArray() const prefixCls = `${stylePrefix}-array-base` return () => { if (base?.field.value.pattern !== 'editable') return null return h( Button, { class: `${prefixCls}-remove`, attrs: { type: 'text', size: 'mini', icon: 'el-icon-delete', ...attrs, }, on: { ...listeners, click: (e: MouseEvent) => { e.stopPropagation() if (Array.isArray(base?.keyMap)) { base?.keyMap?.splice(indexRef.value, 1) } base?.field.value.remove(indexRef.value as number) base?.listeners?.remove?.(indexRef.value as number) if (listeners.click) { listeners.click(e) } }, }, }, { default: () => [props.title], } ) } }, }) const ArrayBaseMoveDown = defineComponent< ButtonProps & { title?: string; index?: number } >({ name: 'ArrayBaseMoveDown', props: ['title', 'index'], setup(props, { attrs, listeners }) { const indexRef = useIndex(props.index) const base = useArray() const prefixCls = `${stylePrefix}-array-base` return () => { if (base?.field.value.pattern !== 'editable') return null return h( Button, { class: `${prefixCls}-move-down`, attrs: { size: 'mini', type: 'text', icon: 'el-icon-arrow-down', ...attrs, }, on: { ...listeners, click: (e: MouseEvent) => { e.stopPropagation() if (Array.isArray(base?.keyMap)) { base.keyMap.splice( indexRef.value + 1, 0, base.keyMap.splice(indexRef.value, 1)[0] ) } base?.field.value.moveDown(indexRef.value as number) base?.listeners?.moveDown?.(indexRef.value as number) if (listeners.click) { listeners.click(e) } }, }, }, { default: () => [props.title], } ) } }, }) const ArrayBaseMoveUp = defineComponent< ButtonProps & { title?: string; index?: number } >({ name: 'ArrayBaseMoveUp', props: ['title', 'index'], setup(props, { attrs, listeners }) { const indexRef = useIndex(props.index) const base = useArray() const prefixCls = `${stylePrefix}-array-base` return () => { if (base?.field.value.pattern !== 'editable') return null return h( Button, { class: `${prefixCls}-move-up`, attrs: { size: 'mini', type: 'text', icon: 'el-icon-arrow-up', ...attrs, }, on: { ...listeners, click: (e: MouseEvent) => { e.stopPropagation() if (Array.isArray(base?.keyMap)) { base.keyMap.splice( indexRef.value - 1, 0, base.keyMap.splice(indexRef.value, 1)[0] ) } base?.field.value.moveUp(indexRef.value as number) base?.listeners?.moveUp?.(indexRef.value as number) if (listeners.click) { listeners.click(e) } }, }, }, { default: () => [props.title], } ) } }, }) export const ArrayBase = composeExport(ArrayBaseInner, { Index: ArrayBaseIndex, Item: ArrayBaseItem, SortHandle: ArrayBaseSortHandle, Addition: ArrayBaseAddition, Remove: ArrayBaseRemove, MoveDown: ArrayBaseMoveDown, MoveUp: ArrayBaseMoveUp, useArray: useArray, useIndex: useIndex, useKey: useKey, useRecord: useRecord, }) export default ArrayBase ``` -------------------------------------------------------------------------------- /packages/path/src/parser.ts: -------------------------------------------------------------------------------- ```typescript import { Tokenizer } from './tokenizer' import { Token, nameTok, colonTok, dotTok, starTok, bangTok, bracketLTok, bracketRTok, braceLTok, braceRTok, bracketDLTok, parenLTok, parenRTok, commaTok, expandTok, eofTok, dbStarTok, } from './tokens' import { bracketArrayContext, destructorContext } from './contexts' import { IdentifierNode, ExpandOperatorNode, WildcardOperatorNode, RangeExpressionNode, GroupExpressionNode, DotOperatorNode, IgnoreExpressionNode, DestructorExpressionNode, ObjectPatternNode, ObjectPatternPropertyNode, ArrayPatternNode, Node, Segments, } from './types' import { parseDestructorRules, setDestructor } from './destructor' import { isNumberLike } from './shared' import { Path } from './index' const createTreeBySegments = (segments: Segments = [], afterNode?: Node) => { const segLen = segments.length const build = (start = 0) => { const after = start < segLen - 1 ? build(start + 1) : afterNode const dot = after && { type: 'DotOperator', after, } return { type: 'Identifier', value: segments[start], after: dot, } } return build() } const calculate = ( a: string | number, b: string | number, operator: string ) => { if (isNumberLike(a) && isNumberLike(b)) { if (operator === '+') return String(Number(a) + Number(b)) if (operator === '-') return String(Number(a) - Number(b)) if (operator === '*') return String(Number(a) * Number(b)) if (operator === '/') return String(Number(a) / Number(b)) } else { if (operator === '+') return String(a) + String(b) if (operator === '-') return 'NaN' if (operator === '*') return 'NaN' if (operator === '/') return 'NaN' } return String(Number(b)) } export class Parser extends Tokenizer { public isMatchPattern = false public isWildMatchPattern = false public haveExcludePattern = false public haveRelativePattern = false public base: Path public relative: string | number public data: { segments: Segments tree?: Node } constructor(input: string, base?: Path) { super(input) this.base = base } parse() { let node: Node this.data = { segments: [], } if (!this.eat(eofTok)) { this.next() node = this.parseAtom(this.state.type) } this.data.tree = node return node } append(parent: Node, node: Node) { if (parent && node) { parent.after = node } } parseAtom(type: Token): Node { switch (type) { case braceLTok: case bracketLTok: if (this.includesContext(destructorContext)) { if (type === braceLTok) { return this.parseObjectPattern() } else { return this.parseArrayPattern() } } return this.parseDestructorExpression() case nameTok: return this.parseIdentifier() case expandTok: return this.parseExpandOperator() case dbStarTok: case starTok: return this.parseWildcardOperator() case bracketDLTok: return this.parseIgnoreExpression() case dotTok: return this.parseDotOperator() } } pushSegments(key: string | number) { this.data.segments.push(key) } parseIdentifier() { const node: IdentifierNode = { type: 'Identifier', value: this.state.value, } const hasNotInDestructor = !this.includesContext(destructorContext) && !this.isMatchPattern && !this.isWildMatchPattern this.next() if (this.includesContext(bracketArrayContext)) { if (this.state.type !== bracketRTok) { throw this.unexpect() } else { this.state.context.pop() this.next() } } else if (hasNotInDestructor) { this.pushSegments(node.value) } if (this.state.type === bracketLTok) { this.next() if (this.state.type !== nameTok) { throw this.unexpect() } this.state.context.push(bracketArrayContext) let isNumberKey = false if (/^\d+$/.test(this.state.value)) { isNumberKey = true } const value = this.state.value this.pushSegments(isNumberKey ? Number(value) : value) const after = this.parseAtom(this.state.type) as IdentifierNode if (isNumberKey) { after.arrayIndex = true } this.append(node, after) } else { this.append(node, this.parseAtom(this.state.type)) } return node } parseExpandOperator() { const node: ExpandOperatorNode = { type: 'ExpandOperator', } this.isMatchPattern = true this.isWildMatchPattern = true this.data.segments = [] this.next() this.append(node, this.parseAtom(this.state.type)) return node } parseWildcardOperator(): WildcardOperatorNode { const node: WildcardOperatorNode = { type: 'WildcardOperator', } if (this.state.type === dbStarTok) { node.optional = true } this.isMatchPattern = true this.isWildMatchPattern = true this.data.segments = [] this.next() if (this.state.type === parenLTok) { node.filter = this.parseGroupExpression(node) } else if (this.state.type === bracketLTok) { node.filter = this.parseRangeExpression(node) } this.append(node, this.parseAtom(this.state.type)) return node } parseDestructorExpression(): DestructorExpressionNode { const node: DestructorExpressionNode = { type: 'DestructorExpression', } this.state.context.push(destructorContext) const startPos = this.state.pos - 1 node.value = this.state.type === braceLTok ? this.parseObjectPattern() : this.parseArrayPattern() const endPos = this.state.pos this.state.context.pop() node.source = this.input .substring(startPos, endPos) .replace( /\[\s*([\+\-\*\/])?\s*([^,\]\s]*)\s*\]/, (match, operator, target) => { if (this.relative !== undefined) { if (operator) { if (target) { return calculate(this.relative, target, operator) } else { return calculate(this.relative, 1, operator) } } else { if (target) { return calculate(this.relative, target, '+') } else { return String(this.relative) } } } return match } ) .replace(/\s*\.\s*/g, '') .replace(/\s*/g, '') if (this.relative === undefined) { setDestructor(node.source, parseDestructorRules(node)) } this.relative = undefined this.pushSegments(node.source) this.next() this.append(node, this.parseAtom(this.state.type)) return node } parseArrayPattern(): ArrayPatternNode { const node: ArrayPatternNode = { type: 'ArrayPattern', elements: [], } this.next() node.elements = this.parseArrayPatternElements() return node } parseArrayPatternElements() { const nodes = [] while (this.state.type !== bracketRTok && this.state.type !== eofTok) { nodes.push(this.parseAtom(this.state.type)) if (this.state.type === bracketRTok) { if (this.includesContext(destructorContext)) { this.next() } return nodes } this.next() } return nodes } parseObjectPattern(): ObjectPatternNode { const node: ObjectPatternNode = { type: 'ObjectPattern', properties: [], } this.next() node.properties = this.parseObjectProperties() return node } parseObjectProperties(): ObjectPatternPropertyNode[] { const nodes = [] while (this.state.type !== braceRTok && this.state.type !== eofTok) { const node: ObjectPatternPropertyNode = { type: 'ObjectPatternProperty', key: this.parseAtom(this.state.type) as IdentifierNode, } nodes.push(node) if (this.state.type === colonTok) { this.next() node.value = this.parseAtom(this.state.type) as | IdentifierNode | ObjectPatternNode[] | ArrayPatternNode[] } if (this.state.type === braceRTok) { if (this.includesContext(destructorContext)) { this.next() } return nodes } this.next() } return nodes } parseDotOperator(): Node { const node: DotOperatorNode = { type: 'DotOperator', } const prevToken = this.type_ if (!prevToken && this.base) { if (this.base.isMatchPattern) { throw new Error('Base path must be an absolute path.') } this.data.segments = this.base.toArr() while (this.state.type === dotTok) { this.relative = this.data.segments.pop() this.haveRelativePattern = true this.next() } return createTreeBySegments( this.data.segments.slice(), this.parseAtom(this.state.type) ) } else { this.next() } this.append(node, this.parseAtom(this.state.type)) return node } parseIgnoreExpression() { this.next() const value = String(this.state.value).replace(/\s*/g, '') const node: IgnoreExpressionNode = { type: 'IgnoreExpression', value: value, } this.pushSegments(value) this.next() this.append(node, this.parseAtom(this.state.type)) this.next() return node } parseGroupExpression(parent: Node) { const node: GroupExpressionNode = { type: 'GroupExpression', value: [], } this.isMatchPattern = true this.data.segments = [] this.next() loop: while (true) { switch (this.state.type) { case commaTok: this.next() break case bangTok: node.isExclude = true this.haveExcludePattern = true this.next() break case eofTok: break loop case parenRTok: break loop default: node.value.push(this.parseAtom(this.state.type)) } } this.next() this.append(parent, this.parseAtom(this.state.type)) return node } parseRangeExpression(parent: Node) { const node: RangeExpressionNode = { type: 'RangeExpression', } this.next() this.isMatchPattern = true this.data.segments = [] let start = false, hasColon = false loop: while (true) { switch (this.state.type) { case colonTok: hasColon = true start = true this.next() break case bracketRTok: if (!hasColon && !node.end) { node.end = node.start } break loop case commaTok: // never reach throw this.unexpect() case eofTok: // never reach break loop default: if (!start) { node.start = this.parseAtom(this.state.type) as IdentifierNode } else { node.end = this.parseAtom(this.state.type) as IdentifierNode } } } this.next() this.append(parent, this.parseAtom(this.state.type)) return node } } ``` -------------------------------------------------------------------------------- /packages/element/src/form-item/style.scss: -------------------------------------------------------------------------------- ```scss @use 'sass:math'; @import '../__builtins__/styles/common.scss'; @import './var.scss'; @import './grid.scss'; @import './animation.scss'; .#{$form-item-prefix} { display: flex; margin-bottom: $--form-item-margin-bottom; position: relative; line-height: $--form-item-medium-line-height; font-size: $--form-font-size; &-label * { line-height: $--form-item-medium-line-height; } &-label-content { min-height: $--form-item-medium-line-height; } &-content-component { line-height: $--form-item-medium-line-height; } .#{$namespace}-input, .#{$namespace}-input-number, .#{$namespace}-input-number.is-controls-right, .#{$namespace}-select, .#{$namespace}-cascader, .#{$namespace}-date-editor--daterange, .#{$namespace}-date-editor--timerange, .#{$namespace}-date-editor--datetimerange, .#{$namespace}-date-editor.#{$namespace}-input, .#{$namespace}-date-editor.#{$namespace}-input__inner, .#{$namespace}-tree-select { width: 100%; } .#{$namespace}-input-group { vertical-align: top; } } .#{$form-item-prefix}-label { position: relative; display: flex; &-content { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } &-tooltip { cursor: help; * { cursor: help; } label { border-bottom: 1px dashed currentColor; } } } .#{$form-item-prefix}-label label { color: $--color-text-regular; } .#{$form-item-prefix}-label-align-left { > .#{$form-item-prefix}-label { justify-content: flex-start; } } .#{$form-item-prefix}-label-align-right { > .#{$form-item-prefix}-label { justify-content: flex-end; } } .#{$form-item-prefix}-label-wrap { .#{$form-item-prefix}-label { label { white-space: pre-line; } } } .#{$form-item-prefix}-feedback-layout-terse { margin-bottom: 8px; &.#{$form-item-prefix}-feedback-has-text:not(.#{$form-item-prefix}-inset) { margin-bottom: 0; } } .#{$form-item-prefix}-feedback-layout-loose { margin-bottom: $--form-error-line-height; &.#{$form-item-prefix}-feedback-has-text:not(.#{$form-item-prefix}-inset) { margin-bottom: 0; } } .#{$form-item-prefix}-feedback-layout-none { margin-bottom: 0; &.#{$form-item-prefix}-feedback-has-text:not(.#{$form-item-prefix}-inset) { margin-bottom: 0; } } .#{$form-item-prefix}-control { width: 100%; flex: 1; .#{$form-item-prefix}-control-content { display: flex; .#{$form-item-prefix}-control-content-component { width: 100%; min-height: $--form-item-medium-line-height; line-height: $--form-item-medium-line-height; &-has-feedback-icon { flex: 1; position: relative; display: flex; align-items: center; } } .#{$form-item-prefix}-addon-before { margin-right: 8px; display: inline-flex; align-items: center; min-height: $--form-item-medium-line-height; flex-shrink: 0; } .#{$form-item-prefix}-addon-after { margin-left: 8px; display: inline-flex; align-items: center; min-height: $--form-item-medium-line-height; flex-shrink: 0; } } } .#{$form-item-prefix}-size-small { font-size: $--font-size-extra-small; .#{$form-item-prefix}-label * { line-height: $--form-item-small-line-height; } .#{$form-item-prefix}-label-content { min-height: $--form-item-small-line-height; } .#{$form-item-prefix}-control-content { .#{$form-item-prefix}-control-content-component { line-height: $--form-item-small-line-height; min-height: $--form-item-small-line-height; } } .#{$form-item-prefix}-help, .#{$form-item-prefix}-extra { min-height: $--form-error-line-height; } .#{$form-item-prefix}-control-content { min-height: $--form-item-small-line-height; } .#{$form-item-prefix}-label > label { height: $--form-item-small-line-height; } .#{$namespace}-input { input { height: $--form-item-small-line-height; line-height: $--form-item-small-line-height; } } .#{$namespace}-input-number { line-height: $--form-item-small-line-height; &.is-controls-right { .#{$namespace}-input-number__increase, .#{$namespace}-input-number__decrease { line-height: math.div($--form-item-small-line-height, 2); height: math.div($--form-item-small-line-height, 2); font-size: $--font-size-extra-small; box-sizing: border-box; } } } } .#{$form-item-prefix}-size-large { font-size: $--font-size-medium; .#{$form-item-prefix}-label * { line-height: $--form-item-large-line-height; } .#{$form-item-prefix}-label-content { min-height: $--form-item-large-line-height; } .#{$form-item-prefix}-control-content { .#{$form-item-prefix}-control-content-component { line-height: $--form-item-large-line-height; min-height: $--form-item-large-line-height; } } .#{$form-item-prefix}-help, .#{$form-item-prefix}-extra { min-height: $--form-error-line-height; } .#{$form-item-prefix}-control-content { min-height: $--form-item-large-line-height; } .#{$namespace}-input { input { height: $--form-item-large-line-height; line-height: $--form-item-large-line-height; } } .#{$namespace}-select { input { height: $--form-item-large-line-height !important; line-height: $--form-item-large-line-height; } } .#{$namespace}-select__tags .el-tag { height: $--form-item-large-line-height - 12px; line-height: $--form-item-large-line-height - 12px; } .#{$namespace}-input-number { line-height: $--form-item-large-line-height; &.is-controls-right { .#{$namespace}-input-number__increase, .#{$namespace}-input-number__decrease { line-height: math.div($--form-item-large-line-height, 2) - 1; font-size: $--font-size-medium; } } } } .#{$form-item-prefix} { &-layout-vertical { display: block; .#{$form-item-prefix}-label * { line-height: $--form-item-label-top-line-height; } .#{$form-item-prefix}-label-content { min-height: $--form-item-label-top-line-height; } } } .#{$form-item-prefix}-feedback-layout-popover { margin-bottom: 8px; } .#{$form-item-prefix}-label-tooltip { margin-left: 4px; color: $--color-text-secondary; display: flex; align-items: center; height: $--form-item-medium-line-height; cursor: pointer; i { line-height: 1; } } .#{$form-item-prefix}-control-align-left { .#{$form-item-prefix}-control-content { justify-content: flex-start; } } .#{$form-item-prefix}-control-align-right { .#{$form-item-prefix}-control-content { justify-content: flex-end; } } .#{$form-item-prefix}-control-wrap { .#{$form-item-prefix}-control { white-space: pre-line; } } .#{$form-item-prefix}-asterisk { color: $--color-danger; margin-right: 4px; display: inline-block; font-family: SimSun, sans-serif; } .#{$form-item-prefix}-colon { margin-left: 2px; margin-right: 8px; } .#{$form-item-prefix}-help, .#{$form-item-prefix}-extra { clear: both; min-height: $--form-error-line-height; line-height: $--form-error-line-height; color: $--color-text-secondary; transition: $--color-transition-base; padding-top: 0; } .#{$form-item-prefix}-fullness { > .#{$form-item-prefix}-control { > .#{$form-item-prefix}-control-content { > .#{$form-item-prefix}-control-content-component { > *:first-child { width: 100%; } } } } } .#{$form-item-prefix}-control-content-component-has-feedback-icon { border-radius: $--border-radius-base; border: $--border-base; padding-right: 8px; transition: $--all-transition; touch-action: manipulation; outline: none; .#{$namespace}-input-number, .#{$namespace}-date-editor .#{$namespace}-input__inner, .#{$namespace}-select .#{$namespace}-input__inner, .#{$namespace}-input .#{$namespace}-input__inner { border: none !important; box-shadow: none !important; } .#{$namespace}-input-number.is-controls-right .#{$namespace}-input__inner { padding-right: 40px; } .#{$namespace}-input-number.is-controls-right .#{$namespace}-input-number__increase { top: 0; right: 8px; border-right: $--border-base; } .#{$namespace}-input-number.is-controls-right .#{$namespace}-input-number__decrease { bottom: 0; right: 8px; border-right: $--border-base; } } .#{$form-item-prefix} { &:hover { .#{$form-item-prefix}-control-content-component-has-feedback-icon { @include hover; } } } .#{$form-item-prefix}-active { .#{$form-item-prefix}-control-content-component-has-feedback-icon { @include active; } } .#{$form-item-prefix}-error { & .#{$namespace}-input__inner, & .#{$namespace}-textarea__inner { &, &.hover { border-color: $--color-danger; } } & .#{$namespace}-input__inner, & .#{$namespace}-textarea__inner { &:focus { border-color: $--color-danger; } } & .#{$namespace}-input-group__append, & .#{$namespace}-input-group__prepend { & .#{$namespace}-input__inner { border-color: transparent; } } .#{$namespace}-input__validateIcon { color: $--color-danger !important; } } .#{$form-item-prefix}-error-help, .#{$form-item-prefix}-warning-help, .#{$form-item-prefix}-success-help { i { margin-right: 8px; } } .#{$form-item-prefix}-error-help { color: $--color-danger; } .#{$form-item-prefix}-warning-help { color: $--color-warning; } .#{$form-item-prefix}-success-help { color: $--color-success; } .#{$form-item-prefix}-warning { & .#{$namespace}-input__inner, & .#{$namespace}-textarea__inner { &, &.hover { border-color: $--color-warning; } } & .#{$namespace}-input__inner, & .#{$namespace}-textarea__inner { &:focus { border-color: $--color-warning; } } & .#{$namespace}-input-group__append, & .#{$namespace}-input-group__prepend { & .#{$namespace}-input__inner { border-color: transparent; } } .#{$namespace}-input__validateIcon { color: $--color-warning !important; } } .#{$form-item-prefix}-success { & .#{$namespace}-input__inner, & .#{$namespace}-textarea__inner { &, &.hover { border-color: $--color-success; } } & .#{$namespace}-input__inner, & .#{$namespace}-textarea__inner { &:focus { border-color: $--color-success; } } & .#{$namespace}-input-group__append, & .#{$namespace}-input-group__prepend { & .#{$namespace}-input__inner { border-color: transparent; } } .#{$namespace}-input__validateIcon { color: $--color-success !important; } } .#{$form-item-prefix}-bordered-none { .#{$namespace}-input__inner { border: none !important; } .#{$namespace}-input-number__decrease, .#{$namespace}-input-number__increase { border: none !important; background: transparent !important; } } .#{$form-item-prefix}-inset { border-radius: $--border-radius-base; border: $--border-base; padding-left: 12px; transition: 0.3s all; &:hover { @include hover; } } .#{$form-item-prefix}-inset-active { @include active; } ``` -------------------------------------------------------------------------------- /packages/antd/src/select-table/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useState, useMemo } from 'react' import { observer, useFieldSchema, useField, Schema, RecursionField, } from '@formily/react' import cls from 'classnames' import { GeneralField, FieldDisplayTypes } from '@formily/core' import { isArr, isBool, isFn } from '@formily/shared' import { Input, Table } from 'antd' import { TableProps, ColumnProps } from 'antd/lib/table' import { SearchProps } from 'antd/lib/input' import { useFilterOptions } from './useFilterOptions' import { useFlatOptions } from './useFlatOptions' import { useSize } from './useSize' import { useTitleAddon } from './useTitleAddon' import { useCheckSlackly, getIndeterminate } from './useCheckSlackly' import { getUISelected, getOutputData } from './utils' import { usePrefixCls } from '../__builtins__' const { Search } = Input interface ObservableColumnSource { field: GeneralField columnProps: ColumnProps<any> schema: Schema display: FieldDisplayTypes name: string } type IFilterOption = boolean | ((option: any, keyword: string) => boolean) type IFilterSort = (optionA: any, optionB: any) => number export interface ISelectTableColumnProps extends ColumnProps<any> { key: React.ReactText } export interface ISelectTableProps extends TableProps<any> { mode?: 'multiple' | 'single' dataSource?: any[] optionAsValue?: boolean valueType?: 'all' | 'parent' | 'child' | 'path' showSearch?: boolean searchProps?: SearchProps primaryKey?: string | ((record: any) => string) filterOption?: IFilterOption filterSort?: IFilterSort onSearch?: (keyword: string) => void onChange?: (value: any, options: any) => void value?: any } type ComposedSelectTable = React.FC< React.PropsWithChildren<ISelectTableProps> > & { Column?: React.FC<React.PropsWithChildren<ISelectTableColumnProps>> } const isColumnComponent = (schema: Schema) => { return schema['x-component']?.indexOf('Column') > -1 } const useSources = () => { const arrayField = useField() const schema = useFieldSchema() const parseSources = (schema: Schema): ObservableColumnSource[] => { if (isColumnComponent(schema)) { if (!schema['x-component-props']?.['dataIndex'] && !schema['name']) return [] const name = schema['x-component-props']?.['dataIndex'] || schema['name'] const field = arrayField.query(arrayField.address.concat(name)).take() const columnProps = field?.component?.[1] || schema['x-component-props'] || {} const display = field?.display || schema['x-display'] return [ { name, display, field, schema, columnProps: { title: field?.title || columnProps.title, ...columnProps, }, }, ] } else if (schema.properties) { return schema.reduceProperties((buf, schema) => { return buf.concat(parseSources(schema)) }, []) } } const parseArrayItems = (schema: Schema['items']) => { if (!schema) return [] const sources: ObservableColumnSource[] = [] const items = isArr(schema) ? schema : [schema] return items.reduce((columns, schema) => { const item = parseSources(schema) if (item) { return columns.concat(item) } return columns }, sources) } const validSchema = ( schema?.type === 'array' && schema?.items ? schema.items : schema ) as Schema return parseArrayItems(validSchema) } const useColumns = ( sources: ObservableColumnSource[] ): TableProps<any>['columns'] => { return sources.reduce((buf, { name, columnProps, schema, display }, key) => { if (display !== 'visible') return buf if (!isColumnComponent(schema)) return buf return buf.concat({ ...columnProps, key, dataIndex: name, }) }, []) } const addPrimaryKey = (dataSource, rowKey, primaryKey) => dataSource.map((item) => { const children = isArr(item.children) ? addPrimaryKey(item.children, rowKey, primaryKey) : {} return { ...item, ...children, [primaryKey]: rowKey(item), } }) export const SelectTable: ComposedSelectTable = observer((props) => { const { mode = 'multiple', dataSource: propsDataSource, optionAsValue, valueType = 'all', showSearch = false, filterOption, filterSort, onSearch, searchProps, className, value, onChange, rowSelection, primaryKey: rowKey = 'key', ...otherTableProps } = props const prefixCls = usePrefixCls('formily-select-table', props) const [searchValue, setSearchValue] = useState<string>() const field = useField() as any const loading = isBool(props.loading) ? props.loading : field.loading const disabled = field.disabled const readOnly = field.readOnly const readPretty = field.readPretty const { searchSize, tableSize } = useSize( field.decoratorProps?.size, searchProps?.size, props?.size ) const primaryKey = isFn(rowKey) ? '__formily_key__' : rowKey const sources = useSources() const columns = useColumns(sources) // dataSource let dataSource = isArr(propsDataSource) ? propsDataSource : field.dataSource dataSource = isFn(rowKey) ? addPrimaryKey(dataSource, rowKey, primaryKey) : dataSource // Filter dataSource By Search const filteredDataSource = useFilterOptions( dataSource, searchValue, filterOption, rowSelection?.checkStrictly ) // Order dataSource By filterSort const orderedFilteredDataSource = useMemo(() => { if (!filterSort) { return filteredDataSource } return [...filteredDataSource].sort((a, b) => filterSort(a, b)) }, [filteredDataSource, filterSort]) const flatDataSource = useFlatOptions(dataSource) const flatFilteredDataSource = useFlatOptions(filteredDataSource) // 分页或异步查询时,dataSource会丢失已选数据,配置optionAsValue则无法获取已选数据,需要进行合并 const getWholeDataSource = () => { if (optionAsValue && mode === 'multiple' && value?.length) { const map = new Map() const arr = [...flatDataSource, ...value] arr.forEach((item) => { if (!map.has(item[primaryKey])) { map.set(item[primaryKey], item) } }) return [...map.values()] } return flatDataSource } // selected keys for Table UI const selected = getUISelected( value, flatDataSource, primaryKey, valueType, optionAsValue, mode, rowSelection?.checkStrictly, rowKey ) // readPretty Value const readPrettyDataSource = useFilterOptions( orderedFilteredDataSource, selected, (value, item) => value.includes(item[primaryKey]) ) const onInnerSearch = (searchText) => { const formatted = (searchText || '').trim() setSearchValue(searchText) onSearch?.(formatted) } const onInnerChange = (selectedRowKeys: any[]) => { if (readOnly) { return } // 筛选后onChange默认的records数据不完整,此处需使用完整数据过滤 const wholeRecords = getWholeDataSource().filter((item) => selectedRowKeys.includes(item?.[primaryKey]) ) const { outputValue, outputOptions } = getOutputData( selectedRowKeys, wholeRecords, dataSource, primaryKey, valueType, optionAsValue, mode, rowSelection?.checkStrictly ) onChange?.(outputValue, outputOptions) } const onRowClick = (record) => { if (readPretty || disabled || readOnly || record?.disabled) { return } const selectedRowKey = record?.[primaryKey] const isSelected = selected?.includes(selectedRowKey) let selectedRowKeys = [] if (mode === 'single') { selectedRowKeys = [selectedRowKey] } else { if (isSelected) { selectedRowKeys = selected.filter((item) => item !== selectedRowKey) } else { selectedRowKeys = [...selected, selectedRowKey] } } if (rowSelection?.checkStrictly !== false) { onInnerChange(selectedRowKeys) } else { onSlacklyChange(selectedRowKeys) } } // TreeData SlacklyChange const onSlacklyChange = (currentSelected: any[]) => { let { selectedRowKeys } = useCheckSlackly( currentSelected, selected, flatDataSource, flatFilteredDataSource, primaryKey, rowSelection?.checkStrictly ) onInnerChange(selectedRowKeys) } // Table All Checkbox const titleAddon = useTitleAddon( selected, flatDataSource, flatFilteredDataSource, primaryKey, mode, disabled, readOnly, rowSelection?.checkStrictly, onInnerChange ) // Antd rowSelection type const modeAsType: any = { multiple: 'checkbox', single: 'radio' }?.[mode] return ( <div className={prefixCls}> {showSearch ? ( <Search {...searchProps} className={cls(`${prefixCls}-search`, searchProps?.className)} style={{ width: '100%', ...searchProps?.style }} onSearch={onInnerSearch} onChange={(e) => onInnerSearch(e.target.value)} disabled={disabled} readOnly={readOnly} size={searchSize} loading={loading} // antd /> ) : null} <Table {...otherTableProps} className={cls(`${prefixCls}-table`, className)} dataSource={ readPretty ? readPrettyDataSource : orderedFilteredDataSource } rowSelection={ readPretty ? undefined : ({ ...rowSelection, ...titleAddon, getCheckboxProps: (record) => ({ ...(rowSelection?.getCheckboxProps?.(record) as any), disabled: disabled || record?.disabled, }), // antd ...(rowSelection?.checkStrictly !== false ? {} : { renderCell: (checked, record, index, originNode) => { return React.cloneElement( originNode as React.ReactElement, { indeterminate: getIndeterminate( record, flatDataSource, selected, primaryKey ), } ) }, }), selectedRowKeys: selected, onChange: rowSelection?.checkStrictly !== false ? onInnerChange : onSlacklyChange, type: modeAsType, preserveSelectedRowKeys: true, checkStrictly: true, } as any) } columns={props.columns || columns} rowKey={primaryKey} loading={loading} size={tableSize} onRow={(record) => { // antd const onRowResult = otherTableProps.onRow?.(record) return { ...onRowResult, onClick: (e) => { onRowResult?.onClick?.(e) onRowClick(record) }, } }} > {''} </Table> {sources.map((column, key) => { //专门用来承接对Column的状态管理 if (!isColumnComponent(column.schema)) return return React.createElement(RecursionField, { name: column.name, schema: column.schema, onlyRenderSelf: true, key, }) })} </div> ) }) const TableColumn: React.FC< React.PropsWithChildren<ISelectTableColumnProps> > = () => <></> SelectTable.Column = TableColumn export default SelectTable ``` -------------------------------------------------------------------------------- /packages/element/src/form-drawer/index.ts: -------------------------------------------------------------------------------- ```typescript import { createForm, Form, IFormProps } from '@formily/core' import { toJS } from '@formily/reactive' import { observer } from '@formily/reactive-vue' import { applyMiddleware, IMiddleware, isBool, isFn, isNum, isStr, } from '@formily/shared' import { FormProvider, Fragment, h } from '@formily/vue' import type { Button as ButtonProps, Drawer as DrawerProps } from 'element-ui' import { Button, Drawer } from 'element-ui' import { t } from 'element-ui/src/locale' import { Portal, PortalTarget } from 'portal-vue' import Vue, { Component, VNode } from 'vue' import { defineComponent } from 'vue-demi' import { stylePrefix } from '../__builtins__/configs' import { createPortalProvider, getProtalContext, isValidElement, loading, resolveComponent, } from '../__builtins__/shared' type FormDrawerContentProps = { form: Form } type FormDrawerContent = Component | ((props: FormDrawerContentProps) => VNode) type DrawerTitle = string | number | Component | VNode | (() => VNode) type IFormDrawerProps = Omit<DrawerProps, 'title'> & { title?: DrawerTitle footer?: null | Component | VNode | (() => VNode) cancelText?: string | Component | VNode | (() => VNode) cancelButtonProps?: ButtonProps okText?: string | Component | VNode | (() => VNode) okButtonProps?: ButtonProps onOpen?: () => void onOpened?: () => void onClose?: () => void onClosed?: () => void onCancel?: () => void onOK?: () => void loadingText?: string } const PORTAL_TARGET_NAME = 'FormDrawerFooter' const isDrawerTitle = (props: any): props is DrawerTitle => { return isNum(props) || isStr(props) || isBool(props) || isValidElement(props) } const getDrawerProps = (props: any): IFormDrawerProps => { if (isDrawerTitle(props)) { return { title: props, } as IFormDrawerProps } else { return props } } export interface IFormDrawer { forOpen(middleware: IMiddleware<IFormProps>): IFormDrawer forConfirm(middleware: IMiddleware<IFormProps>): IFormDrawer forCancel(middleware: IMiddleware<IFormProps>): IFormDrawer open(props?: IFormProps): Promise<any> close(): void } export interface IFormDrawerComponentProps { content: FormDrawerContent resolve: () => any reject: () => any } export function FormDrawer( title: IFormDrawerProps | DrawerTitle, content: FormDrawerContent ): IFormDrawer export function FormDrawer( title: IFormDrawerProps | DrawerTitle, id: string | symbol, content: FormDrawerContent ): IFormDrawer export function FormDrawer( title: DrawerTitle, id: string, content: FormDrawerContent ): IFormDrawer export function FormDrawer( title: IFormDrawerProps | DrawerTitle, id: string | symbol | FormDrawerContent, content?: FormDrawerContent ): IFormDrawer { if (isFn(id) || isValidElement(id)) { content = id as FormDrawerContent id = 'form-drawer' } const prefixCls = `${stylePrefix}-form-drawer` const env = { root: document.createElement('div'), form: null, promise: null, instance: null, openMiddlewares: [], confirmMiddlewares: [], cancelMiddlewares: [], } document.body.appendChild(env.root) const props = getDrawerProps(title) const drawerProps = { ...props, onClosed: () => { props.onClosed?.() env.instance.$destroy() env.instance = null env.root?.parentNode?.removeChild(env.root) env.root = undefined }, } const component = observer( defineComponent({ setup() { return () => h( Fragment, {}, { default: () => resolveComponent(content, { form: env.form, }), } ) }, }) ) const render = (visible = true, resolve?: () => any, reject?: () => any) => { if (!env.instance) { const ComponentConstructor = Vue.extend({ props: ['drawerProps'], data() { return { visible: false, } }, render() { const { onClose, onClosed, onOpen, onOpened, onOK, onCancel, title, footer, okText, cancelText, okButtonProps, cancelButtonProps, ...drawerProps } = this.drawerProps return h( FormProvider, { props: { form: env.form, }, }, { default: () => h( Drawer, { class: [`${prefixCls}`], attrs: { visible: this.visible, ...drawerProps, }, on: { 'update:visible': (val) => { this.visible = val }, close: () => { onClose?.() }, closed: () => { onClosed?.() }, open: () => { onOpen?.() }, opened: () => { onOpened?.() }, }, }, { default: () => [ h( 'div', { class: [`${prefixCls}-body`], }, { default: () => h(component, {}, {}), } ), h( 'div', { class: [`${prefixCls}-footer`], }, { default: () => { const FooterProtalTarget = h( PortalTarget, { props: { name: PORTAL_TARGET_NAME, slim: true, }, }, {} ) if (footer === null) { return [null, FooterProtalTarget] } else if (footer) { return [ resolveComponent(footer), FooterProtalTarget, ] } return [ h( Button, { attrs: cancelButtonProps, on: { click: (e) => { onCancel?.(e) reject() }, }, }, { default: () => resolveComponent( cancelText || t('el.popconfirm.cancelButtonText') ), } ), h( Button, { attrs: { type: 'primary', ...okButtonProps, }, on: { click: (e) => { onOK?.(e) resolve() }, }, }, { default: () => resolveComponent( okText || t('el.popconfirm.confirmButtonText') ), } ), FooterProtalTarget, ] }, } ), ], title: () => h('div', {}, { default: () => resolveComponent(title) }), } ), } ) }, }) env.instance = new ComponentConstructor({ propsData: { drawerProps, }, parent: getProtalContext(id as string | symbol), }) env.instance.$mount(env.root) } env.instance.visible = visible } const formDrawer = { forOpen: (middleware: IMiddleware<IFormProps>) => { if (isFn(middleware)) { env.openMiddlewares.push(middleware) } return formDrawer }, forConfirm: (middleware: IMiddleware<Form>) => { if (isFn(middleware)) { env.confirmMiddlewares.push(middleware) } return formDrawer }, forCancel: (middleware: IMiddleware<Form>) => { if (isFn(middleware)) { env.cancelMiddlewares.push(middleware) } return formDrawer }, open: (props: IFormProps) => { if (env.promise) return env.promise env.promise = new Promise(async (resolve, reject) => { try { props = await loading(drawerProps.loadingText, () => applyMiddleware(props, env.openMiddlewares) ) env.form = env.form || createForm(props) } catch (e) { reject(e) } render( true, () => { env.form .submit(async () => { await applyMiddleware(env.form, env.confirmMiddlewares) resolve(toJS(env.form.values)) if (drawerProps.beforeClose) { setTimeout(() => { drawerProps.beforeClose(() => { formDrawer.close() }) }) } else { formDrawer.close() } }) .catch(() => {}) }, async () => { await loading(drawerProps.loadingText, () => applyMiddleware(env.form, env.cancelMiddlewares) ) if (drawerProps.beforeClose) { drawerProps.beforeClose(() => { formDrawer.close() }) } else { formDrawer.close() } } ) }) return env.promise }, close: () => { if (!env.root) return render(false) }, } return formDrawer } const FormDrawerFooter = defineComponent({ name: 'FFormDrawerFooter', setup(props, { slots }) { return () => { return h( Portal, { props: { to: PORTAL_TARGET_NAME, }, }, slots ) } }, }) FormDrawer.Footer = FormDrawerFooter FormDrawer.Protal = createPortalProvider('form-drawer') export default FormDrawer ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Select.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Select > 下拉框组件 ## Markup Schema 同步数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="select" title="选择框" x-decorator="FormItem" x-component="Select" enum={[ { label: '选项1', value: 1 }, { label: '选项2', value: 2 }, ]} x-component-props={{ style: { width: 120, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Markup Schema 异步搜索案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm, onFieldReact, onFieldInit, FormPathPattern, Field, } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { action, observable } from '@formily/reactive' import { fetch } from 'mfetch' let timeout let currentValue function fetchData(value, callback) { if (timeout) { clearTimeout(timeout) timeout = null } currentValue = value function fake() { fetch(`https://suggest.taobao.com/sug?q=${value}`, { method: 'jsonp', }) .then((response) => response.json()) .then((d) => { if (currentValue === value) { const { result } = d const data = [] result.forEach((r) => { data.push({ value: r[0], text: r[0], }) }) callback(data) } }) } timeout = setTimeout(fake, 300) } const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const useAsyncDataSource = ( pattern: FormPathPattern, service: (param: { keyword: string field: Field }) => Promise<{ label: string; value: any }[]> ) => { const keyword = observable.ref('') onFieldInit(pattern, (field) => { field.setComponentProps({ onSearch: (value) => { keyword.value = value }, }) }) onFieldReact(pattern, (field) => { field.loading = true service({ field, keyword: keyword.value }).then( action.bound((data) => { field.dataSource = data field.loading = false }) ) }) } const form = createForm({ effects: () => { useAsyncDataSource('select', async ({ keyword }) => { if (!keyword) { return [] } return new Promise((resolve) => { fetchData(keyword, resolve) }) }) }, }) export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="select" title="异步搜索选择框" x-decorator="FormItem" x-component="Select" x-component-props={{ showSearch: true, filterOption: false, style: { width: 300, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Markup Schema 异步联动数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm, onFieldReact, FormPathPattern, Field } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { action } from '@formily/reactive' const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const useAsyncDataSource = ( pattern: FormPathPattern, service: (field: Field) => Promise<{ label: string; value: any }[]> ) => { onFieldReact(pattern, (field) => { field.loading = true service(field).then( action.bound((data) => { field.dataSource = data field.loading = false }) ) }) } const form = createForm({ effects: () => { useAsyncDataSource('select', async (field) => { const linkage = field.query('linkage').get('value') if (!linkage) return [] return new Promise((resolve) => { setTimeout(() => { if (linkage === 1) { resolve([ { label: 'AAA', value: 'aaa', }, { label: 'BBB', value: 'ccc', }, ]) } else if (linkage === 2) { resolve([ { label: 'CCC', value: 'ccc', }, { label: 'DDD', value: 'ddd', }, ]) } }, 1500) }) }) }, }) export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="linkage" title="联动选择框" x-decorator="FormItem" x-component="Select" enum={[ { label: '发请求1', value: 1 }, { label: '发请求2', value: 2 }, ]} x-component-props={{ style: { width: 120, }, }} /> <SchemaField.String name="select" title="异步选择框" x-decorator="FormItem" x-component="Select" x-component-props={{ style: { width: 120, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 同步数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { select: { type: 'string', title: '选择框', 'x-decorator': 'FormItem', 'x-component': 'Select', enum: [ { label: '选项1', value: 1 }, { label: '选项2', value: 2 }, ], 'x-component-props': { style: { width: 120, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 异步联动数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { action } from '@formily/reactive' const SchemaField = createSchemaField({ components: { Select, FormItem, }, }) const loadData = async (field) => { const linkage = field.query('linkage').get('value') if (!linkage) return [] return new Promise((resolve) => { setTimeout(() => { if (linkage === 1) { resolve([ { label: 'AAA', value: 'aaa', }, { label: 'BBB', value: 'ccc', }, ]) } else if (linkage === 2) { resolve([ { label: 'CCC', value: 'ccc', }, { label: 'DDD', value: 'ddd', }, ]) } }, 1500) }) } const useAsyncDataSource = (service) => (field) => { field.loading = true service(field).then( action.bound((data) => { field.dataSource = data field.loading = false }) ) } const form = createForm() const schema = { type: 'object', properties: { linkage: { type: 'string', title: '联动选择框', enum: [ { label: '发请求1', value: 1 }, { label: '发请求2', value: 2 }, ], 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: 120, }, }, }, select: { type: 'string', title: '异步选择框', 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { style: { width: 120, }, }, 'x-reactions': ['{{useAsyncDataSource(loadData)}}'], }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} scope={{ useAsyncDataSource, loadData }} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 同步数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="select" title="选择框" dataSource={[ { label: '选项1', value: 1 }, { label: '选项2', value: 2 }, ]} decorator={[FormItem]} component={[ Select, { style: { width: 120, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 异步联动数据源案例 ```tsx import React from 'react' import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm, onFieldReact, FormPathPattern, Field as FieldType, } from '@formily/core' import { FormProvider, Field } from '@formily/react' import { action } from '@formily/reactive' const useAsyncDataSource = ( pattern: FormPathPattern, service: (field: FieldType) => Promise<{ label: string; value: any }[]> ) => { onFieldReact(pattern, (field) => { field.loading = true service(field).then( action.bound((data) => { field.dataSource = data field.loading = false }) ) }) } const form = createForm({ effects: () => { useAsyncDataSource('select', async (field) => { const linkage = field.query('linkage').get('value') if (!linkage) return [] return new Promise((resolve) => { setTimeout(() => { if (linkage === 1) { resolve([ { label: 'AAA', value: 'aaa', }, { label: 'BBB', value: 'ccc', }, ]) } else if (linkage === 2) { resolve([ { label: 'CCC', value: 'ccc', }, { label: 'DDD', value: 'ddd', }, ]) } }, 1500) }) }) }, }) export default () => ( <FormProvider form={form}> <Field name="linkage" title="联动选择框" dataSource={[ { label: '发请求1', value: 1 }, { label: '发请求2', value: 2 }, ]} decorator={[FormItem]} component={[ Select, { style: { width: 120, }, }, ]} /> <Field name="select" title="异步选择框" decorator={[FormItem]} component={[ Select, { style: { width: 120, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://ant.design/components/select-cn/ ```