This is page 21 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/antd/docs/components/Upload.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Upload 2 | 3 | > 上传组件 4 | > 5 | > 注意:使用上传组件,推荐用户进行二次封装,用户无需关心上传组件与 Formily 的数据通信,只需要处理样式与基本上传配置即可。 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | Upload, 13 | FormItem, 14 | FormLayout, 15 | FormButtonGroup, 16 | Submit, 17 | } from '@formily/antd' 18 | import { createForm } from '@formily/core' 19 | import { FormProvider, createSchemaField } from '@formily/react' 20 | import { Button } from 'antd' 21 | import { UploadOutlined, InboxOutlined } from '@ant-design/icons' 22 | 23 | const NormalUpload = (props) => { 24 | return ( 25 | <Upload 26 | {...props} 27 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 28 | headers={{ 29 | authorization: 'authorization-text', 30 | }} 31 | > 32 | <Button icon={<UploadOutlined />}>上传文件</Button> 33 | </Upload> 34 | ) 35 | } 36 | 37 | const CardUpload = (props) => { 38 | return ( 39 | <Upload 40 | {...props} 41 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 42 | listType="picture-card" 43 | headers={{ 44 | authorization: 'authorization-text', 45 | }} 46 | > 47 | <UploadOutlined style={{ fontSize: 20 }} /> 48 | </Upload> 49 | ) 50 | } 51 | 52 | const DraggerUpload = (props) => { 53 | return ( 54 | <Upload.Dragger 55 | {...props} 56 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 57 | > 58 | <p className="ant-upload-drag-icon"> 59 | <InboxOutlined /> 60 | </p> 61 | <p className="ant-upload-text"> 62 | Click or drag file to this area to upload 63 | </p> 64 | <p className="ant-upload-hint"> 65 | Support for a single or bulk upload. Strictly prohibit from uploading 66 | company data or other band files 67 | </p> 68 | </Upload.Dragger> 69 | ) 70 | } 71 | 72 | const SchemaField = createSchemaField({ 73 | components: { 74 | NormalUpload, 75 | CardUpload, 76 | DraggerUpload, 77 | FormItem, 78 | }, 79 | }) 80 | 81 | const form = createForm() 82 | 83 | export default () => ( 84 | <FormProvider form={form}> 85 | <FormLayout labelCol={6} wrapperCol={10}> 86 | <SchemaField> 87 | <SchemaField.Array 88 | name="upload" 89 | title="上传" 90 | x-decorator="FormItem" 91 | x-component="NormalUpload" 92 | required 93 | /> 94 | <SchemaField.Array 95 | name="upload2" 96 | title="卡片上传" 97 | x-decorator="FormItem" 98 | x-component="CardUpload" 99 | required 100 | /> 101 | <SchemaField.Array 102 | name="upload3" 103 | title="拖拽上传" 104 | x-decorator="FormItem" 105 | x-component="DraggerUpload" 106 | required 107 | /> 108 | </SchemaField> 109 | <FormButtonGroup.FormItem> 110 | <Submit onSubmit={console.log}>提交</Submit> 111 | </FormButtonGroup.FormItem> 112 | </FormLayout> 113 | </FormProvider> 114 | ) 115 | ``` 116 | 117 | ## JSON Schema 案例 118 | 119 | ```tsx 120 | import React from 'react' 121 | import { 122 | Upload, 123 | FormItem, 124 | FormLayout, 125 | FormButtonGroup, 126 | Submit, 127 | } from '@formily/antd' 128 | import { createForm } from '@formily/core' 129 | import { FormProvider, createSchemaField } from '@formily/react' 130 | import { Button } from 'antd' 131 | import { UploadOutlined, InboxOutlined } from '@ant-design/icons' 132 | 133 | const NormalUpload = (props) => { 134 | return ( 135 | <Upload 136 | {...props} 137 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 138 | headers={{ 139 | authorization: 'authorization-text', 140 | }} 141 | > 142 | <Button icon={<UploadOutlined />}>上传文件</Button> 143 | </Upload> 144 | ) 145 | } 146 | 147 | const CardUpload = (props) => { 148 | return ( 149 | <Upload 150 | {...props} 151 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 152 | listType="picture-card" 153 | headers={{ 154 | authorization: 'authorization-text', 155 | }} 156 | > 157 | <UploadOutlined style={{ fontSize: 20 }} /> 158 | </Upload> 159 | ) 160 | } 161 | 162 | const DraggerUpload = (props) => { 163 | return ( 164 | <Upload.Dragger 165 | {...props} 166 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 167 | > 168 | <p className="ant-upload-drag-icon"> 169 | <InboxOutlined /> 170 | </p> 171 | <p className="ant-upload-text"> 172 | Click or drag file to this area to upload 173 | </p> 174 | <p className="ant-upload-hint"> 175 | Support for a single or bulk upload. Strictly prohibit from uploading 176 | company data or other band files 177 | </p> 178 | </Upload.Dragger> 179 | ) 180 | } 181 | 182 | const SchemaField = createSchemaField({ 183 | components: { 184 | NormalUpload, 185 | CardUpload, 186 | DraggerUpload, 187 | FormItem, 188 | }, 189 | }) 190 | 191 | const form = createForm() 192 | 193 | const schema = { 194 | type: 'object', 195 | properties: { 196 | upload: { 197 | type: 'array', 198 | title: '上传', 199 | required: true, 200 | 'x-decorator': 'FormItem', 201 | 'x-component': 'NormalUpload', 202 | }, 203 | upload2: { 204 | type: 'array', 205 | title: '卡片上传', 206 | required: true, 207 | 'x-decorator': 'FormItem', 208 | 'x-component': 'CardUpload', 209 | }, 210 | upload3: { 211 | type: 'array', 212 | title: '拖拽上传', 213 | required: true, 214 | 'x-decorator': 'FormItem', 215 | 'x-component': 'DraggerUpload', 216 | }, 217 | }, 218 | } 219 | 220 | export default () => ( 221 | <FormProvider form={form}> 222 | <FormLayout labelCol={6} wrapperCol={10}> 223 | <SchemaField schema={schema} /> 224 | <FormButtonGroup.FormItem> 225 | <Submit onSubmit={console.log}>提交</Submit> 226 | </FormButtonGroup.FormItem> 227 | </FormLayout> 228 | </FormProvider> 229 | ) 230 | ``` 231 | 232 | ## 纯 JSX 案例 233 | 234 | ```tsx 235 | import React from 'react' 236 | import { 237 | Upload, 238 | FormItem, 239 | FormLayout, 240 | FormButtonGroup, 241 | Submit, 242 | } from '@formily/antd' 243 | import { createForm } from '@formily/core' 244 | import { FormProvider, Field } from '@formily/react' 245 | import { Button } from 'antd' 246 | import { UploadOutlined, InboxOutlined } from '@ant-design/icons' 247 | 248 | const NormalUpload = (props) => { 249 | return ( 250 | <Upload 251 | {...props} 252 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 253 | headers={{ 254 | authorization: 'authorization-text', 255 | }} 256 | > 257 | <Button icon={<UploadOutlined />}>上传文件</Button> 258 | </Upload> 259 | ) 260 | } 261 | 262 | const CardUpload = (props) => { 263 | return ( 264 | <Upload 265 | {...props} 266 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 267 | listType="picture-card" 268 | headers={{ 269 | authorization: 'authorization-text', 270 | }} 271 | > 272 | <UploadOutlined style={{ fontSize: 20 }} /> 273 | </Upload> 274 | ) 275 | } 276 | 277 | const DraggerUpload = (props) => { 278 | return ( 279 | <Upload.Dragger 280 | {...props} 281 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 282 | > 283 | <p className="ant-upload-drag-icon"> 284 | <InboxOutlined /> 285 | </p> 286 | <p className="ant-upload-text"> 287 | Click or drag file to this area to upload 288 | </p> 289 | <p className="ant-upload-hint"> 290 | Support for a single or bulk upload. Strictly prohibit from uploading 291 | company data or other band files 292 | </p> 293 | </Upload.Dragger> 294 | ) 295 | } 296 | 297 | const form = createForm() 298 | 299 | export default () => ( 300 | <FormProvider form={form}> 301 | <FormLayout labelCol={6} wrapperCol={10}> 302 | <Field 303 | name="upload" 304 | title="上传" 305 | required 306 | decorator={[FormItem]} 307 | component={[NormalUpload]} 308 | /> 309 | <Field 310 | name="upload2" 311 | title="卡片上传" 312 | required 313 | decorator={[FormItem]} 314 | component={[CardUpload]} 315 | /> 316 | <Field 317 | name="upload3" 318 | title="拖拽上传" 319 | required 320 | decorator={[FormItem]} 321 | component={[DraggerUpload]} 322 | /> 323 | <FormButtonGroup.FormItem> 324 | <Submit onSubmit={console.log}>提交</Submit> 325 | </FormButtonGroup.FormItem> 326 | </FormLayout> 327 | </FormProvider> 328 | ) 329 | ``` 330 | 331 | ## API 332 | 333 | 参考 https://ant.design/components/upload-cn/ 334 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Space.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Space 2 | 3 | > 超级便捷的 Flex 布局组件,可以帮助用户快速实现任何元素的并排紧挨布局 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Input, 11 | FormItem, 12 | FormLayout, 13 | FormButtonGroup, 14 | Submit, 15 | Space, 16 | } from '@formily/antd' 17 | import { createForm } from '@formily/core' 18 | import { FormProvider, createSchemaField } from '@formily/react' 19 | 20 | const SchemaField = createSchemaField({ 21 | components: { 22 | Input, 23 | FormItem, 24 | Space, 25 | }, 26 | }) 27 | 28 | const form = createForm() 29 | 30 | export default () => ( 31 | <FormProvider form={form}> 32 | <FormLayout labelCol={6} wrapperCol={16}> 33 | <SchemaField> 34 | <SchemaField.Void 35 | title="姓名" 36 | x-decorator="FormItem" 37 | x-decorator-props={{ 38 | asterisk: true, 39 | feedbackLayout: 'none', 40 | }} 41 | x-component="Space" 42 | > 43 | <SchemaField.String 44 | name="firstName" 45 | x-decorator="FormItem" 46 | x-component="Input" 47 | required 48 | /> 49 | <SchemaField.String 50 | name="lastName" 51 | x-decorator="FormItem" 52 | x-component="Input" 53 | required 54 | /> 55 | </SchemaField.Void> 56 | <SchemaField.Void 57 | title="文本串联" 58 | x-decorator="FormItem" 59 | x-decorator-props={{ 60 | asterisk: true, 61 | feedbackLayout: 'none', 62 | }} 63 | x-component="Space" 64 | > 65 | <SchemaField.String 66 | name="aa" 67 | x-decorator="FormItem" 68 | x-component="Input" 69 | x-decorator-props={{ 70 | addonAfter: '单位', 71 | }} 72 | required 73 | /> 74 | <SchemaField.String 75 | name="bb" 76 | x-decorator="FormItem" 77 | x-component="Input" 78 | x-decorator-props={{ 79 | addonAfter: '单位', 80 | }} 81 | required 82 | /> 83 | <SchemaField.String 84 | name="cc" 85 | x-decorator="FormItem" 86 | x-component="Input" 87 | x-decorator-props={{ 88 | addonAfter: '单位', 89 | }} 90 | required 91 | /> 92 | </SchemaField.Void> 93 | <SchemaField.String 94 | name="textarea" 95 | title="文本框" 96 | x-decorator="FormItem" 97 | required 98 | x-component="Input.TextArea" 99 | x-component-props={{ 100 | style: { 101 | width: 400, 102 | }, 103 | }} 104 | /> 105 | </SchemaField> 106 | <FormButtonGroup.FormItem> 107 | <Submit onSubmit={console.log}>提交</Submit> 108 | </FormButtonGroup.FormItem> 109 | </FormLayout> 110 | </FormProvider> 111 | ) 112 | ``` 113 | 114 | ## JSON Schema 案例 115 | 116 | ```tsx 117 | import React from 'react' 118 | import { 119 | Input, 120 | FormItem, 121 | FormLayout, 122 | FormButtonGroup, 123 | Submit, 124 | Space, 125 | } from '@formily/antd' 126 | import { createForm } from '@formily/core' 127 | import { FormProvider, createSchemaField } from '@formily/react' 128 | 129 | const SchemaField = createSchemaField({ 130 | components: { 131 | Input, 132 | FormItem, 133 | Space, 134 | }, 135 | }) 136 | 137 | const form = createForm() 138 | 139 | const schema = { 140 | type: 'object', 141 | properties: { 142 | name: { 143 | type: 'void', 144 | title: '姓名', 145 | 'x-decorator': 'FormItem', 146 | 'x-decorator-props': { 147 | asterisk: true, 148 | feedbackLayout: 'none', 149 | }, 150 | 'x-component': 'Space', 151 | properties: { 152 | firstName: { 153 | type: 'string', 154 | 'x-decorator': 'FormItem', 155 | 'x-component': 'Input', 156 | required: true, 157 | }, 158 | lastName: { 159 | type: 'string', 160 | 'x-decorator': 'FormItem', 161 | 'x-component': 'Input', 162 | required: true, 163 | }, 164 | }, 165 | }, 166 | texts: { 167 | type: 'void', 168 | title: '文本串联', 169 | 'x-decorator': 'FormItem', 170 | 'x-decorator-props': { 171 | asterisk: true, 172 | feedbackLayout: 'none', 173 | }, 174 | 'x-component': 'Space', 175 | properties: { 176 | aa: { 177 | type: 'string', 178 | 'x-decorator': 'FormItem', 179 | 'x-decorator-props': { 180 | addonAfter: '单位', 181 | }, 182 | 'x-component': 'Input', 183 | required: true, 184 | }, 185 | bb: { 186 | type: 'string', 187 | 'x-decorator': 'FormItem', 188 | 'x-decorator-props': { 189 | addonAfter: '单位', 190 | }, 191 | 'x-component': 'Input', 192 | required: true, 193 | }, 194 | cc: { 195 | type: 'string', 196 | 'x-decorator': 'FormItem', 197 | 'x-decorator-props': { 198 | addonAfter: '单位', 199 | }, 200 | 'x-component': 'Input', 201 | required: true, 202 | }, 203 | }, 204 | }, 205 | 206 | textarea: { 207 | type: 'string', 208 | title: '文本框', 209 | 'x-decorator': 'FormItem', 210 | 'x-component': 'Input.TextArea', 211 | 'x-component-props': { 212 | style: { 213 | width: 400, 214 | }, 215 | }, 216 | required: true, 217 | }, 218 | }, 219 | } 220 | 221 | export default () => ( 222 | <FormProvider form={form}> 223 | <FormLayout labelCol={6} wrapperCol={16}> 224 | <SchemaField schema={schema} /> 225 | <FormButtonGroup.FormItem> 226 | <Submit onSubmit={console.log}>提交</Submit> 227 | </FormButtonGroup.FormItem> 228 | </FormLayout> 229 | </FormProvider> 230 | ) 231 | ``` 232 | 233 | ## 纯 JSX 案例 234 | 235 | ```tsx 236 | import React from 'react' 237 | import { 238 | Input, 239 | FormItem, 240 | FormLayout, 241 | FormButtonGroup, 242 | Submit, 243 | Space, 244 | } from '@formily/antd' 245 | import { createForm } from '@formily/core' 246 | import { FormProvider, Field, VoidField } from '@formily/react' 247 | 248 | const form = createForm() 249 | 250 | export default () => ( 251 | <FormProvider form={form}> 252 | <FormLayout labelCol={6} wrapperCol={16}> 253 | <VoidField 254 | name="name" 255 | title="姓名" 256 | decorator={[ 257 | FormItem, 258 | { 259 | asterisk: true, 260 | feedbackLayout: 'none', 261 | }, 262 | ]} 263 | component={[Space]} 264 | > 265 | <Field 266 | name="firstName" 267 | decorator={[FormItem]} 268 | component={[Input]} 269 | required 270 | /> 271 | <Field 272 | name="lastName" 273 | decorator={[FormItem]} 274 | component={[Input]} 275 | required 276 | /> 277 | </VoidField> 278 | <VoidField 279 | name="texts" 280 | title="文本串联" 281 | decorator={[ 282 | FormItem, 283 | { 284 | asterisk: true, 285 | feedbackLayout: 'none', 286 | }, 287 | ]} 288 | component={[Space]} 289 | > 290 | <Field 291 | name="aa" 292 | decorator={[ 293 | FormItem, 294 | { 295 | addonAfter: '单位', 296 | }, 297 | ]} 298 | component={[Input]} 299 | required 300 | /> 301 | <Field 302 | name="bb" 303 | decorator={[ 304 | FormItem, 305 | { 306 | addonAfter: '单位', 307 | }, 308 | ]} 309 | component={[Input]} 310 | required 311 | /> 312 | <Field 313 | name="cc" 314 | decorator={[ 315 | FormItem, 316 | { 317 | addonAfter: '单位', 318 | }, 319 | ]} 320 | component={[Input]} 321 | required 322 | /> 323 | </VoidField> 324 | <Field 325 | name="textarea" 326 | title="文本框" 327 | decorator={[FormItem]} 328 | component={[ 329 | Input.TextArea, 330 | { 331 | style: { 332 | width: 400, 333 | }, 334 | }, 335 | ]} 336 | required 337 | /> 338 | <FormButtonGroup.FormItem> 339 | <Submit onSubmit={console.log}>提交</Submit> 340 | </FormButtonGroup.FormItem> 341 | </FormLayout> 342 | </FormProvider> 343 | ) 344 | ``` 345 | 346 | ## API 347 | 348 | 参考 https://ant.design/components/space-cn/ 349 | ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/compiler.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | compile, 3 | silent, 4 | registerCompiler, 5 | shallowCompile, 6 | patchCompile, 7 | patchSchemaCompile, 8 | } from '../compiler' 9 | import { Schema } from '../schema' 10 | 11 | test('compile', () => { 12 | expect(compile('{{123}}xx')).toEqual('{{123}}xx') 13 | expect(compile('{{123}} ')).toEqual(123) 14 | expect(compile('{{123}}')).toEqual(123) 15 | expect( 16 | compile({ 17 | hello: '{{123}}', 18 | }) 19 | ).toEqual({ 20 | hello: 123, 21 | }) 22 | expect( 23 | compile({ 24 | array: ['{{123}}'], 25 | }) 26 | ).toEqual({ 27 | array: [123], 28 | }) 29 | const date = new Date() 30 | date['expression'] = '{{123}}' 31 | const compiledDate = compile(date) 32 | expect(compiledDate).toEqual(date) 33 | expect(compiledDate['expression']).toEqual('{{123}}') 34 | const moment = { _isAMomentObject: true, expression: '{{123}}' } 35 | const compiledMoment = compile(moment) 36 | expect(compiledMoment).toEqual(moment) 37 | expect(compiledMoment['expression']).toEqual('{{123}}') 38 | const react = { _owner: true, $$typeof: true, expression: '{{123}}' } 39 | const compiledReact = compile(react) 40 | expect(compiledReact).toEqual(react) 41 | expect(compiledReact['expression']).toEqual('{{123}}') 42 | const actions = { 43 | [Symbol.for('__REVA_ACTIONS')]: true, 44 | expression: '{{123}}', 45 | } 46 | const compiledActions = compile(actions) 47 | expect(compiledActions).toEqual(actions) 48 | expect(compiledActions['expression']).toEqual('{{123}}') 49 | 50 | const schema = new Schema({ 51 | type: 'object', 52 | properties: { 53 | aa: { 54 | type: 'string', 55 | 'x-component': 'Input', 56 | 'x-component-props': '{{123}}', 57 | }, 58 | }, 59 | }) 60 | const compiledSchema = schema.compile() 61 | expect(compiledSchema.toJSON()).toEqual(schema.toJSON()) 62 | expect(compiledSchema.properties?.['aa']['x-component-props']).toEqual( 63 | '{{123}}' 64 | ) 65 | const toJSable = { 66 | toJS() { 67 | return { 68 | aa: 123, 69 | } 70 | }, 71 | expression: '{{123}}', 72 | } 73 | 74 | const compiledToJSable = compile(toJSable) 75 | expect(compiledToJSable).toEqual(toJSable) 76 | expect(compiledToJSable['expression']).toEqual('{{123}}') 77 | 78 | const toJSONable = { 79 | toJSON() { 80 | return { 81 | aa: 123, 82 | } 83 | }, 84 | expression: '{{123}}', 85 | } 86 | 87 | const compiledToJSONable = compile(toJSONable) 88 | expect(compiledToJSONable).toEqual(toJSONable) 89 | expect(compiledToJSONable['expression']).toEqual('{{123}}') 90 | 91 | const circularRef = { 92 | expression: '{{123}}', 93 | } 94 | circularRef['self'] = circularRef 95 | 96 | const compiledCircularRef = compile(circularRef) 97 | expect(compiledCircularRef['expression']).toEqual(123) 98 | }) 99 | 100 | test('shallowCompile', () => { 101 | expect(shallowCompile('{{123}}xx')).toEqual('{{123}}xx') 102 | expect(shallowCompile('{{123}} ')).toEqual(123) 103 | expect(shallowCompile('{{123}}')).toEqual(123) 104 | expect( 105 | shallowCompile({ 106 | hello: '{{123}}', 107 | }) 108 | ).toEqual({ 109 | hello: '{{123}}', 110 | }) 111 | expect( 112 | shallowCompile({ 113 | array: ['{{123}}'], 114 | }) 115 | ).toEqual({ 116 | array: ['{{123}}'], 117 | }) 118 | expect(shallowCompile(['{{123}}'])).toEqual(['{{123}}']) 119 | expect(shallowCompile([{ kk: '{{123}}' }])).toEqual([{ kk: '{{123}}' }]) 120 | }) 121 | 122 | test('unsilent', () => { 123 | silent(false) 124 | expect(() => compile('{{ ( }}')).toThrowError() 125 | }) 126 | 127 | test('silent', () => { 128 | silent(true) 129 | expect(() => compile('{{ ( }}')).not.toThrowError() 130 | silent(false) 131 | }) 132 | 133 | test('patchCompile', () => { 134 | const targetState = { 135 | title: '', 136 | description: '', 137 | dataSource: [22], 138 | } 139 | patchCompile( 140 | targetState as any, 141 | { 142 | title: '132', 143 | description: '{{"Hello world"}}', 144 | dataSource: [1, 2, 3, '{{333}}'], 145 | extend: '333', 146 | }, 147 | {} 148 | ) 149 | expect(targetState).toEqual({ 150 | title: '132', 151 | description: 'Hello world', 152 | dataSource: [1, 2, 3, 333], 153 | }) 154 | }) 155 | 156 | test('patchSchemaCompile', () => { 157 | const targetState = { 158 | title: '', 159 | description: '', 160 | dataSource: [22], 161 | } 162 | patchSchemaCompile( 163 | targetState as any, 164 | { 165 | title: '132', 166 | description: '{{"Hello world"}}', 167 | enum: [1, 2, 3, '{{333}}'], 168 | 'x-reactions': { 169 | fulfill: { 170 | schema: { 171 | title: 'hello', 172 | }, 173 | }, 174 | }, 175 | version: '1.2.3', 176 | }, 177 | {} 178 | ) 179 | expect(targetState).toEqual({ 180 | title: '132', 181 | description: 'Hello world', 182 | dataSource: [ 183 | { label: 1, value: 1 }, 184 | { label: 2, value: 2 }, 185 | { label: 3, value: 3 }, 186 | { label: 333, value: 333 }, 187 | ], 188 | }) 189 | }) 190 | 191 | test('patchSchemaCompile demand un initialized', () => { 192 | const setValidatorRule = (name: string, value: any) => { 193 | targetState[name] = value 194 | } 195 | const targetState = { 196 | title: '', 197 | description: '', 198 | dataSource: [22], 199 | setValidatorRule, 200 | } 201 | patchSchemaCompile( 202 | targetState as any, 203 | { 204 | title: '132', 205 | 'x-display': undefined, 206 | 'x-hidden': null, 207 | description: '{{"Hello world"}}', 208 | enum: [1, 2, 3, '{{333}}'], 209 | format: 'phone', 210 | 'x-reactions': { 211 | fulfill: { 212 | schema: { 213 | title: 'hello', 214 | }, 215 | }, 216 | }, 217 | version: '1.2.3', 218 | }, 219 | {}, 220 | true 221 | ) 222 | expect(targetState).toEqual({ 223 | title: '132', 224 | description: 'Hello world', 225 | hidden: null, 226 | format: 'phone', 227 | setValidatorRule, 228 | dataSource: [ 229 | { label: 1, value: 1 }, 230 | { label: 2, value: 2 }, 231 | { label: 3, value: 3 }, 232 | { label: 333, value: 333 }, 233 | ], 234 | }) 235 | }) 236 | 237 | test('patchSchemaCompile demand initialized', () => { 238 | const targetState = { 239 | initialized: true, 240 | title: '', 241 | description: '', 242 | dataSource: [22], 243 | } 244 | patchSchemaCompile( 245 | targetState as any, 246 | { 247 | title: '132', 248 | description: '{{"Hello world"}}', 249 | enum: [1, 2, 3, '{{333}}'], 250 | 'x-reactions': { 251 | fulfill: { 252 | schema: { 253 | title: 'hello', 254 | }, 255 | }, 256 | }, 257 | version: '1.2.3', 258 | }, 259 | {}, 260 | true 261 | ) 262 | expect(targetState).toEqual({ 263 | initialized: true, 264 | title: '', 265 | description: '', 266 | dataSource: [22], 267 | }) 268 | }) 269 | 270 | test('patchSchemaCompile x-compile-omitted', () => { 271 | const targetState = { 272 | title: '', 273 | validator: [], 274 | } 275 | patchSchemaCompile( 276 | targetState as any, 277 | { 278 | title: '132', 279 | 'x-validator': [ 280 | { 281 | remoteCheckUniq: '{{field.value}}', 282 | }, 283 | ], 284 | version: '1.2.3', 285 | }, 286 | { 287 | field: { 288 | value: 888, 289 | }, 290 | } 291 | ) 292 | expect(targetState).toEqual({ 293 | title: '132', 294 | validator: [{ remoteCheckUniq: 888 }], 295 | }) 296 | 297 | const targetOmitState = { 298 | title: '', 299 | validator: [], 300 | } 301 | patchSchemaCompile( 302 | targetOmitState as any, 303 | { 304 | title: '132', 305 | 'x-compile-omitted': ['x-validator'], 306 | 'x-validator': [ 307 | { 308 | remoteCheckUniq: '{{field.value}}', 309 | }, 310 | ], 311 | version: '1.2.3', 312 | }, 313 | { 314 | field: { 315 | value: 888, 316 | }, 317 | } 318 | ) 319 | expect(targetOmitState).toEqual({ 320 | title: '132', 321 | validator: [{ remoteCheckUniq: '{{field.value}}' }], 322 | }) 323 | }) 324 | 325 | test('registerCompiler', () => { 326 | registerCompiler(() => { 327 | return 'compiled' 328 | }) 329 | expect(compile('{{123}}xx')).toEqual('{{123}}xx') 330 | expect(compile('{{123}} ')).toEqual('compiled') 331 | expect(compile('{{123}}')).toEqual('compiled') 332 | expect( 333 | compile({ 334 | hello: '{{123}}', 335 | }) 336 | ).toEqual({ 337 | hello: 'compiled', 338 | }) 339 | expect( 340 | compile({ 341 | array: ['{{123}}'], 342 | }) 343 | ).toEqual({ 344 | array: ['compiled'], 345 | }) 346 | registerCompiler(null) 347 | }) 348 | ``` -------------------------------------------------------------------------------- /packages/vue/src/components/SchemaField.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { inject, provide, computed, shallowRef, watch } from 'vue-demi' 2 | import { ISchema, Schema, SchemaTypes } from '@formily/json-schema' 3 | import { RecursionField } from '../components' 4 | import { 5 | SchemaMarkupSymbol, 6 | SchemaExpressionScopeSymbol, 7 | SchemaOptionsSymbol, 8 | } from '../shared' 9 | import { 10 | ISchemaFieldVueFactoryOptions, 11 | SchemaVueComponents, 12 | ISchemaFieldProps, 13 | ISchemaMarkupFieldProps, 14 | ISchemaTypeFieldProps, 15 | } from '../types' 16 | import { resolveSchemaProps } from '../utils/resolveSchemaProps' 17 | import { h } from '../shared/h' 18 | import { Fragment } from '../shared/fragment' 19 | import type { DefineComponent } from '../types' 20 | import { lazyMerge } from '@formily/shared' 21 | 22 | type SchemaFieldComponents = { 23 | SchemaField: DefineComponent<ISchemaFieldProps> 24 | SchemaMarkupField: DefineComponent<ISchemaMarkupFieldProps> 25 | SchemaStringField: DefineComponent<ISchemaTypeFieldProps> 26 | SchemaObjectField: DefineComponent<ISchemaTypeFieldProps> 27 | SchemaArrayField: DefineComponent<ISchemaTypeFieldProps> 28 | SchemaBooleanField: DefineComponent<ISchemaTypeFieldProps> 29 | SchemaDateField: DefineComponent<ISchemaTypeFieldProps> 30 | SchemaDateTimeField: DefineComponent<ISchemaTypeFieldProps> 31 | SchemaVoidField: DefineComponent<ISchemaTypeFieldProps> 32 | SchemaNumberField: DefineComponent<ISchemaTypeFieldProps> 33 | } 34 | 35 | const env = { 36 | nonameId: 0, 37 | } 38 | 39 | const getRandomName = () => { 40 | return `NO_NAME_FIELD_$${env.nonameId++}` 41 | } 42 | 43 | const markupProps = { 44 | version: String, 45 | name: [String, Number], 46 | title: {}, 47 | description: {}, 48 | default: {}, 49 | readOnly: { 50 | type: Boolean, 51 | default: undefined, 52 | }, 53 | writeOnly: { 54 | type: Boolean, 55 | default: undefined, 56 | }, 57 | enum: {}, 58 | const: {}, 59 | multipleOf: Number, 60 | maximum: Number, 61 | exclusiveMaximum: Number, 62 | minimum: Number, 63 | exclusiveMinimum: Number, 64 | maxLength: Number, 65 | minLength: Number, 66 | pattern: {}, 67 | maxItems: Number, 68 | minItems: Number, 69 | uniqueItems: { 70 | type: Boolean, 71 | default: undefined, 72 | }, 73 | maxProperties: Number, 74 | minProperties: Number, 75 | required: { 76 | type: [Boolean, Array, String], 77 | default: undefined, 78 | }, 79 | format: String, 80 | properties: {}, 81 | items: {}, 82 | additionalItems: {}, 83 | patternProperties: {}, 84 | additionalProperties: {}, 85 | xIndex: Number, 86 | xPattern: {}, 87 | xDisplay: {}, 88 | xValidator: {}, 89 | xDecorator: {}, 90 | xDecoratorProps: {}, 91 | xComponent: {}, 92 | xComponentProps: {}, 93 | xReactions: {}, 94 | xContent: {}, 95 | xVisible: { 96 | type: Boolean, 97 | default: undefined, 98 | }, 99 | xHidden: { 100 | type: Boolean, 101 | default: undefined, 102 | }, 103 | xDisabled: { 104 | type: Boolean, 105 | default: undefined, 106 | }, 107 | xEditable: { 108 | type: Boolean, 109 | default: undefined, 110 | }, 111 | xReadOnly: { 112 | type: Boolean, 113 | default: undefined, 114 | }, 115 | xReadPretty: { 116 | type: Boolean, 117 | default: undefined, 118 | }, 119 | } 120 | 121 | export function createSchemaField< 122 | Components extends SchemaVueComponents = SchemaVueComponents 123 | >(options: ISchemaFieldVueFactoryOptions<Components> = {}): SchemaFieldComponents { 124 | const SchemaField = { 125 | name: 'SchemaField', 126 | inheritAttrs: false, 127 | props: { 128 | schema: {}, 129 | scope: {}, 130 | components: {}, 131 | name: [String, Number], 132 | basePath: {}, 133 | onlyRenderProperties: { type: Boolean, default: undefined }, 134 | onlyRenderSelf: { type: Boolean, default: undefined }, 135 | mapProperties: {}, 136 | filterProperties: {}, 137 | }, 138 | setup(props: ISchemaFieldProps, { slots }) { 139 | const schemaRef = computed(() => 140 | Schema.isSchemaInstance(props.schema) 141 | ? props.schema 142 | : new Schema({ 143 | type: 'object', 144 | ...props.schema, 145 | }) 146 | ) 147 | 148 | const scopeRef = computed(() => lazyMerge(options.scope, props.scope)) 149 | 150 | const optionsRef = computed(() => ({ 151 | ...options, 152 | components: { 153 | ...options.components, 154 | ...props.components, 155 | }, 156 | })) 157 | 158 | provide(SchemaMarkupSymbol, schemaRef) 159 | provide(SchemaOptionsSymbol, optionsRef) 160 | provide(SchemaExpressionScopeSymbol, scopeRef) 161 | 162 | return () => { 163 | env.nonameId = 0 164 | 165 | return h( 166 | Fragment, 167 | {}, 168 | { 169 | default: () => { 170 | const children = [] 171 | if (slots.default) { 172 | children.push( 173 | h( 174 | 'template', 175 | {}, 176 | { 177 | default: () => slots.default(), 178 | } 179 | ) 180 | ) 181 | } 182 | children.push( 183 | h( 184 | RecursionField, 185 | { 186 | attrs: { 187 | ...props, 188 | schema: schemaRef.value, 189 | }, 190 | }, 191 | {} 192 | ) 193 | ) 194 | return children 195 | }, 196 | } 197 | ) 198 | } 199 | }, 200 | } 201 | 202 | const MarkupField = { 203 | name: 'MarkupField', 204 | props: { 205 | type: String, 206 | ...markupProps, 207 | }, 208 | setup(props: ISchemaMarkupFieldProps, { slots }) { 209 | const parentRef = inject(SchemaMarkupSymbol, null) 210 | if (!parentRef || !parentRef.value) return () => h('template', {}, {}) 211 | 212 | const name = props.name || getRandomName() 213 | const appendArraySchema = (schema: ISchema) => { 214 | if (parentRef.value.items) { 215 | return parentRef.value.addProperty(name, schema) 216 | } else { 217 | return parentRef.value.setItems(resolveSchemaProps(props)) 218 | } 219 | } 220 | 221 | const schemaRef = shallowRef(null) 222 | 223 | watch( 224 | parentRef, 225 | () => { 226 | if ( 227 | parentRef.value.type === 'object' || 228 | parentRef.value.type === 'void' 229 | ) { 230 | schemaRef.value = parentRef.value.addProperty( 231 | name, 232 | resolveSchemaProps(props) 233 | ) 234 | } else if (parentRef.value.type === 'array') { 235 | const schema = appendArraySchema(resolveSchemaProps(props)) 236 | schemaRef.value = Array.isArray(schema) ? schema[0] : schema 237 | } 238 | }, 239 | { immediate: true } 240 | ) 241 | provide(SchemaMarkupSymbol, schemaRef) 242 | 243 | return () => { 244 | return h('div', { style: 'display: none;' }, slots) 245 | } 246 | }, 247 | } 248 | 249 | const SchemaFieldFactory = (type: SchemaTypes, name: string) => { 250 | return { 251 | name: name, 252 | props: { ...markupProps }, 253 | setup(props: ISchemaTypeFieldProps, { slots }) { 254 | return () => 255 | h( 256 | MarkupField, 257 | { 258 | attrs: { 259 | ...props, 260 | type: type, 261 | }, 262 | }, 263 | slots 264 | ) 265 | }, 266 | } 267 | } 268 | 269 | return { 270 | SchemaField, 271 | SchemaMarkupField: MarkupField, 272 | SchemaStringField: SchemaFieldFactory('string', 'SchemaStringField'), 273 | SchemaObjectField: SchemaFieldFactory('object', 'SchemaObjectField'), 274 | SchemaArrayField: SchemaFieldFactory('array', 'SchemaArrayField'), 275 | SchemaBooleanField: SchemaFieldFactory('boolean', 'SchemaBooleanField'), 276 | SchemaDateField: SchemaFieldFactory('date', 'SchemaDateField'), 277 | SchemaDateTimeField: SchemaFieldFactory('datetime', 'SchemaDatetimeField'), 278 | SchemaVoidField: SchemaFieldFactory('void', 'SchemaVoidField'), 279 | SchemaNumberField: SchemaFieldFactory('number', 'SchemaNumberField'), 280 | } as unknown as SchemaFieldComponents 281 | } 282 | ``` -------------------------------------------------------------------------------- /docs/guide/form-builder.md: -------------------------------------------------------------------------------- ```markdown 1 | # Form designer development guide 2 | 3 | ## Introduction 4 | 5 |  6 | 7 | Formily Form Designer is an extension package based on [designable](https://github.com/alibaba/designable). It inherits the basic capabilities of designable, and provides Formily basic form building and configuration capabilities. 8 | 9 | ## Core Concept 10 | 11 | The core concept of Designable is to turn the designer into a modular combination, everything can be replaced, Designable itself provides a series of out-of-the-box components for users to use, but if users are not satisfied with the components, they can directly replace the components. To achieve maximum flexible customization, that is, Designable itself does not provide any plug-in related APIs 12 | 13 | ## Install 14 | 15 | Ant Design users 16 | 17 | ```bash 18 | npm install --save @designable/formily-antd 19 | ``` 20 | 21 | Alibaba Fusion users 22 | 23 | ```bash 24 | npm install --save @designable/formily-next 25 | ``` 26 | 27 | ## Get started quickly 28 | 29 | [Example Source Code](https://github.com/alibaba/designable/tree/main/formily/antd/playground) 30 | 31 | ```tsx pure 32 | import 'antd/dist/antd.less' 33 | import React, { useMemo } from 'react' 34 | import ReactDOM from 'react-dom' 35 | import { 36 | Designer, //Designer root component, mainly used to deliver context 37 | DesignerToolsWidget, //Drawing board tool pendant 38 | ViewToolsWidget, //View switching tool pendant 39 | Workspace, //Workspace components, core components, used to manage drag and drop behavior in the workspace, tree node data, etc... 40 | OutlineTreeWidget, //Outline tree component, it will automatically identify the current workspace and display the tree nodes in the workspace 41 | ResourceWidget, //Drag and drop the source widget 42 | HistoryWidget, //History widget 43 | StudioPanel, //Main layout panel 44 | CompositePanel, //Left combined layout panel 45 | WorkspacePanel, //Workspace layout panel 46 | ToolbarPanel, //Toolbar layout panel 47 | ViewportPanel, //Viewport layout panel 48 | ViewPanel, //View layout panel 49 | SettingsPanel, //Configure the form layout panel on the right 50 | ComponentTreeWidget, //Component tree renderer 51 | } from '@designable/react' 52 | import { SettingsForm } from '@designable/react-settings-form' 53 | import { 54 | createDesigner, 55 | GlobalRegistry, 56 | Shortcut, 57 | KeyCode, 58 | } from '@designable/core' 59 | import { 60 | LogoWidget, 61 | ActionsWidget, 62 | PreviewWidget, 63 | SchemaEditorWidget, 64 | MarkupSchemaWidget, 65 | } from './widgets' 66 | import { saveSchema } from './service' 67 | import { 68 | Form, 69 | Field, 70 | Input, 71 | Select, 72 | TreeSelect, 73 | Cascader, 74 | Radio, 75 | Checkbox, 76 | Slider, 77 | Rate, 78 | NumberPicker, 79 | Transfer, 80 | Password, 81 | DatePicker, 82 | TimePicker, 83 | Upload, 84 | Switch, 85 | Text, 86 | Card, 87 | ArrayCards, 88 | ObjectContainer, 89 | ArrayTable, 90 | Space, 91 | FormTab, 92 | FormCollapse, 93 | FormLayout, 94 | FormGrid, 95 | } from '../src' 96 | 97 | GlobalRegistry.registerDesignerLocales({ 98 | 'zh-CN': { 99 | sources: { 100 | Inputs: 'Input controls', 101 | Layouts: 'Layout components', 102 | Arrays: 'Self-incrementing components', 103 | Displays: 'Display components', 104 | }, 105 | }, 106 | 'en-US': { 107 | sources: { 108 | Inputs: 'Inputs', 109 | Layouts: 'Layouts', 110 | Arrays: 'Arrays', 111 | Displays: 'Displays', 112 | }, 113 | }, 114 | }) 115 | 116 | const App = () => { 117 | const engine = useMemo( 118 | () => 119 | createDesigner({ 120 | shortcuts: [ 121 | new Shortcut({ 122 | codes: [ 123 | [KeyCode.Meta, KeyCode.S], 124 | [KeyCode.Control, KeyCode.S], 125 | ], 126 | handler(ctx) { 127 | saveSchema(ctx.engine) 128 | }, 129 | }), 130 | ], 131 | rootComponentName: 'Form', 132 | }), 133 | [] 134 | ) 135 | return ( 136 | <Designer engine={engine}> 137 | <StudioPanel logo={<LogoWidget />} actions={<ActionsWidget />}> 138 | <CompositePanel> 139 | <CompositePanel.Item title="panels.Component" icon="Component"> 140 | <ResourceWidget 141 | title="sources.Inputs" 142 | sources={[ 143 | Input, 144 | Password, 145 | NumberPicker, 146 | Rate, 147 | Slider, 148 | Select, 149 | TreeSelect, 150 | Cascader, 151 | Transfer, 152 | Checkbox, 153 | Radio, 154 | DatePicker, 155 | TimePicker, 156 | Upload, 157 | Switch, 158 | ObjectContainer, 159 | ]} 160 | /> 161 | <ResourceWidget 162 | title="sources.Layouts" 163 | sources={[ 164 | Card, 165 | FormGrid, 166 | FormTab, 167 | FormLayout, 168 | FormCollapse, 169 | Space, 170 | ]} 171 | /> 172 | <ResourceWidget 173 | title="sources.Arrays" 174 | sources={[ArrayCards, ArrayTable]} 175 | /> 176 | <ResourceWidget title="sources.Displays" sources={[Text]} /> 177 | </CompositePanel.Item> 178 | <CompositePanel.Item title="panels.OutlinedTree" icon="Outline"> 179 | <OutlineTreeWidget /> 180 | </CompositePanel.Item> 181 | <CompositePanel.Item title="panels.History" icon="History"> 182 | <HistoryWidget /> 183 | </CompositePanel.Item> 184 | </CompositePanel> 185 | <Workspace id="form"> 186 | <WorkspacePanel> 187 | <ToolbarPanel> 188 | <DesignerToolsWidget /> 189 | <ViewToolsWidget 190 | use={['DESIGNABLE', 'JSONTREE', 'MARKUP', 'PREVIEW']} 191 | /> 192 | </ToolbarPanel> 193 | <ViewportPanel> 194 | <ViewPanel type="DESIGNABLE"> 195 | {() => ( 196 | <ComponentTreeWidget 197 | components={{ 198 | Form, 199 | Field, 200 | Input, 201 | Select, 202 | TreeSelect, 203 | Cascader, 204 | Radio, 205 | Checkbox, 206 | Slider, 207 | Rate, 208 | NumberPicker, 209 | Transfer, 210 | Password, 211 | DatePicker, 212 | TimePicker, 213 | Upload, 214 | Switch, 215 | Text, 216 | Card, 217 | ArrayCards, 218 | ArrayTable, 219 | Space, 220 | FormTab, 221 | FormCollapse, 222 | FormGrid, 223 | FormLayout, 224 | ObjectContainer, 225 | }} 226 | /> 227 | )} 228 | </ViewPanel> 229 | <ViewPanel type="JSONTREE" scrollable={false}> 230 | {(tree, onChange) => ( 231 | <SchemaEditorWidget tree={tree} onChange={onChange} /> 232 | )} 233 | </ViewPanel> 234 | <ViewPanel type="MARKUP" scrollable={false}> 235 | {(tree) => <MarkupSchemaWidget tree={tree} />} 236 | </ViewPanel> 237 | <ViewPanel type="PREVIEW"> 238 | {(tree) => <PreviewWidget tree={tree} />} 239 | </ViewPanel> 240 | </ViewportPanel> 241 | </WorkspacePanel> 242 | </Workspace> 243 | <SettingsPanel title="panels.PropertySettings"> 244 | <SettingsForm uploadAction="https://www.mocky.io/v2/5cc8019d300000980a055e76" /> 245 | </SettingsPanel> 246 | </StudioPanel> 247 | </Designer> 248 | ) 249 | } 250 | 251 | ReactDOM.render(<App />, document.getElementById('root')) 252 | ``` 253 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormDrawer.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormDrawer 2 | 3 | > 抽屉表单,主要用在简单的事件打开表单场景 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | FormDrawer, 11 | FormItem, 12 | FormLayout, 13 | Input, 14 | Submit, 15 | Reset, 16 | FormButtonGroup, 17 | } from '@formily/antd' 18 | import { createSchemaField } from '@formily/react' 19 | import { Button } from 'antd' 20 | 21 | const SchemaField = createSchemaField({ 22 | components: { 23 | FormItem, 24 | Input, 25 | }, 26 | }) 27 | 28 | export default () => { 29 | return ( 30 | <Button 31 | onClick={() => { 32 | FormDrawer('抽屉表单', () => { 33 | return ( 34 | <FormLayout labelCol={6} wrapperCol={10}> 35 | <SchemaField> 36 | <SchemaField.String 37 | name="aaa" 38 | required 39 | title="输入框1" 40 | x-decorator="FormItem" 41 | x-component="Input" 42 | /> 43 | <SchemaField.String 44 | name="bbb" 45 | required 46 | title="输入框2" 47 | x-decorator="FormItem" 48 | x-component="Input" 49 | /> 50 | <SchemaField.String 51 | name="ccc" 52 | required 53 | title="输入框3" 54 | x-decorator="FormItem" 55 | x-component="Input" 56 | /> 57 | <SchemaField.String 58 | name="ddd" 59 | required 60 | title="输入框4" 61 | x-decorator="FormItem" 62 | x-component="Input" 63 | /> 64 | </SchemaField> 65 | <FormDrawer.Extra> 66 | <FormButtonGroup align="right"> 67 | <Submit 68 | onSubmit={() => { 69 | return new Promise((resolve) => { 70 | setTimeout(resolve, 1000) 71 | }) 72 | }} 73 | > 74 | 提交 75 | </Submit> 76 | <Reset>重置</Reset> 77 | </FormButtonGroup> 78 | </FormDrawer.Extra> 79 | </FormLayout> 80 | ) 81 | }) 82 | .forOpen((props, next) => { 83 | setTimeout(() => { 84 | next() 85 | }, 1000) 86 | }) 87 | .open({ 88 | initialValues: { 89 | aaa: '123', 90 | }, 91 | }) 92 | .then(console.log) 93 | }} 94 | > 95 | 点我打开表单 96 | </Button> 97 | ) 98 | } 99 | ``` 100 | 101 | ## JSON Schema 案例 102 | 103 | ```tsx 104 | import React from 'react' 105 | import { 106 | FormDrawer, 107 | FormItem, 108 | FormLayout, 109 | Input, 110 | Submit, 111 | Reset, 112 | FormButtonGroup, 113 | } from '@formily/antd' 114 | import { createSchemaField } from '@formily/react' 115 | import { Button } from 'antd' 116 | 117 | const SchemaField = createSchemaField({ 118 | components: { 119 | FormItem, 120 | Input, 121 | }, 122 | }) 123 | 124 | const schema = { 125 | type: 'object', 126 | properties: { 127 | aaa: { 128 | type: 'string', 129 | title: '输入框1', 130 | required: true, 131 | 'x-decorator': 'FormItem', 132 | 'x-component': 'Input', 133 | }, 134 | bbb: { 135 | type: 'string', 136 | title: '输入框2', 137 | required: true, 138 | 'x-decorator': 'FormItem', 139 | 'x-component': 'Input', 140 | }, 141 | ccc: { 142 | type: 'string', 143 | title: '输入框3', 144 | required: true, 145 | 'x-decorator': 'FormItem', 146 | 'x-component': 'Input', 147 | }, 148 | ddd: { 149 | type: 'string', 150 | title: '输入框4', 151 | required: true, 152 | 'x-decorator': 'FormItem', 153 | 'x-component': 'Input', 154 | }, 155 | }, 156 | } 157 | 158 | export default () => { 159 | return ( 160 | <Button 161 | onClick={() => { 162 | FormDrawer('弹窗表单', () => { 163 | return ( 164 | <FormLayout labelCol={6} wrapperCol={10}> 165 | <SchemaField schema={schema} /> 166 | <FormDrawer.Extra> 167 | <FormButtonGroup align="right"> 168 | <Submit 169 | onSubmit={() => { 170 | return new Promise((resolve) => { 171 | setTimeout(resolve, 1000) 172 | }) 173 | }} 174 | > 175 | 提交 176 | </Submit> 177 | <Reset>重置</Reset> 178 | </FormButtonGroup> 179 | </FormDrawer.Extra> 180 | </FormLayout> 181 | ) 182 | }) 183 | .open({ 184 | initialValues: { 185 | aaa: '123', 186 | }, 187 | }) 188 | .then(console.log) 189 | }} 190 | > 191 | 点我打开表单 192 | </Button> 193 | ) 194 | } 195 | ``` 196 | 197 | ## 纯 JSX 案例 198 | 199 | ```tsx 200 | import React from 'react' 201 | import { 202 | FormDrawer, 203 | FormItem, 204 | FormLayout, 205 | Input, 206 | Submit, 207 | Reset, 208 | FormButtonGroup, 209 | } from '@formily/antd' 210 | import { Field } from '@formily/react' 211 | import { Button } from 'antd' 212 | 213 | export default () => { 214 | return ( 215 | <Button 216 | onClick={() => { 217 | FormDrawer('弹窗表单', () => { 218 | return ( 219 | <FormLayout labelCol={6} wrapperCol={10}> 220 | <Field 221 | name="aaa" 222 | required 223 | title="输入框1" 224 | decorator={[FormItem]} 225 | component={[Input]} 226 | /> 227 | <Field 228 | name="bbb" 229 | required 230 | title="输入框2" 231 | decorator={[FormItem]} 232 | component={[Input]} 233 | /> 234 | <Field 235 | name="ccc" 236 | required 237 | title="输入框3" 238 | decorator={[FormItem]} 239 | component={[Input]} 240 | /> 241 | <Field 242 | name="ddd" 243 | required 244 | title="输入框4" 245 | decorator={[FormItem]} 246 | component={[Input]} 247 | /> 248 | <FormDrawer.Extra> 249 | <FormButtonGroup align="right"> 250 | <Submit 251 | onSubmit={() => { 252 | return new Promise((resolve) => { 253 | setTimeout(resolve, 1000) 254 | }) 255 | }} 256 | > 257 | 提交 258 | </Submit> 259 | <Reset>重置</Reset> 260 | </FormButtonGroup> 261 | </FormDrawer.Extra> 262 | </FormLayout> 263 | ) 264 | }) 265 | .open({ 266 | initialValues: { 267 | aaa: '123', 268 | }, 269 | }) 270 | .then(console.log) 271 | }} 272 | > 273 | 点我打开表单 274 | </Button> 275 | ) 276 | } 277 | ``` 278 | 279 | ## API 280 | 281 | ### FormDrawer 282 | 283 | ```ts pure 284 | import { IFormProps, Form } from '@formily/core' 285 | 286 | type FormDrawerRenderer = 287 | | React.ReactElement 288 | | ((form: Form) => React.ReactElement) 289 | 290 | interface IFormDrawer { 291 | forOpen( 292 | middleware: ( 293 | props: IFormProps, 294 | next: (props?: IFormProps) => Promise<any> 295 | ) => any 296 | ): any //中间件拦截器,可以拦截Drawer打开 297 | //打开弹窗,接收表单属性,可以传入initialValues/values/effects etc. 298 | open(props: IFormProps): Promise<any> //返回表单数据 299 | //关闭弹窗 300 | close(): void 301 | } 302 | 303 | export interface IDrawerProps extends DrawerProps { 304 | onClose?: (e: EventType) => void | boolean // return false can prevent onClose 305 | loadingText?: React.ReactNode 306 | } 307 | 308 | interface FormDrawer { 309 | (title: IDrawerProps, id: string, renderer: FormDrawerRenderer): IFormDrawer 310 | (title: IDrawerProps, renderer: FormDrawerRenderer): IFormDrawer 311 | (title: ModalTitle, id: string, renderer: FormDrawerRenderer): IFormDrawer 312 | (title: ModalTitle, renderer: FormDrawerRenderer): IFormDrawer 313 | } 314 | ``` 315 | 316 | `DrawerProps`类型定义参考 ant design [Drawer API](https://ant.design/components/drawer-cn/#API) 317 | 318 | ### FormDrawer.Extra 319 | 320 | 无属性,只接收子节点 321 | 322 | ### FormDrawer.Footer 323 | 324 | 无属性,只接收子节点 325 | 326 | ### FormDrawer.Portal 327 | 328 | 接收可选的 id 属性,默认值为`form-drawer`,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id 329 | ``` -------------------------------------------------------------------------------- /packages/antd/src/array-collapse/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useState, useEffect } from 'react' 2 | import { 3 | Badge, 4 | Card, 5 | Collapse, 6 | CollapsePanelProps, 7 | CollapseProps, 8 | Empty, 9 | } from 'antd' 10 | import { ArrayField } from '@formily/core' 11 | import { 12 | RecursionField, 13 | useField, 14 | useFieldSchema, 15 | observer, 16 | ISchema, 17 | } from '@formily/react' 18 | import { toArr } from '@formily/shared' 19 | import cls from 'classnames' 20 | import ArrayBase, { ArrayBaseMixins, IArrayBaseProps } from '../array-base' 21 | import { usePrefixCls } from '../__builtins__' 22 | 23 | export interface IArrayCollapseProps extends CollapseProps { 24 | defaultOpenPanelCount?: number 25 | } 26 | type ComposedArrayCollapse = React.FC< 27 | React.PropsWithChildren<IArrayCollapseProps & IArrayBaseProps> 28 | > & 29 | ArrayBaseMixins & { 30 | CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>> 31 | } 32 | 33 | const isAdditionComponent = (schema: ISchema) => { 34 | return schema['x-component']?.indexOf?.('Addition') > -1 35 | } 36 | 37 | const isIndexComponent = (schema: ISchema) => { 38 | return schema['x-component']?.indexOf?.('Index') > -1 39 | } 40 | 41 | const isRemoveComponent = (schema: ISchema) => { 42 | return schema['x-component']?.indexOf?.('Remove') > -1 43 | } 44 | 45 | const isMoveUpComponent = (schema: ISchema) => { 46 | return schema['x-component']?.indexOf?.('MoveUp') > -1 47 | } 48 | 49 | const isMoveDownComponent = (schema: ISchema) => { 50 | return schema['x-component']?.indexOf?.('MoveDown') > -1 51 | } 52 | 53 | const isOperationComponent = (schema: ISchema) => { 54 | return ( 55 | isAdditionComponent(schema) || 56 | isRemoveComponent(schema) || 57 | isMoveDownComponent(schema) || 58 | isMoveUpComponent(schema) 59 | ) 60 | } 61 | 62 | const range = (count: number) => Array.from({ length: count }).map((_, i) => i) 63 | 64 | const takeDefaultActiveKeys = ( 65 | dataSourceLength: number, 66 | defaultOpenPanelCount: number 67 | ) => { 68 | if (dataSourceLength < defaultOpenPanelCount) return range(dataSourceLength) 69 | return range(defaultOpenPanelCount) 70 | } 71 | 72 | const insertActiveKeys = (activeKeys: number[], index: number) => { 73 | if (activeKeys.length <= index) return activeKeys.concat(index) 74 | return activeKeys.reduce((buf, key) => { 75 | if (key < index) return buf.concat(key) 76 | if (key === index) return buf.concat([key, key + 1]) 77 | return buf.concat(key + 1) 78 | }, []) 79 | } 80 | 81 | export const ArrayCollapse: ComposedArrayCollapse = observer( 82 | ({ defaultOpenPanelCount = 5, ...props }) => { 83 | const field = useField<ArrayField>() 84 | const dataSource = Array.isArray(field.value) ? field.value : [] 85 | const [activeKeys, setActiveKeys] = useState<number[]>( 86 | takeDefaultActiveKeys(dataSource.length, defaultOpenPanelCount) 87 | ) 88 | const schema = useFieldSchema() 89 | const prefixCls = usePrefixCls('formily-array-collapse', props) 90 | useEffect(() => { 91 | if (!field.modified && dataSource.length) { 92 | setActiveKeys( 93 | takeDefaultActiveKeys(dataSource.length, defaultOpenPanelCount) 94 | ) 95 | } 96 | }, [dataSource.length, field]) 97 | if (!schema) throw new Error('can not found schema object') 98 | const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props 99 | 100 | const renderAddition = () => { 101 | return schema.reduceProperties((addition, schema, key) => { 102 | if (isAdditionComponent(schema)) { 103 | return <RecursionField schema={schema} name={key} /> 104 | } 105 | return addition 106 | }, null) 107 | } 108 | const renderEmpty = () => { 109 | if (dataSource.length) return 110 | return ( 111 | <Card className={cls(`${prefixCls}-item`, props.className)}> 112 | <Empty /> 113 | </Card> 114 | ) 115 | } 116 | 117 | const renderItems = () => { 118 | return ( 119 | <Collapse 120 | {...props} 121 | activeKey={activeKeys} 122 | onChange={(keys: string[]) => setActiveKeys(toArr(keys).map(Number))} 123 | className={cls(`${prefixCls}-item`, props.className)} 124 | > 125 | {dataSource.map((item, index) => { 126 | const items = Array.isArray(schema.items) 127 | ? schema.items[index] || schema.items[0] 128 | : schema.items 129 | 130 | const panelProps = field 131 | .query(`${field.address}.${index}`) 132 | .get('componentProps') 133 | const props: CollapsePanelProps = items['x-component-props'] 134 | const header = () => { 135 | const header = panelProps?.header || props.header || field.title 136 | const path = field.address.concat(index) 137 | const errors = field.form.queryFeedbacks({ 138 | type: 'error', 139 | address: `${path}.**`, 140 | }) 141 | return ( 142 | <ArrayBase.Item 143 | index={index} 144 | record={() => field.value?.[index]} 145 | > 146 | <RecursionField 147 | schema={items} 148 | name={index} 149 | filterProperties={(schema) => { 150 | if (!isIndexComponent(schema)) return false 151 | return true 152 | }} 153 | onlyRenderProperties 154 | /> 155 | {errors.length ? ( 156 | <Badge 157 | size="small" 158 | className="errors-badge" 159 | count={errors.length} 160 | > 161 | {header} 162 | </Badge> 163 | ) : ( 164 | header 165 | )} 166 | </ArrayBase.Item> 167 | ) 168 | } 169 | 170 | const extra = ( 171 | <ArrayBase.Item index={index} record={item}> 172 | <RecursionField 173 | schema={items} 174 | name={index} 175 | filterProperties={(schema) => { 176 | if (!isOperationComponent(schema)) return false 177 | return true 178 | }} 179 | onlyRenderProperties 180 | /> 181 | {panelProps?.extra} 182 | </ArrayBase.Item> 183 | ) 184 | 185 | const content = ( 186 | <RecursionField 187 | schema={items} 188 | name={index} 189 | filterProperties={(schema) => { 190 | if (isIndexComponent(schema)) return false 191 | if (isOperationComponent(schema)) return false 192 | return true 193 | }} 194 | /> 195 | ) 196 | return ( 197 | <Collapse.Panel 198 | {...props} 199 | {...panelProps} 200 | forceRender 201 | key={index} 202 | header={header()} 203 | extra={extra} 204 | > 205 | <ArrayBase.Item index={index} key={index} record={item}> 206 | {content} 207 | </ArrayBase.Item> 208 | </Collapse.Panel> 209 | ) 210 | })} 211 | </Collapse> 212 | ) 213 | } 214 | return ( 215 | <ArrayBase 216 | onAdd={(index) => { 217 | onAdd?.(index) 218 | setActiveKeys(insertActiveKeys(activeKeys, index)) 219 | }} 220 | onCopy={onCopy} 221 | onRemove={onRemove} 222 | onMoveUp={onMoveUp} 223 | onMoveDown={onMoveDown} 224 | > 225 | {renderEmpty()} 226 | {renderItems()} 227 | {renderAddition()} 228 | </ArrayBase> 229 | ) 230 | } 231 | ) 232 | 233 | const CollapsePanel: React.FC<React.PropsWithChildren<CollapsePanelProps>> = ({ 234 | children, 235 | }) => { 236 | return <Fragment>{children}</Fragment> 237 | } 238 | 239 | CollapsePanel.displayName = 'CollapsePanel' 240 | 241 | ArrayCollapse.displayName = 'ArrayCollapse' 242 | ArrayCollapse.CollapsePanel = CollapsePanel 243 | 244 | ArrayBase.mixin(ArrayCollapse) 245 | 246 | export default ArrayCollapse 247 | ``` -------------------------------------------------------------------------------- /packages/next/src/form-dialog/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useRef, useLayoutEffect, useState } from 'react' 2 | import { createPortal } from 'react-dom' 3 | import { createForm, IFormProps, Form } from '@formily/core' 4 | import { toJS } from '@formily/reactive' 5 | import { FormProvider, Observer, observer, ReactFC } from '@formily/react' 6 | import { 7 | isNum, 8 | isStr, 9 | isBool, 10 | isFn, 11 | applyMiddleware, 12 | IMiddleware, 13 | } from '@formily/shared' 14 | import { Dialog, ConfigProvider, Button } from '@alifd/next' 15 | import { DialogProps } from '@alifd/next/lib/dialog' 16 | import { 17 | usePrefixCls, 18 | loading, 19 | createPortalProvider, 20 | createPortalRoot, 21 | } from '../__builtins__' 22 | 23 | type FormDialogRenderer = 24 | | React.ReactElement 25 | | ((form: Form) => React.ReactElement) 26 | 27 | type ModalTitle = string | number | React.ReactElement 28 | 29 | const getContext: () => any = ConfigProvider['getContext'] 30 | 31 | const isModalTitle = (props: any): props is ModalTitle => { 32 | return ( 33 | isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props) 34 | ) 35 | } 36 | 37 | const getModelProps = (props: any): IDialogProps => { 38 | if (isModalTitle(props)) { 39 | return { 40 | title: props, 41 | } 42 | } else { 43 | return props 44 | } 45 | } 46 | 47 | export interface IDialogProps extends DialogProps { 48 | onOk?: (event: React.MouseEvent) => void | boolean 49 | onCancel?: (event: React.MouseEvent) => void | boolean 50 | loadingText?: React.ReactText 51 | } 52 | 53 | export interface IFormDialog { 54 | forOpen(middleware: IMiddleware<IFormProps>): IFormDialog 55 | forConfirm(middleware: IMiddleware<Form>): IFormDialog 56 | forCancel(middleware: IMiddleware<Form>): IFormDialog 57 | open(props?: IFormProps): Promise<any> 58 | close(): void 59 | } 60 | 61 | export function FormDialog( 62 | title: IDialogProps, 63 | id: string, 64 | renderer: FormDialogRenderer 65 | ): IFormDialog 66 | export function FormDialog( 67 | title: IDialogProps, 68 | renderer: FormDialogRenderer 69 | ): IFormDialog 70 | export function FormDialog( 71 | title: ModalTitle, 72 | id: string, 73 | renderer: FormDialogRenderer 74 | ): IFormDialog 75 | export function FormDialog( 76 | title: ModalTitle, 77 | renderer: FormDialogRenderer 78 | ): IFormDialog 79 | export function FormDialog(title: any, id: any, renderer?: any): IFormDialog { 80 | if (isFn(id) || React.isValidElement(id)) { 81 | renderer = id 82 | id = 'form-dialog' 83 | } 84 | const env = { 85 | host: document.createElement('div'), 86 | form: null, 87 | promise: null, 88 | openMiddlewares: [], 89 | confirmMiddlewares: [], 90 | cancelMiddlewares: [], 91 | } 92 | const root = createPortalRoot(env.host, id) 93 | const props = getModelProps(title) 94 | const modal = { 95 | ...props, 96 | style: { 97 | width: '40%', 98 | ...props.style, 99 | }, 100 | afterClose: () => { 101 | props?.afterClose?.() 102 | root.unmount() 103 | }, 104 | } 105 | const DialogContent = observer(() => { 106 | return <Fragment>{isFn(renderer) ? renderer(env.form) : renderer}</Fragment> 107 | }) 108 | const renderDialog = ( 109 | visible = true, 110 | resolve?: () => any, 111 | reject?: () => any 112 | ) => { 113 | const ctx = getContext() 114 | const prefix = modal.prefix || ctx.prefix || 'next' 115 | const okProps = { 116 | children: ctx?.locale?.Dialog?.ok || '确定', 117 | ...(ctx?.defaultPropsConfig?.Dialog?.okProps || {}), 118 | ...(modal.okProps || {}), 119 | } 120 | const cancelProps = { 121 | children: ctx?.locale?.Dialog?.cancel || '取消', 122 | ...(ctx?.defaultPropsConfig?.Dialog?.cancelProps || {}), 123 | ...(modal.cancelProps || {}), 124 | } 125 | const buttonMap = { 126 | ok: ( 127 | <Button 128 | key="ok" 129 | type="primary" 130 | className={prefix + '-dialog-btn'} 131 | loading={env.form.submitting} 132 | onClick={(e) => { 133 | if (modal?.onOk?.(e) !== false) { 134 | resolve() 135 | } 136 | }} 137 | {...okProps} 138 | /> 139 | ), 140 | cancel: ( 141 | <Button 142 | key="cancel" 143 | className={prefix + '-dialog-btn'} 144 | onClick={(e) => { 145 | if (modal?.onCancel?.(e) !== false) { 146 | reject() 147 | } 148 | }} 149 | {...cancelProps} 150 | /> 151 | ), 152 | } 153 | const footerActions = ctx?.defaultPropsConfig?.Dialog?.footerActions || 154 | modal.footerActions || ['cancel', 'ok'] 155 | 156 | return ( 157 | <ConfigProvider {...ctx}> 158 | <Observer> 159 | {() => ( 160 | <Dialog 161 | {...modal} 162 | visible={visible} 163 | footer={ 164 | <Fragment> 165 | {footerActions.map((item) => buttonMap[item])} 166 | </Fragment> 167 | } 168 | onClose={(trigger, e) => { 169 | modal?.onClose?.(trigger, e) 170 | reject() 171 | }} 172 | > 173 | <FormProvider form={env.form}> 174 | <DialogContent /> 175 | </FormProvider> 176 | </Dialog> 177 | )} 178 | </Observer> 179 | </ConfigProvider> 180 | ) 181 | } 182 | document.body.appendChild(env.host) 183 | const formDialog = { 184 | forOpen: (middleware: IMiddleware<IFormProps>) => { 185 | if (isFn(middleware)) { 186 | env.openMiddlewares.push(middleware) 187 | } 188 | return formDialog 189 | }, 190 | forConfirm: (middleware: IMiddleware<Form>) => { 191 | if (isFn(middleware)) { 192 | env.confirmMiddlewares.push(middleware) 193 | } 194 | return formDialog 195 | }, 196 | forCancel: (middleware: IMiddleware<Form>) => { 197 | if (isFn(middleware)) { 198 | env.cancelMiddlewares.push(middleware) 199 | } 200 | return formDialog 201 | }, 202 | open: async (props: IFormProps) => { 203 | if (env.promise) return env.promise 204 | env.promise = new Promise(async (resolve, reject) => { 205 | try { 206 | props = await loading(modal.loadingText, () => 207 | applyMiddleware(props, env.openMiddlewares) 208 | ) 209 | env.form = env.form || createForm(props) 210 | } catch (e) { 211 | reject(e) 212 | } 213 | root.render(() => 214 | renderDialog( 215 | true, 216 | () => { 217 | env.form 218 | .submit(async () => { 219 | await applyMiddleware(env.form, env.confirmMiddlewares) 220 | resolve(toJS(env.form.values)) 221 | formDialog.close() 222 | }) 223 | .catch(() => {}) 224 | }, 225 | async () => { 226 | await loading(modal.loadingText, () => 227 | applyMiddleware(env.form, env.cancelMiddlewares) 228 | ) 229 | formDialog.close() 230 | } 231 | ) 232 | ) 233 | }) 234 | return env.promise 235 | }, 236 | close: () => { 237 | if (!env.host) return 238 | root.render(() => renderDialog(false)) 239 | }, 240 | } 241 | return formDialog 242 | } 243 | 244 | const DialogFooter: ReactFC = (props) => { 245 | const ref = useRef<HTMLDivElement>() 246 | const [footer, setFooter] = useState<HTMLDivElement>() 247 | const footerRef = useRef<HTMLDivElement>() 248 | const prefixCls = usePrefixCls('dialog') 249 | useLayoutEffect(() => { 250 | const content = ref.current?.closest(`.${prefixCls}`) 251 | if (content) { 252 | if (!footerRef.current) { 253 | footerRef.current = content.querySelector(`.${prefixCls}-footer`) 254 | if (!footerRef.current) { 255 | footerRef.current = document.createElement('div') 256 | footerRef.current.classList.add(`${prefixCls}-footer`) 257 | content.appendChild(footerRef.current) 258 | } 259 | } 260 | setFooter(footerRef.current) 261 | } 262 | }) 263 | 264 | footerRef.current = footer 265 | 266 | return ( 267 | <div ref={ref} style={{ display: 'none' }}> 268 | {footer && createPortal(props.children, footer)} 269 | </div> 270 | ) 271 | } 272 | 273 | FormDialog.Footer = DialogFooter 274 | 275 | FormDialog.Portal = createPortalProvider('form-dialog') 276 | 277 | export default FormDialog 278 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/DatePicker.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # DatePicker 2 | 3 | > 日期选择器 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { DatePicker, 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 | DatePicker, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="date" 27 | required 28 | title="普通日期" 29 | x-decorator="FormItem" 30 | x-component="DatePicker" 31 | /> 32 | <SchemaField.String 33 | name="week" 34 | title="周选择" 35 | x-decorator="FormItem" 36 | x-component="DatePicker" 37 | x-component-props={{ 38 | picker: 'week', 39 | }} 40 | /> 41 | <SchemaField.String 42 | name="month" 43 | title="月选择" 44 | x-decorator="FormItem" 45 | x-component="DatePicker" 46 | x-component-props={{ 47 | picker: 'month', 48 | }} 49 | /> 50 | <SchemaField.String 51 | name="quarter" 52 | title="财年选择" 53 | x-decorator="FormItem" 54 | x-component="DatePicker" 55 | x-component-props={{ 56 | picker: 'quarter', 57 | }} 58 | /> 59 | <SchemaField.String 60 | name="year" 61 | title="年选择" 62 | x-decorator="FormItem" 63 | x-component="DatePicker" 64 | x-component-props={{ 65 | picker: 'year', 66 | }} 67 | /> 68 | <SchemaField.String 69 | name="[startDate,endDate]" 70 | title="日期范围" 71 | x-decorator="FormItem" 72 | x-component="DatePicker.RangePicker" 73 | x-component-props={{ 74 | showTime: true, 75 | }} 76 | /> 77 | <SchemaField.String 78 | name="range_week" 79 | title="周范围选择" 80 | x-decorator="FormItem" 81 | x-component="DatePicker.RangePicker" 82 | x-component-props={{ 83 | picker: 'week', 84 | }} 85 | /> 86 | <SchemaField.String 87 | name="range_month" 88 | title="月范围选择" 89 | x-decorator="FormItem" 90 | x-component="DatePicker.RangePicker" 91 | x-component-props={{ 92 | picker: 'month', 93 | }} 94 | /> 95 | <SchemaField.String 96 | name="range_quarter" 97 | title="财年范围选择" 98 | x-decorator="FormItem" 99 | x-component="DatePicker.RangePicker" 100 | x-component-props={{ 101 | picker: 'quarter', 102 | }} 103 | /> 104 | <SchemaField.String 105 | name="range_year" 106 | title="年范围选择" 107 | x-decorator="FormItem" 108 | x-component="DatePicker.RangePicker" 109 | x-component-props={{ 110 | picker: 'year', 111 | }} 112 | /> 113 | </SchemaField> 114 | <FormButtonGroup> 115 | <Submit onSubmit={console.log}>提交</Submit> 116 | </FormButtonGroup> 117 | </FormProvider> 118 | ) 119 | ``` 120 | 121 | ## JSON Schema 案例 122 | 123 | ```tsx 124 | import React from 'react' 125 | import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 126 | import { createForm } from '@formily/core' 127 | import { FormProvider, createSchemaField } from '@formily/react' 128 | 129 | const SchemaField = createSchemaField({ 130 | components: { 131 | DatePicker, 132 | FormItem, 133 | }, 134 | }) 135 | 136 | const form = createForm() 137 | 138 | const schema = { 139 | type: 'object', 140 | properties: { 141 | date: { 142 | title: '普通日期', 143 | 'x-decorator': 'FormItem', 144 | 'x-component': 'DatePicker', 145 | type: 'string', 146 | }, 147 | week: { 148 | title: '周选择', 149 | 'x-decorator': 'FormItem', 150 | 'x-component': 'DatePicker', 151 | 'x-component-props': { 152 | picker: 'week', 153 | }, 154 | type: 'string', 155 | }, 156 | month: { 157 | title: '月选择', 158 | 'x-decorator': 'FormItem', 159 | 'x-component': 'DatePicker', 160 | 'x-component-props': { 161 | picker: 'month', 162 | }, 163 | type: 'string', 164 | }, 165 | quarter: { 166 | title: '财年选择', 167 | 'x-decorator': 'FormItem', 168 | 'x-component': 'DatePicker', 169 | 'x-component-props': { 170 | picker: 'quarter', 171 | }, 172 | type: 'string', 173 | }, 174 | year: { 175 | title: '年选择', 176 | 'x-decorator': 'FormItem', 177 | 'x-component': 'DatePicker', 178 | 'x-component-props': { 179 | picker: 'year', 180 | }, 181 | type: 'string', 182 | }, 183 | '[startDate,endDate]': { 184 | title: '日期范围', 185 | 'x-decorator': 'FormItem', 186 | 'x-component': 'DatePicker.RangePicker', 187 | 'x-component-props': { 188 | showTime: true, 189 | }, 190 | type: 'string', 191 | }, 192 | range_week: { 193 | title: '周范围选择', 194 | 'x-decorator': 'FormItem', 195 | 'x-component': 'DatePicker.RangePicker', 196 | 'x-component-props': { 197 | picker: 'week', 198 | }, 199 | type: 'string', 200 | }, 201 | range_month: { 202 | title: '月范围选择', 203 | 'x-decorator': 'FormItem', 204 | 'x-component': 'DatePicker.RangePicker', 205 | 'x-component-props': { 206 | picker: 'month', 207 | }, 208 | type: 'string', 209 | }, 210 | range_quarter: { 211 | title: '财年范围选择', 212 | 'x-decorator': 'FormItem', 213 | 'x-component': 'DatePicker.RangePicker', 214 | 'x-component-props': { 215 | picker: 'quarter', 216 | }, 217 | type: 'string', 218 | }, 219 | range_year: { 220 | name: 'range_year', 221 | title: '年范围选择', 222 | 'x-decorator': 'FormItem', 223 | 'x-component': 'DatePicker.RangePicker', 224 | 'x-component-props': { 225 | picker: 'year', 226 | }, 227 | type: 'string', 228 | }, 229 | }, 230 | } 231 | 232 | export default () => ( 233 | <FormProvider form={form}> 234 | <SchemaField schema={schema} /> 235 | <FormButtonGroup> 236 | <Submit onSubmit={console.log}>提交</Submit> 237 | </FormButtonGroup> 238 | </FormProvider> 239 | ) 240 | ``` 241 | 242 | ## 纯 JSX 案例 243 | 244 | ```tsx 245 | import React from 'react' 246 | import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 247 | import { createForm } from '@formily/core' 248 | import { FormProvider, Field } from '@formily/react' 249 | 250 | const form = createForm() 251 | 252 | export default () => ( 253 | <FormProvider form={form}> 254 | <Field 255 | name="date" 256 | title="日期选择" 257 | decorator={[FormItem]} 258 | component={[DatePicker]} 259 | /> 260 | <Field 261 | name="week" 262 | title="周选择" 263 | decorator={[FormItem]} 264 | component={[ 265 | DatePicker, 266 | { 267 | picker: 'week', 268 | }, 269 | ]} 270 | /> 271 | <Field 272 | name="quarter" 273 | title="财年选择" 274 | decorator={[FormItem]} 275 | component={[ 276 | DatePicker, 277 | { 278 | picker: 'month', 279 | }, 280 | ]} 281 | /> 282 | <Field 283 | name="year" 284 | title="年选择" 285 | decorator={[FormItem]} 286 | component={[ 287 | DatePicker, 288 | { 289 | picker: 'year', 290 | }, 291 | ]} 292 | /> 293 | <Field 294 | name="[startDate,endDate]" 295 | title="日期范围选择" 296 | decorator={[FormItem]} 297 | component={[DatePicker.RangePicker]} 298 | /> 299 | <Field 300 | name="range_week" 301 | title="周范围选择" 302 | decorator={[FormItem]} 303 | component={[ 304 | DatePicker.RangePicker, 305 | { 306 | picker: 'week', 307 | }, 308 | ]} 309 | /> 310 | <Field 311 | name="range_month" 312 | title="月范围选择" 313 | decorator={[FormItem]} 314 | component={[ 315 | DatePicker.RangePicker, 316 | { 317 | picker: 'month', 318 | }, 319 | ]} 320 | /> 321 | <Field 322 | name="range_quarter" 323 | title="财年范围选择" 324 | decorator={[FormItem]} 325 | component={[ 326 | DatePicker.RangePicker, 327 | { 328 | picker: 'quarter', 329 | }, 330 | ]} 331 | /> 332 | <Field 333 | name="range_year" 334 | title="年范围选择" 335 | decorator={[FormItem]} 336 | component={[ 337 | DatePicker.RangePicker, 338 | { 339 | picker: 'year', 340 | }, 341 | ]} 342 | /> 343 | <FormButtonGroup> 344 | <Submit onSubmit={console.log}>提交</Submit> 345 | </FormButtonGroup> 346 | </FormProvider> 347 | ) 348 | ``` 349 | 350 | ## API 351 | 352 | 参考 https://ant.design/components/date-picker-cn/ 353 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Upload.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Upload 2 | 3 | > 上传组件 4 | > 5 | > 注意:使用上传组件,推荐用户进行二次封装,用户无需关心上传组件与 Formily 的数据通信,只需要处理样式与基本上传配置即可。 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | Upload, 13 | FormItem, 14 | FormButtonGroup, 15 | Submit, 16 | FormLayout, 17 | } from '@formily/next' 18 | import { createForm } from '@formily/core' 19 | import { FormProvider, createSchemaField } from '@formily/react' 20 | import { Button } from '@alifd/next' 21 | import { UploadOutlined, InboxOutlined } from '@ant-design/icons' 22 | 23 | const NormalUpload = (props) => { 24 | return ( 25 | <Upload 26 | {...props} 27 | withCredentials={false} 28 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 29 | headers={{ 30 | authorization: 'authorization-text', 31 | }} 32 | > 33 | <Button> 34 | <UploadOutlined /> 35 | 上传文件 36 | </Button> 37 | </Upload> 38 | ) 39 | } 40 | 41 | const CardUpload = (props) => { 42 | return ( 43 | <Upload.Card 44 | {...props} 45 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 46 | withCredentials={false} 47 | headers={{ 48 | authorization: 'authorization-text', 49 | }} 50 | /> 51 | ) 52 | } 53 | 54 | const DraggerUpload = (props) => { 55 | return ( 56 | <Upload.Dragger 57 | {...props} 58 | withCredentials={false} 59 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 60 | > 61 | <div className="next-upload-drag"> 62 | <p className="next-upload-drag-icon" style={{ fontSize: 50 }}> 63 | <InboxOutlined /> 64 | </p> 65 | <p className="next-upload-drag-text"> 66 | click to <Button text>download template</Button> or drag file here 67 | </p> 68 | <p className="next-upload-drag-hint">supports docx, xls, PDF </p> 69 | </div> 70 | </Upload.Dragger> 71 | ) 72 | } 73 | 74 | const SchemaField = createSchemaField({ 75 | components: { 76 | NormalUpload, 77 | CardUpload, 78 | DraggerUpload, 79 | FormItem, 80 | }, 81 | }) 82 | 83 | const form = createForm() 84 | 85 | export default () => ( 86 | <FormProvider form={form}> 87 | <FormLayout labelCol={6} wrapperCol={14}> 88 | <SchemaField> 89 | <SchemaField.Array 90 | name="upload" 91 | title="上传" 92 | x-decorator="FormItem" 93 | x-component="NormalUpload" 94 | required 95 | /> 96 | <SchemaField.Array 97 | name="upload2" 98 | title="卡片上传" 99 | x-decorator="FormItem" 100 | x-component="CardUpload" 101 | required 102 | /> 103 | <SchemaField.Array 104 | name="upload3" 105 | title="拖拽上传" 106 | x-decorator="FormItem" 107 | x-component="DraggerUpload" 108 | required 109 | /> 110 | </SchemaField> 111 | <FormButtonGroup.FormItem> 112 | <Submit onSubmit={console.log}>提交</Submit> 113 | </FormButtonGroup.FormItem> 114 | </FormLayout> 115 | </FormProvider> 116 | ) 117 | ``` 118 | 119 | ## JSON Schema 案例 120 | 121 | ```tsx 122 | import React from 'react' 123 | import { 124 | Upload, 125 | FormItem, 126 | FormButtonGroup, 127 | Submit, 128 | FormLayout, 129 | } from '@formily/next' 130 | import { createForm } from '@formily/core' 131 | import { FormProvider, createSchemaField } from '@formily/react' 132 | import { Button } from '@alifd/next' 133 | import { UploadOutlined, InboxOutlined } from '@ant-design/icons' 134 | 135 | const NormalUpload = (props) => { 136 | return ( 137 | <Upload 138 | {...props} 139 | withCredentials={false} 140 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 141 | headers={{ 142 | authorization: 'authorization-text', 143 | }} 144 | > 145 | <Button> 146 | <UploadOutlined /> 147 | 上传文件 148 | </Button> 149 | </Upload> 150 | ) 151 | } 152 | 153 | const CardUpload = (props) => { 154 | return ( 155 | <Upload.Card 156 | {...props} 157 | withCredentials={false} 158 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 159 | headers={{ 160 | authorization: 'authorization-text', 161 | }} 162 | /> 163 | ) 164 | } 165 | 166 | const DraggerUpload = (props) => { 167 | return ( 168 | <Upload.Dragger 169 | {...props} 170 | withCredentials={false} 171 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 172 | > 173 | <div className="next-upload-drag"> 174 | <p className="next-upload-drag-icon" style={{ fontSize: 50 }}> 175 | <InboxOutlined /> 176 | </p> 177 | <p className="next-upload-drag-text"> 178 | click to <Button text>download template</Button> or drag file here 179 | </p> 180 | <p className="next-upload-drag-hint">supports docx, xls, PDF </p> 181 | </div> 182 | </Upload.Dragger> 183 | ) 184 | } 185 | 186 | const SchemaField = createSchemaField({ 187 | components: { 188 | NormalUpload, 189 | CardUpload, 190 | DraggerUpload, 191 | FormItem, 192 | }, 193 | }) 194 | 195 | const form = createForm() 196 | 197 | const schema = { 198 | type: 'object', 199 | properties: { 200 | upload: { 201 | type: 'array', 202 | title: '上传', 203 | required: true, 204 | 'x-decorator': 'FormItem', 205 | 'x-component': 'NormalUpload', 206 | }, 207 | upload2: { 208 | type: 'array', 209 | title: '卡片上传', 210 | required: true, 211 | 'x-decorator': 'FormItem', 212 | 'x-component': 'CardUpload', 213 | }, 214 | upload3: { 215 | type: 'array', 216 | title: '拖拽上传', 217 | required: true, 218 | 'x-decorator': 'FormItem', 219 | 'x-component': 'DraggerUpload', 220 | }, 221 | }, 222 | } 223 | 224 | export default () => ( 225 | <FormProvider form={form}> 226 | <FormLayout labelCol={6} wrapperCol={14}> 227 | <SchemaField schema={schema} /> 228 | <FormButtonGroup.FormItem> 229 | <Submit onSubmit={console.log}>提交</Submit> 230 | </FormButtonGroup.FormItem> 231 | </FormLayout> 232 | </FormProvider> 233 | ) 234 | ``` 235 | 236 | ## 纯 JSX 案例 237 | 238 | ```tsx 239 | import React from 'react' 240 | import { 241 | Upload, 242 | FormItem, 243 | FormButtonGroup, 244 | Submit, 245 | FormLayout, 246 | } from '@formily/next' 247 | import { createForm } from '@formily/core' 248 | import { FormProvider, Field } from '@formily/react' 249 | import { Button } from '@alifd/next' 250 | import { UploadOutlined, InboxOutlined } from '@ant-design/icons' 251 | 252 | const NormalUpload = (props) => { 253 | return ( 254 | <Upload 255 | {...props} 256 | withCredentials={false} 257 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 258 | headers={{ 259 | authorization: 'authorization-text', 260 | }} 261 | > 262 | <Button> 263 | <UploadOutlined /> 264 | 上传文件 265 | </Button> 266 | </Upload> 267 | ) 268 | } 269 | 270 | const CardUpload = (props) => { 271 | return ( 272 | <Upload.Card 273 | {...props} 274 | withCredentials={false} 275 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 276 | headers={{ 277 | authorization: 'authorization-text', 278 | }} 279 | /> 280 | ) 281 | } 282 | 283 | const DraggerUpload = (props) => { 284 | return ( 285 | <Upload.Dragger 286 | {...props} 287 | withCredentials={false} 288 | action="https://www.mocky.io/v2/5cc8019d300000980a055e76" 289 | > 290 | <div className="next-upload-drag"> 291 | <p className="next-upload-drag-icon" style={{ fontSize: 50 }}> 292 | <InboxOutlined /> 293 | </p> 294 | <p className="next-upload-drag-text"> 295 | click to <Button text>download template</Button> or drag file here 296 | </p> 297 | <p className="next-upload-drag-hint">supports docx, xls, PDF </p> 298 | </div> 299 | </Upload.Dragger> 300 | ) 301 | } 302 | 303 | const form = createForm() 304 | 305 | export default () => ( 306 | <FormProvider form={form}> 307 | <FormLayout labelCol={6} wrapperCol={14}> 308 | <Field 309 | name="upload" 310 | title="上传" 311 | required 312 | decorator={[FormItem]} 313 | component={[NormalUpload]} 314 | /> 315 | <Field 316 | name="upload2" 317 | title="卡片上传" 318 | required 319 | decorator={[FormItem]} 320 | component={[CardUpload]} 321 | /> 322 | <Field 323 | name="upload3" 324 | title="拖拽上传" 325 | required 326 | decorator={[FormItem]} 327 | component={[DraggerUpload]} 328 | /> 329 | <FormButtonGroup.FormItem> 330 | <Submit onSubmit={console.log}>提交</Submit> 331 | </FormButtonGroup.FormItem> 332 | </FormLayout> 333 | </FormProvider> 334 | ) 335 | ``` 336 | 337 | ## API 338 | 339 | 参考 https://fusion.design/pc/component/basic/upload 340 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Space.md: -------------------------------------------------------------------------------- ```markdown 1 | # Space 2 | 3 | > Super convenient Flex layout component, can help users quickly realize the layout of any element side by side next to each other 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Input, 11 | FormItem, 12 | FormLayout, 13 | FormButtonGroup, 14 | Submit, 15 | Space, 16 | } from '@formily/antd' 17 | import { createForm } from '@formily/core' 18 | import { FormProvider, createSchemaField } from '@formily/react' 19 | 20 | const SchemaField = createSchemaField({ 21 | components: { 22 | Input, 23 | FormItem, 24 | Space, 25 | }, 26 | }) 27 | 28 | const form = createForm() 29 | 30 | export default () => ( 31 | <FormProvider form={form}> 32 | <FormLayout labelCol={6} wrapperCol={16}> 33 | <SchemaField> 34 | <SchemaField.Void 35 | title="name" 36 | x-decorator="FormItem" 37 | x-decorator-props={{ 38 | asterisk: true, 39 | feedbackLayout: 'none', 40 | }} 41 | x-component="Space" 42 | > 43 | <SchemaField.String 44 | name="firstName" 45 | x-decorator="FormItem" 46 | x-component="Input" 47 | required 48 | /> 49 | <SchemaField.String 50 | name="lastName" 51 | x-decorator="FormItem" 52 | x-component="Input" 53 | required 54 | /> 55 | </SchemaField.Void> 56 | <SchemaField.Void 57 | title="Text concatenation" 58 | x-decorator="FormItem" 59 | x-decorator-props={{ 60 | asterisk: true, 61 | feedbackLayout: 'none', 62 | }} 63 | x-component="Space" 64 | > 65 | <SchemaField.String 66 | name="aa" 67 | x-decorator="FormItem" 68 | x-component="Input" 69 | x-decorator-props={{ 70 | addonAfter: 'Unit', 71 | }} 72 | required 73 | /> 74 | <SchemaField.String 75 | name="bb" 76 | x-decorator="FormItem" 77 | x-component="Input" 78 | x-decorator-props={{ 79 | addonAfter: 'Unit', 80 | }} 81 | required 82 | /> 83 | <SchemaField.String 84 | name="cc" 85 | x-decorator="FormItem" 86 | x-component="Input" 87 | x-decorator-props={{ 88 | addonAfter: 'Unit', 89 | }} 90 | required 91 | /> 92 | </SchemaField.Void> 93 | <SchemaField.String 94 | name="textarea" 95 | title="text box" 96 | x-decorator="FormItem" 97 | required 98 | x-component="Input.TextArea" 99 | x-component-props={{ 100 | style: { 101 | width: 400, 102 | }, 103 | }} 104 | /> 105 | </SchemaField> 106 | <FormButtonGroup.FormItem> 107 | <Submit onSubmit={console.log}>Submit</Submit> 108 | </FormButtonGroup.FormItem> 109 | </FormLayout> 110 | </FormProvider> 111 | ) 112 | ``` 113 | 114 | ## JSON Schema case 115 | 116 | ```tsx 117 | import React from 'react' 118 | import { 119 | Input, 120 | FormItem, 121 | FormLayout, 122 | FormButtonGroup, 123 | Submit, 124 | Space, 125 | } from '@formily/antd' 126 | import { createForm } from '@formily/core' 127 | import { FormProvider, createSchemaField } from '@formily/react' 128 | 129 | const SchemaField = createSchemaField({ 130 | components: { 131 | Input, 132 | FormItem, 133 | Space, 134 | }, 135 | }) 136 | 137 | const form = createForm() 138 | 139 | const schema = { 140 | type: 'object', 141 | properties: { 142 | name: { 143 | type: 'void', 144 | title: 'Name', 145 | 'x-decorator': 'FormItem', 146 | 'x-decorator-props': { 147 | asterisk: true, 148 | feedbackLayout: 'none', 149 | }, 150 | 'x-component': 'Space', 151 | properties: { 152 | firstName: { 153 | type: 'string', 154 | 'x-decorator': 'FormItem', 155 | 'x-component': 'Input', 156 | required: true, 157 | }, 158 | lastName: { 159 | type: 'string', 160 | 'x-decorator': 'FormItem', 161 | 'x-component': 'Input', 162 | required: true, 163 | }, 164 | }, 165 | }, 166 | texts: { 167 | type: 'void', 168 | title: 'Text concatenation', 169 | 'x-decorator': 'FormItem', 170 | 'x-decorator-props': { 171 | asterisk: true, 172 | feedbackLayout: 'none', 173 | }, 174 | 'x-component': 'Space', 175 | properties: { 176 | aa: { 177 | type: 'string', 178 | 'x-decorator': 'FormItem', 179 | 'x-decorator-props': { 180 | addonAfter: 'Unit', 181 | }, 182 | 'x-component': 'Input', 183 | required: true, 184 | }, 185 | bb: { 186 | type: 'string', 187 | 'x-decorator': 'FormItem', 188 | 'x-decorator-props': { 189 | addonAfter: 'Unit', 190 | }, 191 | 'x-component': 'Input', 192 | required: true, 193 | }, 194 | cc: { 195 | type: 'string', 196 | 'x-decorator': 'FormItem', 197 | 'x-decorator-props': { 198 | addonAfter: 'Unit', 199 | }, 200 | 'x-component': 'Input', 201 | required: true, 202 | }, 203 | }, 204 | }, 205 | 206 | textarea: { 207 | type: 'string', 208 | title: 'Text box', 209 | 'x-decorator': 'FormItem', 210 | 'x-component': 'Input.TextArea', 211 | 'x-component-props': { 212 | style: { 213 | width: 400, 214 | }, 215 | }, 216 | required: true, 217 | }, 218 | }, 219 | } 220 | 221 | export default () => ( 222 | <FormProvider form={form}> 223 | <FormLayout labelCol={6} wrapperCol={16}> 224 | <SchemaField schema={schema} /> 225 | <FormButtonGroup.FormItem> 226 | <Submit onSubmit={console.log}>Submit</Submit> 227 | </FormButtonGroup.FormItem> 228 | </FormLayout> 229 | </FormProvider> 230 | ) 231 | ``` 232 | 233 | ## Pure JSX case 234 | 235 | ```tsx 236 | import React from 'react' 237 | import { 238 | Input, 239 | FormItem, 240 | FormLayout, 241 | FormButtonGroup, 242 | Submit, 243 | Space, 244 | } from '@formily/antd' 245 | import { createForm } from '@formily/core' 246 | import { FormProvider, Field, VoidField } from '@formily/react' 247 | 248 | const form = createForm() 249 | 250 | export default () => ( 251 | <FormProvider form={form}> 252 | <FormLayout labelCol={6} wrapperCol={16}> 253 | <VoidField 254 | name="name" 255 | title="name" 256 | decorator={[ 257 | FormItem, 258 | { 259 | asterisk: true, 260 | feedbackLayout: 'none', 261 | }, 262 | ]} 263 | component={[Space]} 264 | > 265 | <Field 266 | name="firstName" 267 | decorator={[FormItem]} 268 | component={[Input]} 269 | required 270 | /> 271 | <Field 272 | name="lastName" 273 | decorator={[FormItem]} 274 | component={[Input]} 275 | required 276 | /> 277 | </VoidField> 278 | <VoidField 279 | name="texts" 280 | title="Text concatenation" 281 | decorator={[ 282 | FormItem, 283 | { 284 | asterisk: true, 285 | feedbackLayout: 'none', 286 | }, 287 | ]} 288 | component={[Space]} 289 | > 290 | <Field 291 | name="aa" 292 | decorator={[ 293 | FormItem, 294 | { 295 | addonAfter: 'Unit', 296 | }, 297 | ]} 298 | component={[Input]} 299 | required 300 | /> 301 | <Field 302 | name="bb" 303 | decorator={[ 304 | FormItem, 305 | { 306 | addonAfter: 'Unit', 307 | }, 308 | ]} 309 | component={[Input]} 310 | required 311 | /> 312 | <Field 313 | name="cc" 314 | decorator={[ 315 | FormItem, 316 | { 317 | addonAfter: 'Unit', 318 | }, 319 | ]} 320 | component={[Input]} 321 | required 322 | /> 323 | </VoidField> 324 | <Field 325 | name="textarea" 326 | title="text box" 327 | decorator={[FormItem]} 328 | component={[ 329 | Input.TextArea, 330 | { 331 | style: { 332 | width: 400, 333 | }, 334 | }, 335 | ]} 336 | required 337 | /> 338 | <FormButtonGroup.FormItem> 339 | <Submit onSubmit={console.log}>Submit</Submit> 340 | </FormButtonGroup.FormItem> 341 | </FormLayout> 342 | </FormProvider> 343 | ) 344 | ``` 345 | 346 | ## API 347 | 348 | Reference https://ant.design/components/space-cn/ 349 | ``` -------------------------------------------------------------------------------- /packages/element/src/editable/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Field, isVoidField } from '@formily/core' 2 | import { reaction } from '@formily/reactive' 3 | import { observer } from '@formily/reactive-vue' 4 | import { h, useField } from '@formily/vue' 5 | import { Popover } from 'element-ui' 6 | import { defineComponent, onBeforeUnmount, ref } from 'vue-demi' 7 | import { stylePrefix } from '../__builtins__/configs' 8 | 9 | import type { Popover as PopoverProps } from 'element-ui' 10 | import { FormBaseItem, FormItemProps } from '../form-item' 11 | import { composeExport, useCompatRef } from '../__builtins__/shared' 12 | 13 | export type EditableProps = FormItemProps 14 | export type EditablePopoverProps = PopoverProps 15 | 16 | const getParentPattern = (fieldRef) => { 17 | const field = fieldRef.value 18 | return field?.parent?.pattern || field?.form?.pattern 19 | } 20 | 21 | const getFormItemProps = (fieldRef): FormItemProps => { 22 | const field = fieldRef.value 23 | 24 | if (isVoidField(field)) return {} 25 | if (!field) return {} 26 | const takeMessage = () => { 27 | if (field.selfErrors.length) return field.selfErrors[0] 28 | if (field.selfWarnings.length) return field.selfWarnings[0] 29 | if (field.selfSuccesses.length) return field.selfSuccesses[0] 30 | } 31 | 32 | return { 33 | feedbackStatus: 34 | field.validateStatus === 'validating' ? 'pending' : field.validateStatus, 35 | feedbackText: takeMessage(), 36 | extra: field.description, 37 | } 38 | } 39 | 40 | const EditableInner = observer( 41 | defineComponent<EditableProps>({ 42 | name: 'FEditable', 43 | setup(props, { attrs, slots, refs }) { 44 | const fieldRef = useField<Field>() 45 | const { elRef: innerRef, elRefBinder } = useCompatRef(refs) 46 | const prefixCls = `${stylePrefix}-editable` 47 | const setEditable = (payload: boolean) => { 48 | const pattern = getParentPattern(fieldRef) 49 | 50 | if (pattern !== 'editable') return 51 | fieldRef.value.setPattern(payload ? 'editable' : 'readPretty') 52 | } 53 | 54 | const dispose = reaction( 55 | () => { 56 | const pattern = getParentPattern(fieldRef) 57 | 58 | return pattern 59 | }, 60 | (pattern) => { 61 | if (pattern === 'editable') { 62 | fieldRef.value.setPattern('readPretty') 63 | } 64 | }, 65 | { 66 | fireImmediately: true, 67 | } 68 | ) 69 | 70 | onBeforeUnmount(dispose) 71 | 72 | return () => { 73 | const field = fieldRef.value 74 | const editable = field.pattern === 'editable' 75 | const pattern = getParentPattern(fieldRef) 76 | const itemProps = getFormItemProps(fieldRef) 77 | 78 | const recover = () => { 79 | if (editable && !fieldRef.value?.errors?.length) { 80 | setEditable(false) 81 | } 82 | } 83 | 84 | const onClick = (e: MouseEvent) => { 85 | const target = e.target as HTMLElement 86 | const close = innerRef.value.querySelector(`.${prefixCls}-close-btn`) 87 | 88 | if (target?.contains(close) || close?.contains(target)) { 89 | recover() 90 | } else if (!editable) { 91 | setTimeout(() => { 92 | setEditable(true) 93 | setTimeout(() => { 94 | innerRef.value.querySelector('input')?.focus() 95 | }) 96 | }) 97 | } 98 | } 99 | 100 | const renderEditHelper = () => { 101 | if (editable) return null 102 | 103 | return h( 104 | FormBaseItem, 105 | { 106 | attrs: { 107 | ...attrs, 108 | ...itemProps, 109 | }, 110 | }, 111 | { 112 | default: () => { 113 | return h( 114 | 'i', 115 | { 116 | class: [ 117 | `${prefixCls}-edit-btn`, 118 | pattern === 'editable' 119 | ? 'el-icon-edit' 120 | : 'el-icon-chat-dot-round', 121 | ], 122 | }, 123 | {} 124 | ) 125 | }, 126 | } 127 | ) 128 | } 129 | 130 | const renderCloseHelper = () => { 131 | if (!editable) return null 132 | return h( 133 | FormBaseItem, 134 | { 135 | attrs: { 136 | ...attrs, 137 | }, 138 | }, 139 | { 140 | default: () => { 141 | return h( 142 | 'i', 143 | { 144 | class: [`${prefixCls}-close-btn`, 'el-icon-close'], 145 | }, 146 | {} 147 | ) 148 | }, 149 | } 150 | ) 151 | } 152 | 153 | return h( 154 | 'div', 155 | { 156 | class: prefixCls, 157 | ref: elRefBinder, 158 | on: { 159 | click: onClick, 160 | }, 161 | }, 162 | { 163 | default: () => 164 | h( 165 | 'div', 166 | { 167 | class: `${prefixCls}-content`, 168 | }, 169 | { 170 | default: () => [ 171 | h( 172 | FormBaseItem, 173 | { 174 | attrs: { 175 | ...attrs, 176 | ...itemProps, 177 | }, 178 | }, 179 | slots 180 | ), 181 | renderEditHelper(), 182 | renderCloseHelper(), 183 | ], 184 | } 185 | ), 186 | } 187 | ) 188 | } 189 | }, 190 | }) 191 | ) 192 | 193 | const EditablePopover = observer( 194 | defineComponent<EditablePopoverProps>({ 195 | name: 'FEditablePopover', 196 | setup(props, { attrs, slots }) { 197 | const fieldRef = useField<Field>() 198 | 199 | const prefixCls = `${stylePrefix}-editable` 200 | const visible = ref(false) 201 | 202 | return () => { 203 | const field = fieldRef.value 204 | const pattern = getParentPattern(fieldRef) 205 | return h( 206 | Popover, 207 | { 208 | class: [prefixCls], 209 | attrs: { 210 | ...attrs, 211 | title: attrs.title || field.title, 212 | value: visible.value, 213 | trigger: 'click', 214 | }, 215 | on: { 216 | input: (value) => { 217 | visible.value = value 218 | }, 219 | }, 220 | }, 221 | { 222 | default: () => [slots.default()], 223 | reference: () => 224 | h( 225 | FormBaseItem, 226 | { class: [`${prefixCls}-trigger`] }, 227 | { 228 | default: () => 229 | h( 230 | 'div', 231 | { 232 | class: [`${prefixCls}-content`], 233 | }, 234 | { 235 | default: () => [ 236 | h( 237 | 'span', 238 | { 239 | class: [`${prefixCls}-preview`], 240 | }, 241 | { 242 | default: () => [attrs.title || field.title], 243 | } 244 | ), 245 | h( 246 | 'i', 247 | { 248 | class: [ 249 | `${prefixCls}-edit-btn`, 250 | pattern === 'editable' 251 | ? 'el-icon-edit' 252 | : 'el-icon-chat-dot-round', 253 | ], 254 | }, 255 | {} 256 | ), 257 | ], 258 | } 259 | ), 260 | } 261 | ), 262 | } 263 | ) 264 | } 265 | }, 266 | }) 267 | ) 268 | 269 | export const Editable = composeExport(EditableInner, { 270 | Popover: EditablePopover, 271 | }) 272 | 273 | export default Editable 274 | ```