This is page 27 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/element/src/array-collapse/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ArrayField } from '@formily/core' 2 | import { ISchema } from '@formily/json-schema' 3 | import { observer } from '@formily/reactive-vue' 4 | import { 5 | Fragment, 6 | h, 7 | RecursionField, 8 | useField, 9 | useFieldSchema, 10 | } from '@formily/vue' 11 | import type { 12 | Collapse as CollapseProps, 13 | CollapseItem as CollapseItemProps, 14 | } from 'element-ui' 15 | import { Badge, Card, Collapse, CollapseItem, Empty, Row } from 'element-ui' 16 | import { defineComponent, ref, Ref, watchEffect } from 'vue-demi' 17 | import { ArrayBase } from '../array-base' 18 | import { stylePrefix } from '../__builtins__/configs' 19 | import { composeExport } from '../__builtins__/shared' 20 | 21 | export interface IArrayCollapseProps extends CollapseProps { 22 | defaultOpenPanelCount?: number 23 | } 24 | 25 | const isAdditionComponent = (schema: ISchema) => { 26 | return schema['x-component']?.indexOf?.('Addition') > -1 27 | } 28 | 29 | const isIndexComponent = (schema: ISchema) => { 30 | return schema['x-component']?.indexOf?.('Index') > -1 31 | } 32 | 33 | const isRemoveComponent = (schema: ISchema) => { 34 | return schema['x-component']?.indexOf?.('Remove') > -1 35 | } 36 | 37 | const isMoveUpComponent = (schema: ISchema) => { 38 | return schema['x-component']?.indexOf?.('MoveUp') > -1 39 | } 40 | 41 | const isMoveDownComponent = (schema: ISchema) => { 42 | return schema['x-component']?.indexOf?.('MoveDown') > -1 43 | } 44 | 45 | const isOperationComponent = (schema: ISchema) => { 46 | return ( 47 | isAdditionComponent(schema) || 48 | isRemoveComponent(schema) || 49 | isMoveDownComponent(schema) || 50 | isMoveUpComponent(schema) 51 | ) 52 | } 53 | 54 | const range = (count: number) => Array.from({ length: count }).map((_, i) => i) 55 | 56 | const takeDefaultActiveKeys = ( 57 | dataSourceLength: number, 58 | defaultOpenPanelCount: number, 59 | accordion = false 60 | ) => { 61 | if (accordion) { 62 | return 0 63 | } 64 | if (dataSourceLength < defaultOpenPanelCount) return range(dataSourceLength) 65 | 66 | return range(defaultOpenPanelCount) 67 | } 68 | 69 | const insertActiveKeys = ( 70 | activeKeys: number[] | number, 71 | index: number, 72 | accordion = false 73 | ) => { 74 | if (accordion) return index 75 | if ((activeKeys as number[]).length <= index) 76 | return (activeKeys as number[]).concat(index) 77 | return (activeKeys as number[]).reduce((buf, key) => { 78 | if (key < index) return buf.concat(key) 79 | if (key === index) return buf.concat([key, key + 1]) 80 | return buf.concat(key + 1) 81 | }, []) 82 | } 83 | 84 | export const ArrayCollapseInner = observer( 85 | defineComponent<IArrayCollapseProps>({ 86 | name: 'FArrayCollapse', 87 | props: { 88 | defaultOpenPanelCount: { 89 | type: Number, 90 | default: 5, 91 | }, 92 | }, 93 | setup(props, { attrs }) { 94 | const fieldRef = useField<ArrayField>() 95 | const schemaRef = useFieldSchema() 96 | 97 | const prefixCls = `${stylePrefix}-array-collapse` 98 | const activeKeys: Ref<number[] | number> = ref([]) 99 | 100 | watchEffect(() => { 101 | const field = fieldRef.value 102 | const dataSource = Array.isArray(field.value) ? field.value.slice() : [] 103 | if (!field.modified && dataSource.length) { 104 | activeKeys.value = takeDefaultActiveKeys( 105 | dataSource.length, 106 | props.defaultOpenPanelCount, 107 | attrs.accordion as boolean 108 | ) 109 | } 110 | }) 111 | 112 | const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value) 113 | 114 | return () => { 115 | const field = fieldRef.value 116 | const schema = schemaRef.value 117 | const dataSource = Array.isArray(field.value) ? field.value.slice() : [] 118 | if (!schema) throw new Error('can not found schema object') 119 | 120 | const renderItems = () => { 121 | if (!dataSource.length) { 122 | return null 123 | } 124 | 125 | const items = dataSource?.map((item, index) => { 126 | const items = Array.isArray(schema.items) 127 | ? schema.items[index] || schema.items[0] 128 | : schema.items 129 | const key = getKey(item, index) 130 | const panelProps = field 131 | .query(`${field.address}.${index}`) 132 | .get('componentProps') 133 | const props: CollapseItemProps = items['x-component-props'] 134 | const headerTitle = panelProps?.title || props.title || field.title 135 | const path = field.address.concat(index) 136 | const errors = field.form.queryFeedbacks({ 137 | type: 'error', 138 | address: `${path}.**`, 139 | }) 140 | 141 | const title = h( 142 | ArrayBase.Item, 143 | { 144 | props: { 145 | index, 146 | record: item, 147 | }, 148 | }, 149 | { 150 | default: () => [ 151 | h( 152 | RecursionField, 153 | { 154 | props: { 155 | schema: items, 156 | name: index, 157 | filterProperties: (schema) => { 158 | if (!isIndexComponent(schema)) return false 159 | return true 160 | }, 161 | onlyRenderProperties: true, 162 | }, 163 | }, 164 | {} 165 | ), 166 | errors.length 167 | ? h( 168 | Badge, 169 | { 170 | class: [`${prefixCls}-errors-badge`], 171 | props: { 172 | value: errors.length, 173 | }, 174 | }, 175 | { default: () => headerTitle } 176 | ) 177 | : headerTitle, 178 | ], 179 | } 180 | ) 181 | const extra = h( 182 | ArrayBase.Item, 183 | { 184 | props: { 185 | index, 186 | record: item, 187 | }, 188 | }, 189 | { 190 | default: () => [ 191 | h( 192 | RecursionField, 193 | { 194 | props: { 195 | schema: items, 196 | name: index, 197 | filterProperties: (schema) => { 198 | if (!isOperationComponent(schema)) return false 199 | return true 200 | }, 201 | onlyRenderProperties: true, 202 | }, 203 | }, 204 | {} 205 | ), 206 | ], 207 | } 208 | ) 209 | const content = h( 210 | RecursionField, 211 | { 212 | props: { 213 | schema: items, 214 | name: index, 215 | filterProperties: (schema) => { 216 | if (isIndexComponent(schema)) return false 217 | if (isOperationComponent(schema)) return false 218 | return true 219 | }, 220 | }, 221 | }, 222 | {} 223 | ) 224 | 225 | return h( 226 | CollapseItem, 227 | { 228 | attrs: { 229 | ...props, 230 | ...panelProps, 231 | name: index, 232 | }, 233 | key, 234 | }, 235 | { 236 | default: () => [ 237 | h( 238 | ArrayBase.Item, 239 | { 240 | props: { 241 | index, 242 | record: item, 243 | }, 244 | }, 245 | { 246 | default: () => [content], 247 | } 248 | ), 249 | ], 250 | title: () => 251 | h( 252 | Row, 253 | { 254 | style: { flex: 1 }, 255 | props: { 256 | type: 'flex', 257 | justify: 'space-between', 258 | }, 259 | }, 260 | { 261 | default: () => [ 262 | h('span', {}, { default: () => title }), 263 | h('span', {}, { default: () => extra }), 264 | ], 265 | } 266 | ), 267 | } 268 | ) 269 | }) 270 | 271 | return h( 272 | Collapse, 273 | { 274 | class: [`${prefixCls}-item`], 275 | attrs: { 276 | ...attrs, 277 | value: activeKeys.value, 278 | }, 279 | on: { 280 | change: (keys: number[] | number) => { 281 | activeKeys.value = keys 282 | }, 283 | }, 284 | }, 285 | { 286 | default: () => [items], 287 | } 288 | ) 289 | } 290 | const renderAddition = () => { 291 | return schema.reduceProperties((addition, schema) => { 292 | if (isAdditionComponent(schema)) { 293 | return h( 294 | RecursionField, 295 | { 296 | props: { 297 | schema, 298 | name: 'addition', 299 | }, 300 | }, 301 | {} 302 | ) 303 | } 304 | return addition 305 | }, null) 306 | } 307 | const renderEmpty = () => { 308 | if (dataSource?.length) return 309 | return h( 310 | Card, 311 | { 312 | class: [`${prefixCls}-item`], 313 | attrs: { 314 | shadow: 'never', 315 | ...attrs, 316 | header: attrs.title || field.title, 317 | }, 318 | }, 319 | { 320 | default: () => 321 | h( 322 | Empty, 323 | { props: { description: 'No Data', imageSize: 100 } }, 324 | {} 325 | ), 326 | } 327 | ) 328 | } 329 | 330 | return h( 331 | 'div', 332 | { 333 | class: [prefixCls], 334 | }, 335 | { 336 | default: () => 337 | h( 338 | ArrayBase, 339 | { 340 | props: { 341 | keyMap, 342 | }, 343 | on: { 344 | add: (index: number) => { 345 | activeKeys.value = insertActiveKeys( 346 | activeKeys.value, 347 | index, 348 | attrs.accordion as boolean 349 | ) 350 | }, 351 | }, 352 | }, 353 | { 354 | default: () => [ 355 | renderEmpty(), 356 | renderItems(), 357 | renderAddition(), 358 | ], 359 | } 360 | ), 361 | } 362 | ) 363 | } 364 | }, 365 | }) 366 | ) 367 | 368 | export const ArrayCollapseItem = defineComponent<CollapseItemProps>({ 369 | name: 'FArrayCollapseItem', 370 | setup(_props, { slots }) { 371 | return () => h(Fragment, {}, slots) 372 | }, 373 | }) 374 | 375 | export const ArrayCollapse = composeExport(ArrayCollapseInner, { 376 | Item: ArrayCollapseItem, 377 | Index: ArrayBase.Index, 378 | SortHandle: ArrayBase.SortHandle, 379 | Addition: ArrayBase.Addition, 380 | Remove: ArrayBase.Remove, 381 | MoveDown: ArrayBase.MoveDown, 382 | MoveUp: ArrayBase.MoveUp, 383 | useArray: ArrayBase.useArray, 384 | useIndex: ArrayBase.useIndex, 385 | useRecord: ArrayBase.useRecord, 386 | }) 387 | 388 | export default ArrayCollapse 389 | ``` -------------------------------------------------------------------------------- /packages/next/src/select-table/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useState, useMemo } from 'react' 2 | import { 3 | observer, 4 | useFieldSchema, 5 | useField, 6 | Schema, 7 | RecursionField, 8 | } from '@formily/react' 9 | import cls from 'classnames' 10 | import { GeneralField, FieldDisplayTypes } from '@formily/core' 11 | import { isArr, isBool, isFn } from '@formily/shared' 12 | import { Search, Table } from '@alifd/next' 13 | import { TableProps, ColumnProps } from '@alifd/next/types/table' 14 | import { SearchProps } from '@alifd/next/types/search' 15 | import { useFilterOptions } from './useFilterOptions' 16 | import { useFlatOptions } from './useFlatOptions' 17 | import { useSize } from './useSize' 18 | import { useTitleAddon } from './useTitleAddon' 19 | import { useCheckSlackly, getIndeterminate } from './useCheckSlackly' 20 | import { getUISelected, getOutputData } from './utils' 21 | import { usePrefixCls } from '../__builtins__' 22 | 23 | interface ObservableColumnSource { 24 | field: GeneralField 25 | columnProps: ColumnProps 26 | schema: Schema 27 | display: FieldDisplayTypes 28 | name: string 29 | } 30 | 31 | type IFilterOption = boolean | ((option: any, keyword: string) => boolean) 32 | 33 | type IFilterSort = (optionA: any, optionB: any) => number 34 | 35 | export interface ISelectTableColumnProps extends ColumnProps { 36 | key: React.ReactText 37 | } 38 | 39 | export interface ISelectTableProps 40 | extends Omit<TableProps, 'primaryKey' | 'onChange'> { 41 | mode?: 'multiple' | 'single' 42 | dataSource?: any[] 43 | optionAsValue?: boolean 44 | valueType?: 'all' | 'parent' | 'child' | 'path' 45 | showSearch?: boolean 46 | searchProps?: SearchProps 47 | primaryKey?: string | ((record: any) => string) 48 | filterOption?: IFilterOption 49 | filterSort?: IFilterSort 50 | onSearch?: (keyword: string) => void 51 | onChange?: (value: any, options: any) => void 52 | value?: any 53 | rowSelection?: TableProps['rowSelection'] & { 54 | checkStrictly?: boolean 55 | } 56 | } 57 | 58 | type ComposedSelectTable = React.FC< 59 | React.PropsWithChildren<ISelectTableProps> 60 | > & { 61 | Column?: React.FC<React.PropsWithChildren<ISelectTableColumnProps>> 62 | } 63 | 64 | const isColumnComponent = (schema: Schema) => { 65 | return schema['x-component']?.indexOf('Column') > -1 66 | } 67 | 68 | const useSources = () => { 69 | const arrayField = useField() 70 | const schema = useFieldSchema() 71 | const parseSources = (schema: Schema): ObservableColumnSource[] => { 72 | if (isColumnComponent(schema)) { 73 | if (!schema['x-component-props']?.['dataIndex'] && !schema['name']) 74 | return [] 75 | const name = schema['x-component-props']?.['dataIndex'] || schema['name'] 76 | const field = arrayField.query(arrayField.address.concat(name)).take() 77 | const columnProps = 78 | field?.component?.[1] || schema['x-component-props'] || {} 79 | const display = field?.display || schema['x-display'] 80 | return [ 81 | { 82 | name, 83 | display, 84 | field, 85 | schema, 86 | columnProps: { 87 | title: field?.title || columnProps.title, 88 | ...columnProps, 89 | }, 90 | }, 91 | ] 92 | } else if (schema.properties) { 93 | return schema.reduceProperties((buf, schema) => { 94 | return buf.concat(parseSources(schema)) 95 | }, []) 96 | } 97 | } 98 | 99 | const parseArrayItems = (schema: Schema['items']) => { 100 | if (!schema) return [] 101 | const sources: ObservableColumnSource[] = [] 102 | const items = isArr(schema) ? schema : [schema] 103 | return items.reduce((columns, schema) => { 104 | const item = parseSources(schema) 105 | if (item) { 106 | return columns.concat(item) 107 | } 108 | return columns 109 | }, sources) 110 | } 111 | 112 | const validSchema = ( 113 | schema?.type === 'array' && schema?.items ? schema.items : schema 114 | ) as Schema 115 | 116 | return parseArrayItems(validSchema) 117 | } 118 | 119 | const useColumns = ( 120 | sources: ObservableColumnSource[] 121 | ): TableProps['columns'] => { 122 | return sources.reduce((buf, { name, columnProps, schema, display }, key) => { 123 | if (display !== 'visible') return buf 124 | if (!isColumnComponent(schema)) return buf 125 | return buf.concat({ 126 | ...columnProps, 127 | key, 128 | dataIndex: name, 129 | }) 130 | }, []) 131 | } 132 | 133 | const addPrimaryKey = (dataSource, rowKey, primaryKey) => 134 | dataSource.map((item) => { 135 | const children = isArr(item.children) 136 | ? addPrimaryKey(item.children, rowKey, primaryKey) 137 | : {} 138 | return { 139 | ...item, 140 | ...children, 141 | [primaryKey]: rowKey(item), 142 | } 143 | }) 144 | 145 | export const SelectTable: ComposedSelectTable = observer((props) => { 146 | const { 147 | mode = 'multiple', 148 | dataSource: propsDataSource, 149 | optionAsValue, 150 | valueType = 'all', 151 | showSearch = false, 152 | filterOption, 153 | filterSort, 154 | onSearch, 155 | searchProps, 156 | className, 157 | value, 158 | onChange, 159 | rowSelection, 160 | primaryKey: rowKey = 'key', 161 | ...otherTableProps 162 | } = props 163 | const prefixCls = usePrefixCls('formily-select-table', props) 164 | const [searchValue, setSearchValue] = useState<string>() 165 | const field = useField() as any 166 | const loading = isBool(props.loading) ? props.loading : field.loading 167 | const disabled = field.disabled 168 | const readOnly = field.readOnly 169 | const readPretty = field.readPretty 170 | const { searchSize, tableSize } = useSize( 171 | field.decoratorProps?.size, 172 | searchProps?.size, 173 | props?.size 174 | ) 175 | const primaryKey = isFn(rowKey) ? '__formily_key__' : rowKey 176 | const sources = useSources() 177 | const columns = useColumns(sources) 178 | 179 | // dataSource 180 | let dataSource = isArr(propsDataSource) ? propsDataSource : field.dataSource 181 | dataSource = isFn(rowKey) 182 | ? addPrimaryKey(dataSource, rowKey, primaryKey) 183 | : dataSource 184 | 185 | // Filter dataSource By Search 186 | const filteredDataSource = useFilterOptions( 187 | dataSource, 188 | searchValue, 189 | filterOption, 190 | rowSelection?.checkStrictly 191 | ) 192 | 193 | // Order dataSource By filterSort 194 | const orderedFilteredDataSource = useMemo(() => { 195 | if (!filterSort) { 196 | return filteredDataSource 197 | } 198 | return [...filteredDataSource].sort((a, b) => filterSort(a, b)) 199 | }, [filteredDataSource, filterSort]) 200 | 201 | const flatDataSource = useFlatOptions(dataSource) 202 | const flatFilteredDataSource = useFlatOptions(filteredDataSource) 203 | 204 | // 分页或异步查询时,dataSource会丢失已选数据,配置optionAsValue则无法获取已选数据,需要进行合并 205 | const getWholeDataSource = () => { 206 | if (optionAsValue && mode === 'multiple' && value?.length) { 207 | const map = new Map() 208 | const arr = [...flatDataSource, ...value] 209 | arr.forEach((item) => { 210 | if (!map.has(item[primaryKey])) { 211 | map.set(item[primaryKey], item) 212 | } 213 | }) 214 | return [...map.values()] 215 | } 216 | return flatDataSource 217 | } 218 | 219 | // selected keys for Table UI 220 | const selected = getUISelected( 221 | value, 222 | flatDataSource, 223 | primaryKey, 224 | valueType, 225 | optionAsValue, 226 | mode, 227 | rowSelection?.checkStrictly, 228 | rowKey 229 | ) 230 | 231 | // readPretty Value 232 | const readPrettyDataSource = useFilterOptions( 233 | orderedFilteredDataSource, 234 | selected, 235 | (value, item) => value.includes(item[primaryKey]) 236 | ) 237 | 238 | const onInnerSearch = (searchText) => { 239 | const formatted = (searchText || '').trim() 240 | setSearchValue(searchText) 241 | onSearch?.(formatted) 242 | } 243 | 244 | const onInnerChange = (selectedRowKeys: any[]) => { 245 | if (readOnly) { 246 | return 247 | } 248 | // 筛选后onChange默认的records数据不完整,此处需使用完整数据过滤 249 | const wholeRecords = getWholeDataSource().filter((item) => 250 | selectedRowKeys.includes(item?.[primaryKey]) 251 | ) 252 | 253 | const { outputValue, outputOptions } = getOutputData( 254 | selectedRowKeys, 255 | wholeRecords, 256 | dataSource, 257 | primaryKey, 258 | valueType, 259 | optionAsValue, 260 | mode, 261 | rowSelection?.checkStrictly 262 | ) 263 | 264 | onChange?.(outputValue, outputOptions) 265 | } 266 | 267 | const onRowClick = (record) => { 268 | if (readPretty || disabled || readOnly || record?.disabled) { 269 | return 270 | } 271 | const selectedRowKey = record?.[primaryKey] 272 | const isSelected = selected?.includes(selectedRowKey) 273 | let selectedRowKeys = [] 274 | if (mode === 'single') { 275 | selectedRowKeys = [selectedRowKey] 276 | } else { 277 | if (isSelected) { 278 | selectedRowKeys = selected.filter((item) => item !== selectedRowKey) 279 | } else { 280 | selectedRowKeys = [...selected, selectedRowKey] 281 | } 282 | } 283 | if (rowSelection?.checkStrictly !== false) { 284 | onInnerChange(selectedRowKeys) 285 | } else { 286 | onSlacklyChange(selectedRowKeys) 287 | } 288 | } 289 | 290 | // TreeData SlacklyChange 291 | const onSlacklyChange = (currentSelected: any[]) => { 292 | let { selectedRowKeys } = useCheckSlackly( 293 | currentSelected, 294 | selected, 295 | flatDataSource, 296 | flatFilteredDataSource, 297 | primaryKey, 298 | rowSelection?.checkStrictly 299 | ) 300 | onInnerChange(selectedRowKeys) 301 | } 302 | 303 | // Table All Checkbox 304 | const titleAddon = useTitleAddon( 305 | selected, 306 | flatDataSource, 307 | flatFilteredDataSource, 308 | primaryKey, 309 | mode, 310 | disabled, 311 | readOnly, 312 | rowSelection?.checkStrictly, 313 | onInnerChange 314 | ) 315 | 316 | return ( 317 | <div className={prefixCls}> 318 | {showSearch ? ( 319 | <Search 320 | {...searchProps} 321 | className={cls(`${prefixCls}-search`, searchProps?.className)} 322 | style={{ width: '100%', ...searchProps?.style }} 323 | onSearch={onInnerSearch} 324 | onChange={onInnerSearch} 325 | disabled={disabled} 326 | readOnly={readOnly} 327 | size={searchSize} 328 | buttonProps={{ ...searchProps?.buttonProps, loading }} // fusion 329 | /> 330 | ) : null} 331 | <Table 332 | {...otherTableProps} 333 | className={cls(`${prefixCls}-table`, className)} 334 | dataSource={ 335 | readPretty ? readPrettyDataSource : orderedFilteredDataSource 336 | } 337 | rowSelection={ 338 | readPretty 339 | ? undefined 340 | : { 341 | ...rowSelection, 342 | ...titleAddon, 343 | getProps: (record, index) => ({ 344 | ...(rowSelection?.getProps?.(record, index) as any), 345 | ...(rowSelection?.checkStrictly !== false 346 | ? {} 347 | : { 348 | indeterminate: getIndeterminate( 349 | record, 350 | flatDataSource, 351 | selected, 352 | primaryKey 353 | ), 354 | }), // 父子关联模式indeterminate值 355 | disabled: disabled || record?.disabled, 356 | }), // fusion 357 | selectedRowKeys: selected, 358 | onChange: 359 | rowSelection?.checkStrictly !== false 360 | ? onInnerChange 361 | : onSlacklyChange, 362 | mode, 363 | } 364 | } 365 | columns={props.columns || columns} 366 | primaryKey={primaryKey} 367 | loading={loading} 368 | size={tableSize} 369 | onRowClick={(record, index, e) => { 370 | // fusion 371 | onRowClick(record) 372 | props.onRowClick?.(record, index, e) 373 | }} 374 | > 375 | {''} 376 | </Table> 377 | {sources.map((column, key) => { 378 | //专门用来承接对Column的状态管理 379 | if (!isColumnComponent(column.schema)) return 380 | return React.createElement(RecursionField, { 381 | name: column.name, 382 | schema: column.schema, 383 | onlyRenderSelf: true, 384 | key, 385 | }) 386 | })} 387 | </div> 388 | ) 389 | }) 390 | 391 | const TableColumn: React.FC< 392 | React.PropsWithChildren<ISelectTableColumnProps> 393 | > = () => <></> 394 | 395 | SelectTable.Column = TableColumn 396 | 397 | export default SelectTable 398 | ``` -------------------------------------------------------------------------------- /packages/element/src/array-base/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ArrayField } from '@formily/core' 2 | import { clone, isValid, uid } from '@formily/shared' 3 | import { 4 | ExpressionScope, 5 | Fragment, 6 | h, 7 | useField, 8 | useFieldSchema, 9 | } from '@formily/vue' 10 | import { 11 | defineComponent, 12 | inject, 13 | InjectionKey, 14 | onBeforeUnmount, 15 | PropType, 16 | provide, 17 | Ref, 18 | ref, 19 | toRefs, 20 | } from 'vue-demi' 21 | import { stylePrefix } from '../__builtins__/configs' 22 | 23 | import type { Schema } from '@formily/json-schema' 24 | import type { Button as ButtonProps } from 'element-ui' 25 | import { Button } from 'element-ui' 26 | import { HandleDirective } from 'vue-slicksort' 27 | import { composeExport } from '../__builtins__/shared' 28 | 29 | export interface IArrayBaseAdditionProps extends ButtonProps { 30 | title?: string 31 | method?: 'push' | 'unshift' 32 | defaultValue?: any 33 | } 34 | 35 | export type ArrayBaseMixins = { 36 | Addition?: typeof ArrayBaseAddition 37 | Remove?: typeof ArrayBaseRemove 38 | MoveUp?: typeof ArrayBaseMoveUp 39 | MoveDown?: typeof ArrayBaseMoveDown 40 | SortHandle?: typeof ArrayBaseSortHandle 41 | Index?: typeof ArrayBaseIndex 42 | useArray?: typeof useArray 43 | useIndex?: typeof useIndex 44 | useRecord?: typeof useRecord 45 | } 46 | 47 | export interface IArrayBaseProps { 48 | disabled?: boolean 49 | keyMap?: WeakMap<Object, String> | String[] | null 50 | } 51 | 52 | export interface IArrayBaseItemProps { 53 | index: number 54 | record: any 55 | } 56 | 57 | export interface IArrayBaseContext { 58 | field: Ref<ArrayField> 59 | schema: Ref<Schema> 60 | props: IArrayBaseProps 61 | listeners: { 62 | [key in string]?: Function 63 | } 64 | keyMap?: WeakMap<Object, String> | String[] | null 65 | } 66 | 67 | const ArrayBaseSymbol: InjectionKey<IArrayBaseContext> = 68 | Symbol('ArrayBaseContext') 69 | const ItemSymbol: InjectionKey<IArrayBaseItemProps> = Symbol('ItemContext') 70 | 71 | const useArray = () => { 72 | return inject(ArrayBaseSymbol, null) 73 | } 74 | 75 | const useIndex = (index?: number) => { 76 | const { index: indexRef } = toRefs(inject(ItemSymbol)) 77 | return indexRef ?? ref(index) 78 | } 79 | 80 | const useRecord = (record?: number) => { 81 | const { record: recordRef } = toRefs(inject(ItemSymbol)) 82 | return recordRef ?? ref(record) 83 | } 84 | 85 | const isObjectValue = (schema: Schema) => { 86 | if (Array.isArray(schema?.items)) return isObjectValue(schema.items[0]) 87 | 88 | if (schema?.items?.type === 'array' || schema?.items?.type === 'object') { 89 | return true 90 | } 91 | return false 92 | } 93 | 94 | const useKey = (schema: Schema) => { 95 | const isObject = isObjectValue(schema) 96 | let keyMap: WeakMap<Object, String> | String[] | null = null 97 | 98 | if (isObject) { 99 | keyMap = new WeakMap() 100 | } else { 101 | keyMap = [] 102 | } 103 | 104 | onBeforeUnmount(() => { 105 | keyMap = null 106 | }) 107 | 108 | return { 109 | keyMap, 110 | getKey: (record: any, index?: number) => { 111 | if (keyMap instanceof WeakMap) { 112 | if (!keyMap.has(record)) { 113 | keyMap.set(record, uid()) 114 | } 115 | return `${keyMap.get(record)}-${index}` 116 | } 117 | 118 | if (!keyMap[index]) { 119 | keyMap[index] = uid() 120 | } 121 | 122 | return `${keyMap[index]}-${index}` 123 | }, 124 | } 125 | } 126 | 127 | const getDefaultValue = (defaultValue: any, schema: Schema): any => { 128 | if (isValid(defaultValue)) return clone(defaultValue) 129 | if (Array.isArray(schema?.items)) 130 | return getDefaultValue(defaultValue, schema.items[0]) 131 | if (schema?.items?.type === 'array') return [] 132 | if (schema?.items?.type === 'boolean') return true 133 | if (schema?.items?.type === 'date') return '' 134 | if (schema?.items?.type === 'datetime') return '' 135 | if (schema?.items?.type === 'number') return 0 136 | if (schema?.items?.type === 'object') return {} 137 | if (schema?.items?.type === 'string') return '' 138 | return null 139 | } 140 | 141 | const ArrayBaseInner = defineComponent<IArrayBaseProps>({ 142 | name: 'ArrayBase', 143 | props: { 144 | disabled: { 145 | type: Boolean, 146 | default: false, 147 | }, 148 | keyMap: { 149 | type: [WeakMap, Array] as PropType<WeakMap<Object, String> | String[]>, 150 | }, 151 | }, 152 | setup(props, { slots, listeners }) { 153 | const field = useField<ArrayField>() 154 | const schema = useFieldSchema() 155 | 156 | provide(ArrayBaseSymbol, { 157 | field, 158 | schema, 159 | props, 160 | listeners, 161 | keyMap: props.keyMap, 162 | }) 163 | return () => { 164 | return h(Fragment, {}, slots) 165 | } 166 | }, 167 | }) 168 | 169 | const ArrayBaseItem = defineComponent({ 170 | name: 'ArrayBaseItem', 171 | props: ['index', 'record'], 172 | setup(props: IArrayBaseItemProps, { slots }) { 173 | provide(ItemSymbol, props) 174 | return () => { 175 | return h( 176 | ExpressionScope, 177 | { props: { value: { $record: props.record, $index: props.index } } }, 178 | { 179 | default: () => h(Fragment, {}, slots), 180 | } 181 | ) 182 | } 183 | }, 184 | }) 185 | 186 | const ArrayBaseSortHandle = defineComponent({ 187 | name: 'ArrayBaseSortHandle', 188 | props: ['index'], 189 | directives: { 190 | handle: HandleDirective, 191 | }, 192 | setup(props, { attrs }) { 193 | const array = useArray() 194 | const prefixCls = `${stylePrefix}-array-base` 195 | 196 | return () => { 197 | if (!array) return null 198 | if (array.field.value?.pattern !== 'editable') return null 199 | 200 | return h( 201 | Button, 202 | { 203 | directives: [{ name: 'handle' }], 204 | class: [`${prefixCls}-sort-handle`], 205 | attrs: { 206 | size: 'mini', 207 | type: 'text', 208 | icon: 'el-icon-rank', 209 | ...attrs, 210 | }, 211 | }, 212 | {} 213 | ) 214 | } 215 | }, 216 | }) 217 | 218 | const ArrayBaseIndex = defineComponent({ 219 | name: 'ArrayBaseIndex', 220 | setup(props, { attrs }) { 221 | const index = useIndex() 222 | const prefixCls = `${stylePrefix}-array-base` 223 | return () => { 224 | return h( 225 | 'span', 226 | { 227 | class: `${prefixCls}-index`, 228 | attrs, 229 | }, 230 | { 231 | default: () => [`#${index.value + 1}.`], 232 | } 233 | ) 234 | } 235 | }, 236 | }) 237 | 238 | const ArrayBaseAddition = defineComponent({ 239 | name: 'ArrayBaseAddition', 240 | props: ['title', 'method', 'defaultValue'], 241 | setup(props: IArrayBaseAdditionProps, { listeners }) { 242 | const self = useField() 243 | const array = useArray() 244 | const prefixCls = `${stylePrefix}-array-base` 245 | return () => { 246 | if (!array) return null 247 | if (array?.field.value.pattern !== 'editable') return null 248 | return h( 249 | Button, 250 | { 251 | class: `${prefixCls}-addition`, 252 | attrs: { 253 | type: 'ghost', 254 | icon: 'qax-icon-Alone-Plus', 255 | ...props, 256 | }, 257 | on: { 258 | ...listeners, 259 | click: (e) => { 260 | if (array.props?.disabled) return 261 | const defaultValue = getDefaultValue( 262 | props.defaultValue, 263 | array?.schema.value 264 | ) 265 | if (props.method === 'unshift') { 266 | array?.field?.value.unshift(defaultValue) 267 | array.listeners?.add?.(0) 268 | } else { 269 | array?.field?.value.push(defaultValue) 270 | array.listeners?.add?.(array?.field?.value?.value?.length - 1) 271 | } 272 | if (listeners.click) { 273 | listeners.click(e) 274 | } 275 | }, 276 | }, 277 | }, 278 | { 279 | default: () => [self.value.title || props.title], 280 | } 281 | ) 282 | } 283 | }, 284 | }) 285 | 286 | const ArrayBaseRemove = defineComponent< 287 | ButtonProps & { title?: string; index?: number } 288 | >({ 289 | name: 'ArrayBaseRemove', 290 | props: ['title', 'index'], 291 | setup(props, { attrs, listeners }) { 292 | const indexRef = useIndex(props.index) 293 | const base = useArray() 294 | const prefixCls = `${stylePrefix}-array-base` 295 | return () => { 296 | if (base?.field.value.pattern !== 'editable') return null 297 | return h( 298 | Button, 299 | { 300 | class: `${prefixCls}-remove`, 301 | attrs: { 302 | type: 'text', 303 | size: 'mini', 304 | icon: 'el-icon-delete', 305 | ...attrs, 306 | }, 307 | on: { 308 | ...listeners, 309 | click: (e: MouseEvent) => { 310 | e.stopPropagation() 311 | if (Array.isArray(base?.keyMap)) { 312 | base?.keyMap?.splice(indexRef.value, 1) 313 | } 314 | 315 | base?.field.value.remove(indexRef.value as number) 316 | base?.listeners?.remove?.(indexRef.value as number) 317 | 318 | if (listeners.click) { 319 | listeners.click(e) 320 | } 321 | }, 322 | }, 323 | }, 324 | { 325 | default: () => [props.title], 326 | } 327 | ) 328 | } 329 | }, 330 | }) 331 | 332 | const ArrayBaseMoveDown = defineComponent< 333 | ButtonProps & { title?: string; index?: number } 334 | >({ 335 | name: 'ArrayBaseMoveDown', 336 | props: ['title', 'index'], 337 | setup(props, { attrs, listeners }) { 338 | const indexRef = useIndex(props.index) 339 | const base = useArray() 340 | const prefixCls = `${stylePrefix}-array-base` 341 | return () => { 342 | if (base?.field.value.pattern !== 'editable') return null 343 | return h( 344 | Button, 345 | { 346 | class: `${prefixCls}-move-down`, 347 | attrs: { 348 | size: 'mini', 349 | type: 'text', 350 | icon: 'el-icon-arrow-down', 351 | ...attrs, 352 | }, 353 | on: { 354 | ...listeners, 355 | click: (e: MouseEvent) => { 356 | e.stopPropagation() 357 | if (Array.isArray(base?.keyMap)) { 358 | base.keyMap.splice( 359 | indexRef.value + 1, 360 | 0, 361 | base.keyMap.splice(indexRef.value, 1)[0] 362 | ) 363 | } 364 | 365 | base?.field.value.moveDown(indexRef.value as number) 366 | base?.listeners?.moveDown?.(indexRef.value as number) 367 | 368 | if (listeners.click) { 369 | listeners.click(e) 370 | } 371 | }, 372 | }, 373 | }, 374 | { 375 | default: () => [props.title], 376 | } 377 | ) 378 | } 379 | }, 380 | }) 381 | 382 | const ArrayBaseMoveUp = defineComponent< 383 | ButtonProps & { title?: string; index?: number } 384 | >({ 385 | name: 'ArrayBaseMoveUp', 386 | props: ['title', 'index'], 387 | setup(props, { attrs, listeners }) { 388 | const indexRef = useIndex(props.index) 389 | const base = useArray() 390 | const prefixCls = `${stylePrefix}-array-base` 391 | return () => { 392 | if (base?.field.value.pattern !== 'editable') return null 393 | return h( 394 | Button, 395 | { 396 | class: `${prefixCls}-move-up`, 397 | attrs: { 398 | size: 'mini', 399 | type: 'text', 400 | icon: 'el-icon-arrow-up', 401 | ...attrs, 402 | }, 403 | on: { 404 | ...listeners, 405 | click: (e: MouseEvent) => { 406 | e.stopPropagation() 407 | if (Array.isArray(base?.keyMap)) { 408 | base.keyMap.splice( 409 | indexRef.value - 1, 410 | 0, 411 | base.keyMap.splice(indexRef.value, 1)[0] 412 | ) 413 | } 414 | 415 | base?.field.value.moveUp(indexRef.value as number) 416 | base?.listeners?.moveUp?.(indexRef.value as number) 417 | 418 | if (listeners.click) { 419 | listeners.click(e) 420 | } 421 | }, 422 | }, 423 | }, 424 | { 425 | default: () => [props.title], 426 | } 427 | ) 428 | } 429 | }, 430 | }) 431 | 432 | export const ArrayBase = composeExport(ArrayBaseInner, { 433 | Index: ArrayBaseIndex, 434 | Item: ArrayBaseItem, 435 | SortHandle: ArrayBaseSortHandle, 436 | Addition: ArrayBaseAddition, 437 | Remove: ArrayBaseRemove, 438 | MoveDown: ArrayBaseMoveDown, 439 | MoveUp: ArrayBaseMoveUp, 440 | useArray: useArray, 441 | useIndex: useIndex, 442 | useKey: useKey, 443 | useRecord: useRecord, 444 | }) 445 | 446 | export default ArrayBase 447 | ``` -------------------------------------------------------------------------------- /packages/path/src/parser.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Tokenizer } from './tokenizer' 2 | import { 3 | Token, 4 | nameTok, 5 | colonTok, 6 | dotTok, 7 | starTok, 8 | bangTok, 9 | bracketLTok, 10 | bracketRTok, 11 | braceLTok, 12 | braceRTok, 13 | bracketDLTok, 14 | parenLTok, 15 | parenRTok, 16 | commaTok, 17 | expandTok, 18 | eofTok, 19 | dbStarTok, 20 | } from './tokens' 21 | import { bracketArrayContext, destructorContext } from './contexts' 22 | import { 23 | IdentifierNode, 24 | ExpandOperatorNode, 25 | WildcardOperatorNode, 26 | RangeExpressionNode, 27 | GroupExpressionNode, 28 | DotOperatorNode, 29 | IgnoreExpressionNode, 30 | DestructorExpressionNode, 31 | ObjectPatternNode, 32 | ObjectPatternPropertyNode, 33 | ArrayPatternNode, 34 | Node, 35 | Segments, 36 | } from './types' 37 | import { parseDestructorRules, setDestructor } from './destructor' 38 | import { isNumberLike } from './shared' 39 | import { Path } from './index' 40 | 41 | const createTreeBySegments = (segments: Segments = [], afterNode?: Node) => { 42 | const segLen = segments.length 43 | const build = (start = 0) => { 44 | const after = start < segLen - 1 ? build(start + 1) : afterNode 45 | const dot = after && { 46 | type: 'DotOperator', 47 | after, 48 | } 49 | return { 50 | type: 'Identifier', 51 | value: segments[start], 52 | after: dot, 53 | } 54 | } 55 | return build() 56 | } 57 | 58 | const calculate = ( 59 | a: string | number, 60 | b: string | number, 61 | operator: string 62 | ) => { 63 | if (isNumberLike(a) && isNumberLike(b)) { 64 | if (operator === '+') return String(Number(a) + Number(b)) 65 | if (operator === '-') return String(Number(a) - Number(b)) 66 | if (operator === '*') return String(Number(a) * Number(b)) 67 | if (operator === '/') return String(Number(a) / Number(b)) 68 | } else { 69 | if (operator === '+') return String(a) + String(b) 70 | if (operator === '-') return 'NaN' 71 | if (operator === '*') return 'NaN' 72 | if (operator === '/') return 'NaN' 73 | } 74 | return String(Number(b)) 75 | } 76 | 77 | export class Parser extends Tokenizer { 78 | public isMatchPattern = false 79 | 80 | public isWildMatchPattern = false 81 | 82 | public haveExcludePattern = false 83 | 84 | public haveRelativePattern = false 85 | 86 | public base: Path 87 | 88 | public relative: string | number 89 | 90 | public data: { 91 | segments: Segments 92 | tree?: Node 93 | } 94 | 95 | constructor(input: string, base?: Path) { 96 | super(input) 97 | this.base = base 98 | } 99 | 100 | parse() { 101 | let node: Node 102 | this.data = { 103 | segments: [], 104 | } 105 | if (!this.eat(eofTok)) { 106 | this.next() 107 | node = this.parseAtom(this.state.type) 108 | } 109 | this.data.tree = node 110 | 111 | return node 112 | } 113 | 114 | append(parent: Node, node: Node) { 115 | if (parent && node) { 116 | parent.after = node 117 | } 118 | } 119 | 120 | parseAtom(type: Token): Node { 121 | switch (type) { 122 | case braceLTok: 123 | case bracketLTok: 124 | if (this.includesContext(destructorContext)) { 125 | if (type === braceLTok) { 126 | return this.parseObjectPattern() 127 | } else { 128 | return this.parseArrayPattern() 129 | } 130 | } 131 | return this.parseDestructorExpression() 132 | case nameTok: 133 | return this.parseIdentifier() 134 | case expandTok: 135 | return this.parseExpandOperator() 136 | case dbStarTok: 137 | case starTok: 138 | return this.parseWildcardOperator() 139 | case bracketDLTok: 140 | return this.parseIgnoreExpression() 141 | case dotTok: 142 | return this.parseDotOperator() 143 | } 144 | } 145 | 146 | pushSegments(key: string | number) { 147 | this.data.segments.push(key) 148 | } 149 | 150 | parseIdentifier() { 151 | const node: IdentifierNode = { 152 | type: 'Identifier', 153 | value: this.state.value, 154 | } 155 | const hasNotInDestructor = 156 | !this.includesContext(destructorContext) && 157 | !this.isMatchPattern && 158 | !this.isWildMatchPattern 159 | 160 | this.next() 161 | if (this.includesContext(bracketArrayContext)) { 162 | if (this.state.type !== bracketRTok) { 163 | throw this.unexpect() 164 | } else { 165 | this.state.context.pop() 166 | this.next() 167 | } 168 | } else if (hasNotInDestructor) { 169 | this.pushSegments(node.value) 170 | } 171 | if (this.state.type === bracketLTok) { 172 | this.next() 173 | if (this.state.type !== nameTok) { 174 | throw this.unexpect() 175 | } 176 | this.state.context.push(bracketArrayContext) 177 | let isNumberKey = false 178 | if (/^\d+$/.test(this.state.value)) { 179 | isNumberKey = true 180 | } 181 | const value = this.state.value 182 | this.pushSegments(isNumberKey ? Number(value) : value) 183 | const after = this.parseAtom(this.state.type) as IdentifierNode 184 | if (isNumberKey) { 185 | after.arrayIndex = true 186 | } 187 | this.append(node, after) 188 | } else { 189 | this.append(node, this.parseAtom(this.state.type)) 190 | } 191 | 192 | return node 193 | } 194 | 195 | parseExpandOperator() { 196 | const node: ExpandOperatorNode = { 197 | type: 'ExpandOperator', 198 | } 199 | 200 | this.isMatchPattern = true 201 | this.isWildMatchPattern = true 202 | this.data.segments = [] 203 | 204 | this.next() 205 | 206 | this.append(node, this.parseAtom(this.state.type)) 207 | 208 | return node 209 | } 210 | 211 | parseWildcardOperator(): WildcardOperatorNode { 212 | const node: WildcardOperatorNode = { 213 | type: 'WildcardOperator', 214 | } 215 | 216 | if (this.state.type === dbStarTok) { 217 | node.optional = true 218 | } 219 | 220 | this.isMatchPattern = true 221 | this.isWildMatchPattern = true 222 | this.data.segments = [] 223 | 224 | this.next() 225 | 226 | if (this.state.type === parenLTok) { 227 | node.filter = this.parseGroupExpression(node) 228 | } else if (this.state.type === bracketLTok) { 229 | node.filter = this.parseRangeExpression(node) 230 | } 231 | 232 | this.append(node, this.parseAtom(this.state.type)) 233 | 234 | return node 235 | } 236 | 237 | parseDestructorExpression(): DestructorExpressionNode { 238 | const node: DestructorExpressionNode = { 239 | type: 'DestructorExpression', 240 | } 241 | this.state.context.push(destructorContext) 242 | const startPos = this.state.pos - 1 243 | node.value = 244 | this.state.type === braceLTok 245 | ? this.parseObjectPattern() 246 | : this.parseArrayPattern() 247 | const endPos = this.state.pos 248 | this.state.context.pop() 249 | node.source = this.input 250 | .substring(startPos, endPos) 251 | .replace( 252 | /\[\s*([\+\-\*\/])?\s*([^,\]\s]*)\s*\]/, 253 | (match, operator, target) => { 254 | if (this.relative !== undefined) { 255 | if (operator) { 256 | if (target) { 257 | return calculate(this.relative, target, operator) 258 | } else { 259 | return calculate(this.relative, 1, operator) 260 | } 261 | } else { 262 | if (target) { 263 | return calculate(this.relative, target, '+') 264 | } else { 265 | return String(this.relative) 266 | } 267 | } 268 | } 269 | return match 270 | } 271 | ) 272 | .replace(/\s*\.\s*/g, '') 273 | .replace(/\s*/g, '') 274 | if (this.relative === undefined) { 275 | setDestructor(node.source, parseDestructorRules(node)) 276 | } 277 | this.relative = undefined 278 | this.pushSegments(node.source) 279 | this.next() 280 | this.append(node, this.parseAtom(this.state.type)) 281 | return node 282 | } 283 | 284 | parseArrayPattern(): ArrayPatternNode { 285 | const node: ArrayPatternNode = { 286 | type: 'ArrayPattern', 287 | elements: [], 288 | } 289 | this.next() 290 | node.elements = this.parseArrayPatternElements() 291 | return node 292 | } 293 | 294 | parseArrayPatternElements() { 295 | const nodes = [] 296 | while (this.state.type !== bracketRTok && this.state.type !== eofTok) { 297 | nodes.push(this.parseAtom(this.state.type)) 298 | if (this.state.type === bracketRTok) { 299 | if (this.includesContext(destructorContext)) { 300 | this.next() 301 | } 302 | return nodes 303 | } 304 | this.next() 305 | } 306 | return nodes 307 | } 308 | 309 | parseObjectPattern(): ObjectPatternNode { 310 | const node: ObjectPatternNode = { 311 | type: 'ObjectPattern', 312 | properties: [], 313 | } 314 | this.next() 315 | node.properties = this.parseObjectProperties() 316 | return node 317 | } 318 | 319 | parseObjectProperties(): ObjectPatternPropertyNode[] { 320 | const nodes = [] 321 | while (this.state.type !== braceRTok && this.state.type !== eofTok) { 322 | const node: ObjectPatternPropertyNode = { 323 | type: 'ObjectPatternProperty', 324 | key: this.parseAtom(this.state.type) as IdentifierNode, 325 | } 326 | nodes.push(node) 327 | if (this.state.type === colonTok) { 328 | this.next() 329 | node.value = this.parseAtom(this.state.type) as 330 | | IdentifierNode 331 | | ObjectPatternNode[] 332 | | ArrayPatternNode[] 333 | } 334 | if (this.state.type === braceRTok) { 335 | if (this.includesContext(destructorContext)) { 336 | this.next() 337 | } 338 | return nodes 339 | } 340 | this.next() 341 | } 342 | return nodes 343 | } 344 | 345 | parseDotOperator(): Node { 346 | const node: DotOperatorNode = { 347 | type: 'DotOperator', 348 | } 349 | 350 | const prevToken = this.type_ 351 | if (!prevToken && this.base) { 352 | if (this.base.isMatchPattern) { 353 | throw new Error('Base path must be an absolute path.') 354 | } 355 | this.data.segments = this.base.toArr() 356 | while (this.state.type === dotTok) { 357 | this.relative = this.data.segments.pop() 358 | this.haveRelativePattern = true 359 | this.next() 360 | } 361 | return createTreeBySegments( 362 | this.data.segments.slice(), 363 | this.parseAtom(this.state.type) 364 | ) 365 | } else { 366 | this.next() 367 | } 368 | 369 | this.append(node, this.parseAtom(this.state.type)) 370 | 371 | return node 372 | } 373 | 374 | parseIgnoreExpression() { 375 | this.next() 376 | 377 | const value = String(this.state.value).replace(/\s*/g, '') 378 | 379 | const node: IgnoreExpressionNode = { 380 | type: 'IgnoreExpression', 381 | value: value, 382 | } 383 | 384 | this.pushSegments(value) 385 | 386 | this.next() 387 | 388 | this.append(node, this.parseAtom(this.state.type)) 389 | 390 | this.next() 391 | 392 | return node 393 | } 394 | 395 | parseGroupExpression(parent: Node) { 396 | const node: GroupExpressionNode = { 397 | type: 'GroupExpression', 398 | value: [], 399 | } 400 | 401 | this.isMatchPattern = true 402 | this.data.segments = [] 403 | 404 | this.next() 405 | 406 | loop: while (true) { 407 | switch (this.state.type) { 408 | case commaTok: 409 | this.next() 410 | break 411 | case bangTok: 412 | node.isExclude = true 413 | this.haveExcludePattern = true 414 | this.next() 415 | break 416 | case eofTok: 417 | break loop 418 | case parenRTok: 419 | break loop 420 | default: 421 | node.value.push(this.parseAtom(this.state.type)) 422 | } 423 | } 424 | 425 | this.next() 426 | 427 | this.append(parent, this.parseAtom(this.state.type)) 428 | 429 | return node 430 | } 431 | 432 | parseRangeExpression(parent: Node) { 433 | const node: RangeExpressionNode = { 434 | type: 'RangeExpression', 435 | } 436 | 437 | this.next() 438 | 439 | this.isMatchPattern = true 440 | this.data.segments = [] 441 | 442 | let start = false, 443 | hasColon = false 444 | 445 | loop: while (true) { 446 | switch (this.state.type) { 447 | case colonTok: 448 | hasColon = true 449 | start = true 450 | this.next() 451 | break 452 | case bracketRTok: 453 | if (!hasColon && !node.end) { 454 | node.end = node.start 455 | } 456 | break loop 457 | case commaTok: 458 | // never reach 459 | throw this.unexpect() 460 | case eofTok: 461 | // never reach 462 | break loop 463 | default: 464 | if (!start) { 465 | node.start = this.parseAtom(this.state.type) as IdentifierNode 466 | } else { 467 | node.end = this.parseAtom(this.state.type) as IdentifierNode 468 | } 469 | } 470 | } 471 | 472 | this.next() 473 | 474 | this.append(parent, this.parseAtom(this.state.type)) 475 | 476 | return node 477 | } 478 | } 479 | ``` -------------------------------------------------------------------------------- /packages/element/src/form-item/style.scss: -------------------------------------------------------------------------------- ```scss 1 | @use 'sass:math'; 2 | @import '../__builtins__/styles/common.scss'; 3 | @import './var.scss'; 4 | @import './grid.scss'; 5 | @import './animation.scss'; 6 | 7 | .#{$form-item-prefix} { 8 | display: flex; 9 | margin-bottom: $--form-item-margin-bottom; 10 | position: relative; 11 | line-height: $--form-item-medium-line-height; 12 | font-size: $--form-font-size; 13 | 14 | &-label * { 15 | line-height: $--form-item-medium-line-height; 16 | } 17 | 18 | &-label-content { 19 | min-height: $--form-item-medium-line-height; 20 | } 21 | 22 | &-content-component { 23 | line-height: $--form-item-medium-line-height; 24 | } 25 | 26 | .#{$namespace}-input, 27 | .#{$namespace}-input-number, 28 | .#{$namespace}-input-number.is-controls-right, 29 | .#{$namespace}-select, 30 | .#{$namespace}-cascader, 31 | .#{$namespace}-date-editor--daterange, 32 | .#{$namespace}-date-editor--timerange, 33 | .#{$namespace}-date-editor--datetimerange, 34 | .#{$namespace}-date-editor.#{$namespace}-input, 35 | .#{$namespace}-date-editor.#{$namespace}-input__inner, 36 | .#{$namespace}-tree-select { 37 | width: 100%; 38 | } 39 | 40 | .#{$namespace}-input-group { 41 | vertical-align: top; 42 | } 43 | } 44 | 45 | .#{$form-item-prefix}-label { 46 | position: relative; 47 | display: flex; 48 | 49 | &-content { 50 | overflow: hidden; 51 | text-overflow: ellipsis; 52 | white-space: nowrap; 53 | } 54 | 55 | &-tooltip { 56 | cursor: help; 57 | 58 | * { 59 | cursor: help; 60 | } 61 | 62 | label { 63 | border-bottom: 1px dashed currentColor; 64 | } 65 | } 66 | } 67 | 68 | .#{$form-item-prefix}-label label { 69 | color: $--color-text-regular; 70 | } 71 | 72 | .#{$form-item-prefix}-label-align-left { 73 | > .#{$form-item-prefix}-label { 74 | justify-content: flex-start; 75 | } 76 | } 77 | 78 | .#{$form-item-prefix}-label-align-right { 79 | > .#{$form-item-prefix}-label { 80 | justify-content: flex-end; 81 | } 82 | } 83 | 84 | .#{$form-item-prefix}-label-wrap { 85 | .#{$form-item-prefix}-label { 86 | label { 87 | white-space: pre-line; 88 | } 89 | } 90 | } 91 | 92 | .#{$form-item-prefix}-feedback-layout-terse { 93 | margin-bottom: 8px; 94 | 95 | &.#{$form-item-prefix}-feedback-has-text:not(.#{$form-item-prefix}-inset) { 96 | margin-bottom: 0; 97 | } 98 | } 99 | 100 | .#{$form-item-prefix}-feedback-layout-loose { 101 | margin-bottom: $--form-error-line-height; 102 | 103 | &.#{$form-item-prefix}-feedback-has-text:not(.#{$form-item-prefix}-inset) { 104 | margin-bottom: 0; 105 | } 106 | } 107 | 108 | .#{$form-item-prefix}-feedback-layout-none { 109 | margin-bottom: 0; 110 | 111 | &.#{$form-item-prefix}-feedback-has-text:not(.#{$form-item-prefix}-inset) { 112 | margin-bottom: 0; 113 | } 114 | } 115 | 116 | .#{$form-item-prefix}-control { 117 | width: 100%; 118 | flex: 1; 119 | 120 | .#{$form-item-prefix}-control-content { 121 | display: flex; 122 | 123 | .#{$form-item-prefix}-control-content-component { 124 | width: 100%; 125 | min-height: $--form-item-medium-line-height; 126 | line-height: $--form-item-medium-line-height; 127 | 128 | &-has-feedback-icon { 129 | flex: 1; 130 | position: relative; 131 | display: flex; 132 | align-items: center; 133 | } 134 | } 135 | 136 | .#{$form-item-prefix}-addon-before { 137 | margin-right: 8px; 138 | display: inline-flex; 139 | align-items: center; 140 | min-height: $--form-item-medium-line-height; 141 | flex-shrink: 0; 142 | } 143 | 144 | .#{$form-item-prefix}-addon-after { 145 | margin-left: 8px; 146 | display: inline-flex; 147 | align-items: center; 148 | min-height: $--form-item-medium-line-height; 149 | flex-shrink: 0; 150 | } 151 | } 152 | } 153 | 154 | .#{$form-item-prefix}-size-small { 155 | font-size: $--font-size-extra-small; 156 | 157 | .#{$form-item-prefix}-label * { 158 | line-height: $--form-item-small-line-height; 159 | } 160 | 161 | .#{$form-item-prefix}-label-content { 162 | min-height: $--form-item-small-line-height; 163 | } 164 | 165 | .#{$form-item-prefix}-control-content { 166 | .#{$form-item-prefix}-control-content-component { 167 | line-height: $--form-item-small-line-height; 168 | min-height: $--form-item-small-line-height; 169 | } 170 | } 171 | 172 | .#{$form-item-prefix}-help, 173 | .#{$form-item-prefix}-extra { 174 | min-height: $--form-error-line-height; 175 | } 176 | 177 | .#{$form-item-prefix}-control-content { 178 | min-height: $--form-item-small-line-height; 179 | } 180 | 181 | .#{$form-item-prefix}-label > label { 182 | height: $--form-item-small-line-height; 183 | } 184 | 185 | .#{$namespace}-input { 186 | input { 187 | height: $--form-item-small-line-height; 188 | line-height: $--form-item-small-line-height; 189 | } 190 | } 191 | 192 | .#{$namespace}-input-number { 193 | line-height: $--form-item-small-line-height; 194 | &.is-controls-right { 195 | .#{$namespace}-input-number__increase, 196 | .#{$namespace}-input-number__decrease { 197 | line-height: math.div($--form-item-small-line-height, 2); 198 | height: math.div($--form-item-small-line-height, 2); 199 | font-size: $--font-size-extra-small; 200 | box-sizing: border-box; 201 | } 202 | } 203 | } 204 | } 205 | 206 | .#{$form-item-prefix}-size-large { 207 | font-size: $--font-size-medium; 208 | 209 | .#{$form-item-prefix}-label * { 210 | line-height: $--form-item-large-line-height; 211 | } 212 | 213 | .#{$form-item-prefix}-label-content { 214 | min-height: $--form-item-large-line-height; 215 | } 216 | 217 | .#{$form-item-prefix}-control-content { 218 | .#{$form-item-prefix}-control-content-component { 219 | line-height: $--form-item-large-line-height; 220 | min-height: $--form-item-large-line-height; 221 | } 222 | } 223 | 224 | .#{$form-item-prefix}-help, 225 | .#{$form-item-prefix}-extra { 226 | min-height: $--form-error-line-height; 227 | } 228 | 229 | .#{$form-item-prefix}-control-content { 230 | min-height: $--form-item-large-line-height; 231 | } 232 | 233 | .#{$namespace}-input { 234 | input { 235 | height: $--form-item-large-line-height; 236 | line-height: $--form-item-large-line-height; 237 | } 238 | } 239 | 240 | .#{$namespace}-select { 241 | input { 242 | height: $--form-item-large-line-height !important; 243 | line-height: $--form-item-large-line-height; 244 | } 245 | } 246 | 247 | .#{$namespace}-select__tags .el-tag { 248 | height: $--form-item-large-line-height - 12px; 249 | line-height: $--form-item-large-line-height - 12px; 250 | } 251 | 252 | .#{$namespace}-input-number { 253 | line-height: $--form-item-large-line-height; 254 | &.is-controls-right { 255 | .#{$namespace}-input-number__increase, 256 | .#{$namespace}-input-number__decrease { 257 | line-height: math.div($--form-item-large-line-height, 2) - 1; 258 | font-size: $--font-size-medium; 259 | } 260 | } 261 | } 262 | } 263 | 264 | .#{$form-item-prefix} { 265 | &-layout-vertical { 266 | display: block; 267 | 268 | .#{$form-item-prefix}-label * { 269 | line-height: $--form-item-label-top-line-height; 270 | } 271 | 272 | .#{$form-item-prefix}-label-content { 273 | min-height: $--form-item-label-top-line-height; 274 | } 275 | } 276 | } 277 | 278 | .#{$form-item-prefix}-feedback-layout-popover { 279 | margin-bottom: 8px; 280 | } 281 | 282 | .#{$form-item-prefix}-label-tooltip { 283 | margin-left: 4px; 284 | color: $--color-text-secondary; 285 | display: flex; 286 | align-items: center; 287 | height: $--form-item-medium-line-height; 288 | cursor: pointer; 289 | i { 290 | line-height: 1; 291 | } 292 | } 293 | 294 | .#{$form-item-prefix}-control-align-left { 295 | .#{$form-item-prefix}-control-content { 296 | justify-content: flex-start; 297 | } 298 | } 299 | 300 | .#{$form-item-prefix}-control-align-right { 301 | .#{$form-item-prefix}-control-content { 302 | justify-content: flex-end; 303 | } 304 | } 305 | 306 | .#{$form-item-prefix}-control-wrap { 307 | .#{$form-item-prefix}-control { 308 | white-space: pre-line; 309 | } 310 | } 311 | 312 | .#{$form-item-prefix}-asterisk { 313 | color: $--color-danger; 314 | margin-right: 4px; 315 | display: inline-block; 316 | font-family: SimSun, sans-serif; 317 | } 318 | 319 | .#{$form-item-prefix}-colon { 320 | margin-left: 2px; 321 | margin-right: 8px; 322 | } 323 | 324 | .#{$form-item-prefix}-help, 325 | .#{$form-item-prefix}-extra { 326 | clear: both; 327 | min-height: $--form-error-line-height; 328 | line-height: $--form-error-line-height; 329 | color: $--color-text-secondary; 330 | transition: $--color-transition-base; 331 | padding-top: 0; 332 | } 333 | 334 | .#{$form-item-prefix}-fullness { 335 | > .#{$form-item-prefix}-control { 336 | > .#{$form-item-prefix}-control-content { 337 | > .#{$form-item-prefix}-control-content-component { 338 | > *:first-child { 339 | width: 100%; 340 | } 341 | } 342 | } 343 | } 344 | } 345 | 346 | .#{$form-item-prefix}-control-content-component-has-feedback-icon { 347 | border-radius: $--border-radius-base; 348 | border: $--border-base; 349 | padding-right: 8px; 350 | transition: $--all-transition; 351 | touch-action: manipulation; 352 | outline: none; 353 | 354 | .#{$namespace}-input-number, 355 | .#{$namespace}-date-editor .#{$namespace}-input__inner, 356 | .#{$namespace}-select .#{$namespace}-input__inner, 357 | .#{$namespace}-input .#{$namespace}-input__inner { 358 | border: none !important; 359 | box-shadow: none !important; 360 | } 361 | .#{$namespace}-input-number.is-controls-right .#{$namespace}-input__inner { 362 | padding-right: 40px; 363 | } 364 | .#{$namespace}-input-number.is-controls-right 365 | .#{$namespace}-input-number__increase { 366 | top: 0; 367 | right: 8px; 368 | border-right: $--border-base; 369 | } 370 | .#{$namespace}-input-number.is-controls-right 371 | .#{$namespace}-input-number__decrease { 372 | bottom: 0; 373 | right: 8px; 374 | border-right: $--border-base; 375 | } 376 | } 377 | 378 | .#{$form-item-prefix} { 379 | &:hover { 380 | .#{$form-item-prefix}-control-content-component-has-feedback-icon { 381 | @include hover; 382 | } 383 | } 384 | } 385 | 386 | .#{$form-item-prefix}-active { 387 | .#{$form-item-prefix}-control-content-component-has-feedback-icon { 388 | @include active; 389 | } 390 | } 391 | 392 | .#{$form-item-prefix}-error { 393 | & .#{$namespace}-input__inner, 394 | & .#{$namespace}-textarea__inner { 395 | &, 396 | &.hover { 397 | border-color: $--color-danger; 398 | } 399 | } 400 | 401 | & .#{$namespace}-input__inner, 402 | & .#{$namespace}-textarea__inner { 403 | &:focus { 404 | border-color: $--color-danger; 405 | } 406 | } 407 | 408 | & .#{$namespace}-input-group__append, 409 | & .#{$namespace}-input-group__prepend { 410 | & .#{$namespace}-input__inner { 411 | border-color: transparent; 412 | } 413 | } 414 | .#{$namespace}-input__validateIcon { 415 | color: $--color-danger !important; 416 | } 417 | } 418 | 419 | .#{$form-item-prefix}-error-help, 420 | .#{$form-item-prefix}-warning-help, 421 | .#{$form-item-prefix}-success-help { 422 | i { 423 | margin-right: 8px; 424 | } 425 | } 426 | 427 | .#{$form-item-prefix}-error-help { 428 | color: $--color-danger; 429 | } 430 | 431 | .#{$form-item-prefix}-warning-help { 432 | color: $--color-warning; 433 | } 434 | 435 | .#{$form-item-prefix}-success-help { 436 | color: $--color-success; 437 | } 438 | 439 | .#{$form-item-prefix}-warning { 440 | & .#{$namespace}-input__inner, 441 | & .#{$namespace}-textarea__inner { 442 | &, 443 | &.hover { 444 | border-color: $--color-warning; 445 | } 446 | } 447 | 448 | & .#{$namespace}-input__inner, 449 | & .#{$namespace}-textarea__inner { 450 | &:focus { 451 | border-color: $--color-warning; 452 | } 453 | } 454 | 455 | & .#{$namespace}-input-group__append, 456 | & .#{$namespace}-input-group__prepend { 457 | & .#{$namespace}-input__inner { 458 | border-color: transparent; 459 | } 460 | } 461 | .#{$namespace}-input__validateIcon { 462 | color: $--color-warning !important; 463 | } 464 | } 465 | 466 | .#{$form-item-prefix}-success { 467 | & .#{$namespace}-input__inner, 468 | & .#{$namespace}-textarea__inner { 469 | &, 470 | &.hover { 471 | border-color: $--color-success; 472 | } 473 | } 474 | 475 | & .#{$namespace}-input__inner, 476 | & .#{$namespace}-textarea__inner { 477 | &:focus { 478 | border-color: $--color-success; 479 | } 480 | } 481 | 482 | & .#{$namespace}-input-group__append, 483 | & .#{$namespace}-input-group__prepend { 484 | & .#{$namespace}-input__inner { 485 | border-color: transparent; 486 | } 487 | } 488 | .#{$namespace}-input__validateIcon { 489 | color: $--color-success !important; 490 | } 491 | } 492 | 493 | .#{$form-item-prefix}-bordered-none { 494 | .#{$namespace}-input__inner { 495 | border: none !important; 496 | } 497 | 498 | .#{$namespace}-input-number__decrease, 499 | .#{$namespace}-input-number__increase { 500 | border: none !important; 501 | background: transparent !important; 502 | } 503 | } 504 | 505 | .#{$form-item-prefix}-inset { 506 | border-radius: $--border-radius-base; 507 | border: $--border-base; 508 | padding-left: 12px; 509 | transition: 0.3s all; 510 | 511 | &:hover { 512 | @include hover; 513 | } 514 | } 515 | 516 | .#{$form-item-prefix}-inset-active { 517 | @include active; 518 | } 519 | ``` -------------------------------------------------------------------------------- /packages/antd/src/select-table/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useState, useMemo } from 'react' 2 | import { 3 | observer, 4 | useFieldSchema, 5 | useField, 6 | Schema, 7 | RecursionField, 8 | } from '@formily/react' 9 | import cls from 'classnames' 10 | import { GeneralField, FieldDisplayTypes } from '@formily/core' 11 | import { isArr, isBool, isFn } from '@formily/shared' 12 | import { Input, Table } from 'antd' 13 | import { TableProps, ColumnProps } from 'antd/lib/table' 14 | import { SearchProps } from 'antd/lib/input' 15 | import { useFilterOptions } from './useFilterOptions' 16 | import { useFlatOptions } from './useFlatOptions' 17 | import { useSize } from './useSize' 18 | import { useTitleAddon } from './useTitleAddon' 19 | import { useCheckSlackly, getIndeterminate } from './useCheckSlackly' 20 | import { getUISelected, getOutputData } from './utils' 21 | import { usePrefixCls } from '../__builtins__' 22 | 23 | const { Search } = Input 24 | 25 | interface ObservableColumnSource { 26 | field: GeneralField 27 | columnProps: ColumnProps<any> 28 | schema: Schema 29 | display: FieldDisplayTypes 30 | name: string 31 | } 32 | 33 | type IFilterOption = boolean | ((option: any, keyword: string) => boolean) 34 | 35 | type IFilterSort = (optionA: any, optionB: any) => number 36 | 37 | export interface ISelectTableColumnProps extends ColumnProps<any> { 38 | key: React.ReactText 39 | } 40 | 41 | export interface ISelectTableProps extends TableProps<any> { 42 | mode?: 'multiple' | 'single' 43 | dataSource?: any[] 44 | optionAsValue?: boolean 45 | valueType?: 'all' | 'parent' | 'child' | 'path' 46 | showSearch?: boolean 47 | searchProps?: SearchProps 48 | primaryKey?: string | ((record: any) => string) 49 | filterOption?: IFilterOption 50 | filterSort?: IFilterSort 51 | onSearch?: (keyword: string) => void 52 | onChange?: (value: any, options: any) => void 53 | value?: any 54 | } 55 | 56 | type ComposedSelectTable = React.FC< 57 | React.PropsWithChildren<ISelectTableProps> 58 | > & { 59 | Column?: React.FC<React.PropsWithChildren<ISelectTableColumnProps>> 60 | } 61 | 62 | const isColumnComponent = (schema: Schema) => { 63 | return schema['x-component']?.indexOf('Column') > -1 64 | } 65 | 66 | const useSources = () => { 67 | const arrayField = useField() 68 | const schema = useFieldSchema() 69 | const parseSources = (schema: Schema): ObservableColumnSource[] => { 70 | if (isColumnComponent(schema)) { 71 | if (!schema['x-component-props']?.['dataIndex'] && !schema['name']) 72 | return [] 73 | const name = schema['x-component-props']?.['dataIndex'] || schema['name'] 74 | const field = arrayField.query(arrayField.address.concat(name)).take() 75 | const columnProps = 76 | field?.component?.[1] || schema['x-component-props'] || {} 77 | const display = field?.display || schema['x-display'] 78 | return [ 79 | { 80 | name, 81 | display, 82 | field, 83 | schema, 84 | columnProps: { 85 | title: field?.title || columnProps.title, 86 | ...columnProps, 87 | }, 88 | }, 89 | ] 90 | } else if (schema.properties) { 91 | return schema.reduceProperties((buf, schema) => { 92 | return buf.concat(parseSources(schema)) 93 | }, []) 94 | } 95 | } 96 | 97 | const parseArrayItems = (schema: Schema['items']) => { 98 | if (!schema) return [] 99 | const sources: ObservableColumnSource[] = [] 100 | const items = isArr(schema) ? schema : [schema] 101 | return items.reduce((columns, schema) => { 102 | const item = parseSources(schema) 103 | if (item) { 104 | return columns.concat(item) 105 | } 106 | return columns 107 | }, sources) 108 | } 109 | 110 | const validSchema = ( 111 | schema?.type === 'array' && schema?.items ? schema.items : schema 112 | ) as Schema 113 | 114 | return parseArrayItems(validSchema) 115 | } 116 | 117 | const useColumns = ( 118 | sources: ObservableColumnSource[] 119 | ): TableProps<any>['columns'] => { 120 | return sources.reduce((buf, { name, columnProps, schema, display }, key) => { 121 | if (display !== 'visible') return buf 122 | if (!isColumnComponent(schema)) return buf 123 | return buf.concat({ 124 | ...columnProps, 125 | key, 126 | dataIndex: name, 127 | }) 128 | }, []) 129 | } 130 | 131 | const addPrimaryKey = (dataSource, rowKey, primaryKey) => 132 | dataSource.map((item) => { 133 | const children = isArr(item.children) 134 | ? addPrimaryKey(item.children, rowKey, primaryKey) 135 | : {} 136 | return { 137 | ...item, 138 | ...children, 139 | [primaryKey]: rowKey(item), 140 | } 141 | }) 142 | 143 | export const SelectTable: ComposedSelectTable = observer((props) => { 144 | const { 145 | mode = 'multiple', 146 | dataSource: propsDataSource, 147 | optionAsValue, 148 | valueType = 'all', 149 | showSearch = false, 150 | filterOption, 151 | filterSort, 152 | onSearch, 153 | searchProps, 154 | className, 155 | value, 156 | onChange, 157 | rowSelection, 158 | primaryKey: rowKey = 'key', 159 | ...otherTableProps 160 | } = props 161 | const prefixCls = usePrefixCls('formily-select-table', props) 162 | const [searchValue, setSearchValue] = useState<string>() 163 | const field = useField() as any 164 | const loading = isBool(props.loading) ? props.loading : field.loading 165 | const disabled = field.disabled 166 | const readOnly = field.readOnly 167 | const readPretty = field.readPretty 168 | const { searchSize, tableSize } = useSize( 169 | field.decoratorProps?.size, 170 | searchProps?.size, 171 | props?.size 172 | ) 173 | const primaryKey = isFn(rowKey) ? '__formily_key__' : rowKey 174 | const sources = useSources() 175 | const columns = useColumns(sources) 176 | 177 | // dataSource 178 | let dataSource = isArr(propsDataSource) ? propsDataSource : field.dataSource 179 | dataSource = isFn(rowKey) 180 | ? addPrimaryKey(dataSource, rowKey, primaryKey) 181 | : dataSource 182 | 183 | // Filter dataSource By Search 184 | const filteredDataSource = useFilterOptions( 185 | dataSource, 186 | searchValue, 187 | filterOption, 188 | rowSelection?.checkStrictly 189 | ) 190 | 191 | // Order dataSource By filterSort 192 | const orderedFilteredDataSource = useMemo(() => { 193 | if (!filterSort) { 194 | return filteredDataSource 195 | } 196 | return [...filteredDataSource].sort((a, b) => filterSort(a, b)) 197 | }, [filteredDataSource, filterSort]) 198 | 199 | const flatDataSource = useFlatOptions(dataSource) 200 | const flatFilteredDataSource = useFlatOptions(filteredDataSource) 201 | 202 | // 分页或异步查询时,dataSource会丢失已选数据,配置optionAsValue则无法获取已选数据,需要进行合并 203 | const getWholeDataSource = () => { 204 | if (optionAsValue && mode === 'multiple' && value?.length) { 205 | const map = new Map() 206 | const arr = [...flatDataSource, ...value] 207 | arr.forEach((item) => { 208 | if (!map.has(item[primaryKey])) { 209 | map.set(item[primaryKey], item) 210 | } 211 | }) 212 | return [...map.values()] 213 | } 214 | return flatDataSource 215 | } 216 | 217 | // selected keys for Table UI 218 | const selected = getUISelected( 219 | value, 220 | flatDataSource, 221 | primaryKey, 222 | valueType, 223 | optionAsValue, 224 | mode, 225 | rowSelection?.checkStrictly, 226 | rowKey 227 | ) 228 | 229 | // readPretty Value 230 | const readPrettyDataSource = useFilterOptions( 231 | orderedFilteredDataSource, 232 | selected, 233 | (value, item) => value.includes(item[primaryKey]) 234 | ) 235 | 236 | const onInnerSearch = (searchText) => { 237 | const formatted = (searchText || '').trim() 238 | setSearchValue(searchText) 239 | onSearch?.(formatted) 240 | } 241 | 242 | const onInnerChange = (selectedRowKeys: any[]) => { 243 | if (readOnly) { 244 | return 245 | } 246 | // 筛选后onChange默认的records数据不完整,此处需使用完整数据过滤 247 | const wholeRecords = getWholeDataSource().filter((item) => 248 | selectedRowKeys.includes(item?.[primaryKey]) 249 | ) 250 | const { outputValue, outputOptions } = getOutputData( 251 | selectedRowKeys, 252 | wholeRecords, 253 | dataSource, 254 | primaryKey, 255 | valueType, 256 | optionAsValue, 257 | mode, 258 | rowSelection?.checkStrictly 259 | ) 260 | 261 | onChange?.(outputValue, outputOptions) 262 | } 263 | 264 | const onRowClick = (record) => { 265 | if (readPretty || disabled || readOnly || record?.disabled) { 266 | return 267 | } 268 | const selectedRowKey = record?.[primaryKey] 269 | const isSelected = selected?.includes(selectedRowKey) 270 | let selectedRowKeys = [] 271 | if (mode === 'single') { 272 | selectedRowKeys = [selectedRowKey] 273 | } else { 274 | if (isSelected) { 275 | selectedRowKeys = selected.filter((item) => item !== selectedRowKey) 276 | } else { 277 | selectedRowKeys = [...selected, selectedRowKey] 278 | } 279 | } 280 | if (rowSelection?.checkStrictly !== false) { 281 | onInnerChange(selectedRowKeys) 282 | } else { 283 | onSlacklyChange(selectedRowKeys) 284 | } 285 | } 286 | 287 | // TreeData SlacklyChange 288 | const onSlacklyChange = (currentSelected: any[]) => { 289 | let { selectedRowKeys } = useCheckSlackly( 290 | currentSelected, 291 | selected, 292 | flatDataSource, 293 | flatFilteredDataSource, 294 | primaryKey, 295 | rowSelection?.checkStrictly 296 | ) 297 | onInnerChange(selectedRowKeys) 298 | } 299 | 300 | // Table All Checkbox 301 | const titleAddon = useTitleAddon( 302 | selected, 303 | flatDataSource, 304 | flatFilteredDataSource, 305 | primaryKey, 306 | mode, 307 | disabled, 308 | readOnly, 309 | rowSelection?.checkStrictly, 310 | onInnerChange 311 | ) 312 | 313 | // Antd rowSelection type 314 | const modeAsType: any = { multiple: 'checkbox', single: 'radio' }?.[mode] 315 | 316 | return ( 317 | <div className={prefixCls}> 318 | {showSearch ? ( 319 | <Search 320 | {...searchProps} 321 | className={cls(`${prefixCls}-search`, searchProps?.className)} 322 | style={{ width: '100%', ...searchProps?.style }} 323 | onSearch={onInnerSearch} 324 | onChange={(e) => onInnerSearch(e.target.value)} 325 | disabled={disabled} 326 | readOnly={readOnly} 327 | size={searchSize} 328 | loading={loading} // antd 329 | /> 330 | ) : null} 331 | <Table 332 | {...otherTableProps} 333 | className={cls(`${prefixCls}-table`, className)} 334 | dataSource={ 335 | readPretty ? readPrettyDataSource : orderedFilteredDataSource 336 | } 337 | rowSelection={ 338 | readPretty 339 | ? undefined 340 | : ({ 341 | ...rowSelection, 342 | ...titleAddon, 343 | getCheckboxProps: (record) => ({ 344 | ...(rowSelection?.getCheckboxProps?.(record) as any), 345 | disabled: disabled || record?.disabled, 346 | }), // antd 347 | ...(rowSelection?.checkStrictly !== false 348 | ? {} 349 | : { 350 | renderCell: (checked, record, index, originNode) => { 351 | return React.cloneElement( 352 | originNode as React.ReactElement, 353 | { 354 | indeterminate: getIndeterminate( 355 | record, 356 | flatDataSource, 357 | selected, 358 | primaryKey 359 | ), 360 | } 361 | ) 362 | }, 363 | }), 364 | selectedRowKeys: selected, 365 | onChange: 366 | rowSelection?.checkStrictly !== false 367 | ? onInnerChange 368 | : onSlacklyChange, 369 | type: modeAsType, 370 | preserveSelectedRowKeys: true, 371 | checkStrictly: true, 372 | } as any) 373 | } 374 | columns={props.columns || columns} 375 | rowKey={primaryKey} 376 | loading={loading} 377 | size={tableSize} 378 | onRow={(record) => { 379 | // antd 380 | const onRowResult = otherTableProps.onRow?.(record) 381 | return { 382 | ...onRowResult, 383 | onClick: (e) => { 384 | onRowResult?.onClick?.(e) 385 | onRowClick(record) 386 | }, 387 | } 388 | }} 389 | > 390 | {''} 391 | </Table> 392 | {sources.map((column, key) => { 393 | //专门用来承接对Column的状态管理 394 | if (!isColumnComponent(column.schema)) return 395 | return React.createElement(RecursionField, { 396 | name: column.name, 397 | schema: column.schema, 398 | onlyRenderSelf: true, 399 | key, 400 | }) 401 | })} 402 | </div> 403 | ) 404 | }) 405 | 406 | const TableColumn: React.FC< 407 | React.PropsWithChildren<ISelectTableColumnProps> 408 | > = () => <></> 409 | 410 | SelectTable.Column = TableColumn 411 | 412 | export default SelectTable 413 | ``` -------------------------------------------------------------------------------- /packages/element/src/form-drawer/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { createForm, Form, IFormProps } from '@formily/core' 2 | import { toJS } from '@formily/reactive' 3 | import { observer } from '@formily/reactive-vue' 4 | import { 5 | applyMiddleware, 6 | IMiddleware, 7 | isBool, 8 | isFn, 9 | isNum, 10 | isStr, 11 | } from '@formily/shared' 12 | import { FormProvider, Fragment, h } from '@formily/vue' 13 | import type { Button as ButtonProps, Drawer as DrawerProps } from 'element-ui' 14 | import { Button, Drawer } from 'element-ui' 15 | import { t } from 'element-ui/src/locale' 16 | import { Portal, PortalTarget } from 'portal-vue' 17 | import Vue, { Component, VNode } from 'vue' 18 | import { defineComponent } from 'vue-demi' 19 | import { stylePrefix } from '../__builtins__/configs' 20 | import { 21 | createPortalProvider, 22 | getProtalContext, 23 | isValidElement, 24 | loading, 25 | resolveComponent, 26 | } from '../__builtins__/shared' 27 | 28 | type FormDrawerContentProps = { form: Form } 29 | 30 | type FormDrawerContent = Component | ((props: FormDrawerContentProps) => VNode) 31 | 32 | type DrawerTitle = string | number | Component | VNode | (() => VNode) 33 | 34 | type IFormDrawerProps = Omit<DrawerProps, 'title'> & { 35 | title?: DrawerTitle 36 | footer?: null | Component | VNode | (() => VNode) 37 | cancelText?: string | Component | VNode | (() => VNode) 38 | cancelButtonProps?: ButtonProps 39 | okText?: string | Component | VNode | (() => VNode) 40 | okButtonProps?: ButtonProps 41 | onOpen?: () => void 42 | onOpened?: () => void 43 | onClose?: () => void 44 | onClosed?: () => void 45 | onCancel?: () => void 46 | onOK?: () => void 47 | loadingText?: string 48 | } 49 | 50 | const PORTAL_TARGET_NAME = 'FormDrawerFooter' 51 | 52 | const isDrawerTitle = (props: any): props is DrawerTitle => { 53 | return isNum(props) || isStr(props) || isBool(props) || isValidElement(props) 54 | } 55 | 56 | const getDrawerProps = (props: any): IFormDrawerProps => { 57 | if (isDrawerTitle(props)) { 58 | return { 59 | title: props, 60 | } as IFormDrawerProps 61 | } else { 62 | return props 63 | } 64 | } 65 | 66 | export interface IFormDrawer { 67 | forOpen(middleware: IMiddleware<IFormProps>): IFormDrawer 68 | forConfirm(middleware: IMiddleware<IFormProps>): IFormDrawer 69 | forCancel(middleware: IMiddleware<IFormProps>): IFormDrawer 70 | open(props?: IFormProps): Promise<any> 71 | close(): void 72 | } 73 | 74 | export interface IFormDrawerComponentProps { 75 | content: FormDrawerContent 76 | resolve: () => any 77 | reject: () => any 78 | } 79 | 80 | export function FormDrawer( 81 | title: IFormDrawerProps | DrawerTitle, 82 | content: FormDrawerContent 83 | ): IFormDrawer 84 | 85 | export function FormDrawer( 86 | title: IFormDrawerProps | DrawerTitle, 87 | id: string | symbol, 88 | content: FormDrawerContent 89 | ): IFormDrawer 90 | 91 | export function FormDrawer( 92 | title: DrawerTitle, 93 | id: string, 94 | content: FormDrawerContent 95 | ): IFormDrawer 96 | 97 | export function FormDrawer( 98 | title: IFormDrawerProps | DrawerTitle, 99 | id: string | symbol | FormDrawerContent, 100 | content?: FormDrawerContent 101 | ): IFormDrawer { 102 | if (isFn(id) || isValidElement(id)) { 103 | content = id as FormDrawerContent 104 | id = 'form-drawer' 105 | } 106 | 107 | const prefixCls = `${stylePrefix}-form-drawer` 108 | const env = { 109 | root: document.createElement('div'), 110 | form: null, 111 | promise: null, 112 | instance: null, 113 | openMiddlewares: [], 114 | confirmMiddlewares: [], 115 | cancelMiddlewares: [], 116 | } 117 | 118 | document.body.appendChild(env.root) 119 | 120 | const props = getDrawerProps(title) 121 | const drawerProps = { 122 | ...props, 123 | onClosed: () => { 124 | props.onClosed?.() 125 | env.instance.$destroy() 126 | env.instance = null 127 | env.root?.parentNode?.removeChild(env.root) 128 | env.root = undefined 129 | }, 130 | } 131 | 132 | const component = observer( 133 | defineComponent({ 134 | setup() { 135 | return () => 136 | h( 137 | Fragment, 138 | {}, 139 | { 140 | default: () => 141 | resolveComponent(content, { 142 | form: env.form, 143 | }), 144 | } 145 | ) 146 | }, 147 | }) 148 | ) 149 | 150 | const render = (visible = true, resolve?: () => any, reject?: () => any) => { 151 | if (!env.instance) { 152 | const ComponentConstructor = Vue.extend({ 153 | props: ['drawerProps'], 154 | data() { 155 | return { 156 | visible: false, 157 | } 158 | }, 159 | render() { 160 | const { 161 | onClose, 162 | onClosed, 163 | onOpen, 164 | onOpened, 165 | onOK, 166 | onCancel, 167 | title, 168 | footer, 169 | okText, 170 | cancelText, 171 | okButtonProps, 172 | cancelButtonProps, 173 | ...drawerProps 174 | } = this.drawerProps 175 | 176 | return h( 177 | FormProvider, 178 | { 179 | props: { 180 | form: env.form, 181 | }, 182 | }, 183 | { 184 | default: () => 185 | h( 186 | Drawer, 187 | { 188 | class: [`${prefixCls}`], 189 | attrs: { 190 | visible: this.visible, 191 | ...drawerProps, 192 | }, 193 | on: { 194 | 'update:visible': (val) => { 195 | this.visible = val 196 | }, 197 | close: () => { 198 | onClose?.() 199 | }, 200 | 201 | closed: () => { 202 | onClosed?.() 203 | }, 204 | open: () => { 205 | onOpen?.() 206 | }, 207 | opened: () => { 208 | onOpened?.() 209 | }, 210 | }, 211 | }, 212 | { 213 | default: () => [ 214 | h( 215 | 'div', 216 | { 217 | class: [`${prefixCls}-body`], 218 | }, 219 | { 220 | default: () => h(component, {}, {}), 221 | } 222 | ), 223 | h( 224 | 'div', 225 | { 226 | class: [`${prefixCls}-footer`], 227 | }, 228 | { 229 | default: () => { 230 | const FooterProtalTarget = h( 231 | PortalTarget, 232 | { 233 | props: { 234 | name: PORTAL_TARGET_NAME, 235 | slim: true, 236 | }, 237 | }, 238 | {} 239 | ) 240 | 241 | if (footer === null) { 242 | return [null, FooterProtalTarget] 243 | } else if (footer) { 244 | return [ 245 | resolveComponent(footer), 246 | FooterProtalTarget, 247 | ] 248 | } 249 | 250 | return [ 251 | h( 252 | Button, 253 | { 254 | attrs: cancelButtonProps, 255 | on: { 256 | click: (e) => { 257 | onCancel?.(e) 258 | reject() 259 | }, 260 | }, 261 | }, 262 | { 263 | default: () => 264 | resolveComponent( 265 | cancelText || 266 | t('el.popconfirm.cancelButtonText') 267 | ), 268 | } 269 | ), 270 | 271 | h( 272 | Button, 273 | { 274 | attrs: { 275 | type: 'primary', 276 | ...okButtonProps, 277 | }, 278 | on: { 279 | click: (e) => { 280 | onOK?.(e) 281 | resolve() 282 | }, 283 | }, 284 | }, 285 | { 286 | default: () => 287 | resolveComponent( 288 | okText || 289 | t('el.popconfirm.confirmButtonText') 290 | ), 291 | } 292 | ), 293 | FooterProtalTarget, 294 | ] 295 | }, 296 | } 297 | ), 298 | ], 299 | title: () => 300 | h('div', {}, { default: () => resolveComponent(title) }), 301 | } 302 | ), 303 | } 304 | ) 305 | }, 306 | }) 307 | env.instance = new ComponentConstructor({ 308 | propsData: { 309 | drawerProps, 310 | }, 311 | parent: getProtalContext(id as string | symbol), 312 | }) 313 | env.instance.$mount(env.root) 314 | } 315 | 316 | env.instance.visible = visible 317 | } 318 | 319 | const formDrawer = { 320 | forOpen: (middleware: IMiddleware<IFormProps>) => { 321 | if (isFn(middleware)) { 322 | env.openMiddlewares.push(middleware) 323 | } 324 | return formDrawer 325 | }, 326 | forConfirm: (middleware: IMiddleware<Form>) => { 327 | if (isFn(middleware)) { 328 | env.confirmMiddlewares.push(middleware) 329 | } 330 | return formDrawer 331 | }, 332 | forCancel: (middleware: IMiddleware<Form>) => { 333 | if (isFn(middleware)) { 334 | env.cancelMiddlewares.push(middleware) 335 | } 336 | return formDrawer 337 | }, 338 | open: (props: IFormProps) => { 339 | if (env.promise) return env.promise 340 | 341 | env.promise = new Promise(async (resolve, reject) => { 342 | try { 343 | props = await loading(drawerProps.loadingText, () => 344 | applyMiddleware(props, env.openMiddlewares) 345 | ) 346 | env.form = env.form || createForm(props) 347 | } catch (e) { 348 | reject(e) 349 | } 350 | 351 | render( 352 | true, 353 | () => { 354 | env.form 355 | .submit(async () => { 356 | await applyMiddleware(env.form, env.confirmMiddlewares) 357 | resolve(toJS(env.form.values)) 358 | if (drawerProps.beforeClose) { 359 | setTimeout(() => { 360 | drawerProps.beforeClose(() => { 361 | formDrawer.close() 362 | }) 363 | }) 364 | } else { 365 | formDrawer.close() 366 | } 367 | }) 368 | .catch(() => {}) 369 | }, 370 | async () => { 371 | await loading(drawerProps.loadingText, () => 372 | applyMiddleware(env.form, env.cancelMiddlewares) 373 | ) 374 | 375 | if (drawerProps.beforeClose) { 376 | drawerProps.beforeClose(() => { 377 | formDrawer.close() 378 | }) 379 | } else { 380 | formDrawer.close() 381 | } 382 | } 383 | ) 384 | }) 385 | return env.promise 386 | }, 387 | close: () => { 388 | if (!env.root) return 389 | render(false) 390 | }, 391 | } 392 | return formDrawer 393 | } 394 | 395 | const FormDrawerFooter = defineComponent({ 396 | name: 'FFormDrawerFooter', 397 | setup(props, { slots }) { 398 | return () => { 399 | return h( 400 | Portal, 401 | { 402 | props: { 403 | to: PORTAL_TARGET_NAME, 404 | }, 405 | }, 406 | slots 407 | ) 408 | } 409 | }, 410 | }) 411 | 412 | FormDrawer.Footer = FormDrawerFooter 413 | FormDrawer.Protal = createPortalProvider('form-drawer') 414 | 415 | export default FormDrawer 416 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Select.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Select 2 | 3 | > 下拉框组件 4 | 5 | ## Markup Schema 同步数据源案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Select, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Number 26 | name="select" 27 | title="选择框" 28 | x-decorator="FormItem" 29 | x-component="Select" 30 | enum={[ 31 | { label: '选项1', value: 1 }, 32 | { label: '选项2', value: 2 }, 33 | ]} 34 | x-component-props={{ 35 | style: { 36 | width: 120, 37 | }, 38 | }} 39 | /> 40 | </SchemaField> 41 | <FormButtonGroup> 42 | <Submit onSubmit={console.log}>提交</Submit> 43 | </FormButtonGroup> 44 | </FormProvider> 45 | ) 46 | ``` 47 | 48 | ## Markup Schema 异步搜索案例 49 | 50 | ```tsx 51 | import React from 'react' 52 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' 53 | import { 54 | createForm, 55 | onFieldReact, 56 | onFieldInit, 57 | FormPathPattern, 58 | Field, 59 | } from '@formily/core' 60 | import { FormProvider, createSchemaField } from '@formily/react' 61 | import { action, observable } from '@formily/reactive' 62 | import { fetch } from 'mfetch' 63 | 64 | let timeout 65 | let currentValue 66 | 67 | function fetchData(value, callback) { 68 | if (timeout) { 69 | clearTimeout(timeout) 70 | timeout = null 71 | } 72 | currentValue = value 73 | 74 | function fake() { 75 | fetch(`https://suggest.taobao.com/sug?q=${value}`, { 76 | method: 'jsonp', 77 | }) 78 | .then((response) => response.json()) 79 | .then((d) => { 80 | if (currentValue === value) { 81 | const { result } = d 82 | const data = [] 83 | result.forEach((r) => { 84 | data.push({ 85 | value: r[0], 86 | text: r[0], 87 | }) 88 | }) 89 | callback(data) 90 | } 91 | }) 92 | } 93 | 94 | timeout = setTimeout(fake, 300) 95 | } 96 | 97 | const SchemaField = createSchemaField({ 98 | components: { 99 | Select, 100 | FormItem, 101 | }, 102 | }) 103 | 104 | const useAsyncDataSource = ( 105 | pattern: FormPathPattern, 106 | service: (param: { 107 | keyword: string 108 | field: Field 109 | }) => Promise<{ label: string; value: any }[]> 110 | ) => { 111 | const keyword = observable.ref('') 112 | 113 | onFieldInit(pattern, (field) => { 114 | field.setComponentProps({ 115 | onSearch: (value) => { 116 | keyword.value = value 117 | }, 118 | }) 119 | }) 120 | 121 | onFieldReact(pattern, (field) => { 122 | field.loading = true 123 | service({ field, keyword: keyword.value }).then( 124 | action.bound((data) => { 125 | field.dataSource = data 126 | field.loading = false 127 | }) 128 | ) 129 | }) 130 | } 131 | 132 | const form = createForm({ 133 | effects: () => { 134 | useAsyncDataSource('select', async ({ keyword }) => { 135 | if (!keyword) { 136 | return [] 137 | } 138 | return new Promise((resolve) => { 139 | fetchData(keyword, resolve) 140 | }) 141 | }) 142 | }, 143 | }) 144 | 145 | export default () => ( 146 | <FormProvider form={form}> 147 | <SchemaField> 148 | <SchemaField.String 149 | name="select" 150 | title="异步搜索选择框" 151 | x-decorator="FormItem" 152 | x-component="Select" 153 | x-component-props={{ 154 | showSearch: true, 155 | filterOption: false, 156 | style: { 157 | width: 300, 158 | }, 159 | }} 160 | /> 161 | </SchemaField> 162 | <FormButtonGroup> 163 | <Submit onSubmit={console.log}>提交</Submit> 164 | </FormButtonGroup> 165 | </FormProvider> 166 | ) 167 | ``` 168 | 169 | ## Markup Schema 异步联动数据源案例 170 | 171 | ```tsx 172 | import React from 'react' 173 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' 174 | import { createForm, onFieldReact, FormPathPattern, Field } from '@formily/core' 175 | import { FormProvider, createSchemaField } from '@formily/react' 176 | import { action } from '@formily/reactive' 177 | 178 | const SchemaField = createSchemaField({ 179 | components: { 180 | Select, 181 | FormItem, 182 | }, 183 | }) 184 | 185 | const useAsyncDataSource = ( 186 | pattern: FormPathPattern, 187 | service: (field: Field) => Promise<{ label: string; value: any }[]> 188 | ) => { 189 | onFieldReact(pattern, (field) => { 190 | field.loading = true 191 | service(field).then( 192 | action.bound((data) => { 193 | field.dataSource = data 194 | field.loading = false 195 | }) 196 | ) 197 | }) 198 | } 199 | 200 | const form = createForm({ 201 | effects: () => { 202 | useAsyncDataSource('select', async (field) => { 203 | const linkage = field.query('linkage').get('value') 204 | if (!linkage) return [] 205 | return new Promise((resolve) => { 206 | setTimeout(() => { 207 | if (linkage === 1) { 208 | resolve([ 209 | { 210 | label: 'AAA', 211 | value: 'aaa', 212 | }, 213 | { 214 | label: 'BBB', 215 | value: 'ccc', 216 | }, 217 | ]) 218 | } else if (linkage === 2) { 219 | resolve([ 220 | { 221 | label: 'CCC', 222 | value: 'ccc', 223 | }, 224 | { 225 | label: 'DDD', 226 | value: 'ddd', 227 | }, 228 | ]) 229 | } 230 | }, 1500) 231 | }) 232 | }) 233 | }, 234 | }) 235 | 236 | export default () => ( 237 | <FormProvider form={form}> 238 | <SchemaField> 239 | <SchemaField.Number 240 | name="linkage" 241 | title="联动选择框" 242 | x-decorator="FormItem" 243 | x-component="Select" 244 | enum={[ 245 | { label: '发请求1', value: 1 }, 246 | { label: '发请求2', value: 2 }, 247 | ]} 248 | x-component-props={{ 249 | style: { 250 | width: 120, 251 | }, 252 | }} 253 | /> 254 | <SchemaField.String 255 | name="select" 256 | title="异步选择框" 257 | x-decorator="FormItem" 258 | x-component="Select" 259 | x-component-props={{ 260 | style: { 261 | width: 120, 262 | }, 263 | }} 264 | /> 265 | </SchemaField> 266 | <FormButtonGroup> 267 | <Submit onSubmit={console.log}>提交</Submit> 268 | </FormButtonGroup> 269 | </FormProvider> 270 | ) 271 | ``` 272 | 273 | ## JSON Schema 同步数据源案例 274 | 275 | ```tsx 276 | import React from 'react' 277 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' 278 | import { createForm } from '@formily/core' 279 | import { FormProvider, createSchemaField } from '@formily/react' 280 | 281 | const SchemaField = createSchemaField({ 282 | components: { 283 | Select, 284 | FormItem, 285 | }, 286 | }) 287 | 288 | const form = createForm() 289 | 290 | const schema = { 291 | type: 'object', 292 | properties: { 293 | select: { 294 | type: 'string', 295 | title: '选择框', 296 | 'x-decorator': 'FormItem', 297 | 'x-component': 'Select', 298 | enum: [ 299 | { label: '选项1', value: 1 }, 300 | { label: '选项2', value: 2 }, 301 | ], 302 | 'x-component-props': { 303 | style: { 304 | width: 120, 305 | }, 306 | }, 307 | }, 308 | }, 309 | } 310 | 311 | export default () => ( 312 | <FormProvider form={form}> 313 | <SchemaField schema={schema} /> 314 | <FormButtonGroup> 315 | <Submit onSubmit={console.log}>提交</Submit> 316 | </FormButtonGroup> 317 | </FormProvider> 318 | ) 319 | ``` 320 | 321 | ## JSON Schema 异步联动数据源案例 322 | 323 | ```tsx 324 | import React from 'react' 325 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' 326 | import { createForm } from '@formily/core' 327 | import { FormProvider, createSchemaField } from '@formily/react' 328 | import { action } from '@formily/reactive' 329 | 330 | const SchemaField = createSchemaField({ 331 | components: { 332 | Select, 333 | FormItem, 334 | }, 335 | }) 336 | 337 | const loadData = async (field) => { 338 | const linkage = field.query('linkage').get('value') 339 | if (!linkage) return [] 340 | return new Promise((resolve) => { 341 | setTimeout(() => { 342 | if (linkage === 1) { 343 | resolve([ 344 | { 345 | label: 'AAA', 346 | value: 'aaa', 347 | }, 348 | { 349 | label: 'BBB', 350 | value: 'ccc', 351 | }, 352 | ]) 353 | } else if (linkage === 2) { 354 | resolve([ 355 | { 356 | label: 'CCC', 357 | value: 'ccc', 358 | }, 359 | { 360 | label: 'DDD', 361 | value: 'ddd', 362 | }, 363 | ]) 364 | } 365 | }, 1500) 366 | }) 367 | } 368 | 369 | const useAsyncDataSource = (service) => (field) => { 370 | field.loading = true 371 | service(field).then( 372 | action.bound((data) => { 373 | field.dataSource = data 374 | field.loading = false 375 | }) 376 | ) 377 | } 378 | 379 | const form = createForm() 380 | 381 | const schema = { 382 | type: 'object', 383 | properties: { 384 | linkage: { 385 | type: 'string', 386 | title: '联动选择框', 387 | enum: [ 388 | { label: '发请求1', value: 1 }, 389 | { label: '发请求2', value: 2 }, 390 | ], 391 | 'x-decorator': 'FormItem', 392 | 'x-component': 'Select', 393 | 'x-component-props': { 394 | style: { 395 | width: 120, 396 | }, 397 | }, 398 | }, 399 | select: { 400 | type: 'string', 401 | title: '异步选择框', 402 | 'x-decorator': 'FormItem', 403 | 'x-component': 'Select', 404 | 'x-component-props': { 405 | style: { 406 | width: 120, 407 | }, 408 | }, 409 | 'x-reactions': ['{{useAsyncDataSource(loadData)}}'], 410 | }, 411 | }, 412 | } 413 | 414 | export default () => ( 415 | <FormProvider form={form}> 416 | <SchemaField schema={schema} scope={{ useAsyncDataSource, loadData }} /> 417 | <FormButtonGroup> 418 | <Submit onSubmit={console.log}>提交</Submit> 419 | </FormButtonGroup> 420 | </FormProvider> 421 | ) 422 | ``` 423 | 424 | ## 纯 JSX 同步数据源案例 425 | 426 | ```tsx 427 | import React from 'react' 428 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' 429 | import { createForm } from '@formily/core' 430 | import { FormProvider, Field } from '@formily/react' 431 | 432 | const form = createForm() 433 | 434 | export default () => ( 435 | <FormProvider form={form}> 436 | <Field 437 | name="select" 438 | title="选择框" 439 | dataSource={[ 440 | { label: '选项1', value: 1 }, 441 | { label: '选项2', value: 2 }, 442 | ]} 443 | decorator={[FormItem]} 444 | component={[ 445 | Select, 446 | { 447 | style: { 448 | width: 120, 449 | }, 450 | }, 451 | ]} 452 | /> 453 | <FormButtonGroup> 454 | <Submit onSubmit={console.log}>提交</Submit> 455 | </FormButtonGroup> 456 | </FormProvider> 457 | ) 458 | ``` 459 | 460 | ## 纯 JSX 异步联动数据源案例 461 | 462 | ```tsx 463 | import React from 'react' 464 | import { Select, FormItem, FormButtonGroup, Submit } from '@formily/antd' 465 | import { 466 | createForm, 467 | onFieldReact, 468 | FormPathPattern, 469 | Field as FieldType, 470 | } from '@formily/core' 471 | import { FormProvider, Field } from '@formily/react' 472 | import { action } from '@formily/reactive' 473 | 474 | const useAsyncDataSource = ( 475 | pattern: FormPathPattern, 476 | service: (field: FieldType) => Promise<{ label: string; value: any }[]> 477 | ) => { 478 | onFieldReact(pattern, (field) => { 479 | field.loading = true 480 | service(field).then( 481 | action.bound((data) => { 482 | field.dataSource = data 483 | field.loading = false 484 | }) 485 | ) 486 | }) 487 | } 488 | 489 | const form = createForm({ 490 | effects: () => { 491 | useAsyncDataSource('select', async (field) => { 492 | const linkage = field.query('linkage').get('value') 493 | if (!linkage) return [] 494 | return new Promise((resolve) => { 495 | setTimeout(() => { 496 | if (linkage === 1) { 497 | resolve([ 498 | { 499 | label: 'AAA', 500 | value: 'aaa', 501 | }, 502 | { 503 | label: 'BBB', 504 | value: 'ccc', 505 | }, 506 | ]) 507 | } else if (linkage === 2) { 508 | resolve([ 509 | { 510 | label: 'CCC', 511 | value: 'ccc', 512 | }, 513 | { 514 | label: 'DDD', 515 | value: 'ddd', 516 | }, 517 | ]) 518 | } 519 | }, 1500) 520 | }) 521 | }) 522 | }, 523 | }) 524 | 525 | export default () => ( 526 | <FormProvider form={form}> 527 | <Field 528 | name="linkage" 529 | title="联动选择框" 530 | dataSource={[ 531 | { label: '发请求1', value: 1 }, 532 | { label: '发请求2', value: 2 }, 533 | ]} 534 | decorator={[FormItem]} 535 | component={[ 536 | Select, 537 | { 538 | style: { 539 | width: 120, 540 | }, 541 | }, 542 | ]} 543 | /> 544 | <Field 545 | name="select" 546 | title="异步选择框" 547 | decorator={[FormItem]} 548 | component={[ 549 | Select, 550 | { 551 | style: { 552 | width: 120, 553 | }, 554 | }, 555 | ]} 556 | /> 557 | <FormButtonGroup> 558 | <Submit onSubmit={console.log}>提交</Submit> 559 | </FormButtonGroup> 560 | </FormProvider> 561 | ) 562 | ``` 563 | 564 | ## API 565 | 566 | 参考 https://ant.design/components/select-cn/ 567 | ```