This is page 15 of 35. Use http://codebase.md/alibaba/formily?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/next/docs/components/Editable.md: -------------------------------------------------------------------------------- ```markdown # Editable > Partial editor, you can use this component for some form areas with high space requirements > > Editable component is equivalent to a variant of FormItem component, so it is usually placed in decorator ## Markup Schema example ```tsx import React from 'react' import { Input, DatePicker, Editable, FormItem, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { DatePicker, Editable, Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="date" title="date" x-decorator="Editable" x-component="DatePicker" /> <SchemaField.String name="input" title="input box" x-decorator="Editable" x-component="Input" /> <SchemaField.Void name="void" title="Virtual Node Container" x-component="Editable.Popover" x-reactions={(field) => { field.title = field.query('.void.date2').get('value') || field.title }} > <SchemaField.String name="date2" title="date" x-decorator="FormItem" x-component="DatePicker" x-component-props={{ followTrigger: true, }} /> <SchemaField.String name="input2" title="input box" x-decorator="FormItem" x-component="Input" /> </SchemaField.Void> <SchemaField.Object name="iobject" title="Object node container" x-component="Editable.Popover" x-reactions={(field) => { field.title = field.value?.date || field.title }} > <SchemaField.String name="date" title="date" x-decorator="FormItem" x-component="DatePicker" x-component-props={{ followTrigger: true, }} /> <SchemaField.String name="input" title="input box" x-decorator="FormItem" x-component="Input" /> </SchemaField.Object> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Input, DatePicker, Editable, FormItem, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { DatePicker, Editable, Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { date: { type: 'string', title: 'Date', 'x-decorator': 'Editable', 'x-component': 'DatePicker', }, input: { type: 'string', title: 'input box', 'x-decorator': 'Editable', 'x-component': 'Input', }, void: { type: 'void', title: 'Virtual Node Container', 'x-component': 'Editable.Popover', 'x-reactions': "{{(field) => field.title = field.query('.void.date2').get('value') || field.title}}", properties: { date2: { type: 'string', title: 'Date', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { followTrigger: true, }, }, input2: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, iobject: { type: 'object', title: 'Object node container', 'x-component': 'Editable.Popover', 'x-reactions': '{{(field) => field.title = field.value && field.value.date || field.title}}', properties: { date: { type: 'string', title: 'Date', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { followTrigger: true, }, }, input: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Input, DatePicker, Editable, FormItem, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field, VoidField, ObjectField } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="date" title="date" decorator={[Editable]} component={[DatePicker]} /> <Field name="input" title="input box" decorator={[Editable]} component={[Input]} /> <VoidField name="void" title="Virtual Node Container" reactions={(field) => { field.title = field.query('.void.date2').get('value') || field.title }} component={[Editable.Popover]} > <Field name="date2" title="date" decorator={[FormItem]} component={[ DatePicker, { followTrigger: true, }, ]} /> <Field name="input2" title="input box" decorator={[FormItem]} component={[Input]} /> </VoidField> <ObjectField name="iobject" title="Object node container" component={[ Editable.Popover, { renderPreview: (field) => { return field.value?.date }, }, ]} > <Field name="date" title="date" decorator={[FormItem]} component={[ DatePicker, { followTrigger: true, }, ]} /> <Field name="input" title="input box" decorator={[FormItem]} component={[Input]} /> </ObjectField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API ### Editable > Inline editing Refer to the FormItem property in https://fusion.design/pc/component/basic/form ### Editable.Popover > Floating layer editing | Property name | Type | Description | Default value | | ------------- | --------------------------------- | ---------------- | ------------- | | renderPreview | `(field:GeneralField)=>ReactNode` | Preview renderer | | Note: If there is a floating layer component such as Select/DatePicker inside the Popover, followTrigger=true needs to be configured on the floating layer component Other references https://fusion.design/pc/component/basic/balloon ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Upload.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Upload > 上传组件 > > 注意:使用上传组件,推荐用户进行二次封装,用户无需关心上传组件与 Formily 的数据通信,只需要处理样式与基本上传配置即可。 ## Markup Schema 案例 ```tsx import React from 'react' import { Upload, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from 'antd' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button icon={<UploadOutlined />}>上传文件</Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" listType="picture-card" headers={{ authorization: 'authorization-text', }} > <UploadOutlined style={{ fontSize: 20 }} /> </Upload> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text"> Click or drag file to this area to upload </p> <p className="ant-upload-hint"> Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files </p> </Upload.Dragger> ) } const SchemaField = createSchemaField({ components: { NormalUpload, CardUpload, DraggerUpload, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.Array name="upload" title="上传" x-decorator="FormItem" x-component="NormalUpload" required /> <SchemaField.Array name="upload2" title="卡片上传" x-decorator="FormItem" x-component="CardUpload" required /> <SchemaField.Array name="upload3" title="拖拽上传" x-decorator="FormItem" x-component="DraggerUpload" required /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Upload, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from 'antd' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button icon={<UploadOutlined />}>上传文件</Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" listType="picture-card" headers={{ authorization: 'authorization-text', }} > <UploadOutlined style={{ fontSize: 20 }} /> </Upload> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text"> Click or drag file to this area to upload </p> <p className="ant-upload-hint"> Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files </p> </Upload.Dragger> ) } const SchemaField = createSchemaField({ components: { NormalUpload, CardUpload, DraggerUpload, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { upload: { type: 'array', title: '上传', required: true, 'x-decorator': 'FormItem', 'x-component': 'NormalUpload', }, upload2: { type: 'array', title: '卡片上传', required: true, 'x-decorator': 'FormItem', 'x-component': 'CardUpload', }, upload3: { type: 'array', title: '拖拽上传', required: true, 'x-decorator': 'FormItem', 'x-component': 'DraggerUpload', }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Upload, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' import { Button } from 'antd' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button icon={<UploadOutlined />}>上传文件</Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" listType="picture-card" headers={{ authorization: 'authorization-text', }} > <UploadOutlined style={{ fontSize: 20 }} /> </Upload> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text"> Click or drag file to this area to upload </p> <p className="ant-upload-hint"> Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files </p> </Upload.Dragger> ) } const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <Field name="upload" title="上传" required decorator={[FormItem]} component={[NormalUpload]} /> <Field name="upload2" title="卡片上传" required decorator={[FormItem]} component={[CardUpload]} /> <Field name="upload3" title="拖拽上传" required decorator={[FormItem]} component={[DraggerUpload]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API 参考 https://ant.design/components/upload-cn/ ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Space.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Space > 超级便捷的 Flex 布局组件,可以帮助用户快速实现任何元素的并排紧挨布局 ## Markup Schema 案例 ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, Space, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <SchemaField> <SchemaField.Void title="姓名" x-decorator="FormItem" x-decorator-props={{ asterisk: true, feedbackLayout: 'none', }} x-component="Space" > <SchemaField.String name="firstName" x-decorator="FormItem" x-component="Input" required /> <SchemaField.String name="lastName" x-decorator="FormItem" x-component="Input" required /> </SchemaField.Void> <SchemaField.Void title="文本串联" x-decorator="FormItem" x-decorator-props={{ asterisk: true, feedbackLayout: 'none', }} x-component="Space" > <SchemaField.String name="aa" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: '单位', }} required /> <SchemaField.String name="bb" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: '单位', }} required /> <SchemaField.String name="cc" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: '单位', }} required /> </SchemaField.Void> <SchemaField.String name="textarea" title="文本框" x-decorator="FormItem" required x-component="Input.TextArea" x-component-props={{ style: { width: 400, }, }} /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, Space, }, }) const form = createForm() const schema = { type: 'object', properties: { name: { type: 'void', title: '姓名', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { firstName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, lastName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, }, }, texts: { type: 'void', title: '文本串联', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { aa: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: '单位', }, 'x-component': 'Input', required: true, }, bb: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: '单位', }, 'x-component': 'Input', required: true, }, cc: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: '单位', }, 'x-component': 'Input', required: true, }, }, }, textarea: { type: 'string', title: '文本框', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 400, }, }, required: true, }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field, VoidField } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <VoidField name="name" title="姓名" decorator={[ FormItem, { asterisk: true, feedbackLayout: 'none', }, ]} component={[Space]} > <Field name="firstName" decorator={[FormItem]} component={[Input]} required /> <Field name="lastName" decorator={[FormItem]} component={[Input]} required /> </VoidField> <VoidField name="texts" title="文本串联" decorator={[ FormItem, { asterisk: true, feedbackLayout: 'none', }, ]} component={[Space]} > <Field name="aa" decorator={[ FormItem, { addonAfter: '单位', }, ]} component={[Input]} required /> <Field name="bb" decorator={[ FormItem, { addonAfter: '单位', }, ]} component={[Input]} required /> <Field name="cc" decorator={[ FormItem, { addonAfter: '单位', }, ]} component={[Input]} required /> </VoidField> <Field name="textarea" title="文本框" decorator={[FormItem]} component={[ Input.TextArea, { style: { width: 400, }, }, ]} required /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API 参考 https://ant.design/components/space-cn/ ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/compiler.spec.ts: -------------------------------------------------------------------------------- ```typescript import { compile, silent, registerCompiler, shallowCompile, patchCompile, patchSchemaCompile, } from '../compiler' import { Schema } from '../schema' test('compile', () => { expect(compile('{{123}}xx')).toEqual('{{123}}xx') expect(compile('{{123}} ')).toEqual(123) expect(compile('{{123}}')).toEqual(123) expect( compile({ hello: '{{123}}', }) ).toEqual({ hello: 123, }) expect( compile({ array: ['{{123}}'], }) ).toEqual({ array: [123], }) const date = new Date() date['expression'] = '{{123}}' const compiledDate = compile(date) expect(compiledDate).toEqual(date) expect(compiledDate['expression']).toEqual('{{123}}') const moment = { _isAMomentObject: true, expression: '{{123}}' } const compiledMoment = compile(moment) expect(compiledMoment).toEqual(moment) expect(compiledMoment['expression']).toEqual('{{123}}') const react = { _owner: true, $$typeof: true, expression: '{{123}}' } const compiledReact = compile(react) expect(compiledReact).toEqual(react) expect(compiledReact['expression']).toEqual('{{123}}') const actions = { [Symbol.for('__REVA_ACTIONS')]: true, expression: '{{123}}', } const compiledActions = compile(actions) expect(compiledActions).toEqual(actions) expect(compiledActions['expression']).toEqual('{{123}}') const schema = new Schema({ type: 'object', properties: { aa: { type: 'string', 'x-component': 'Input', 'x-component-props': '{{123}}', }, }, }) const compiledSchema = schema.compile() expect(compiledSchema.toJSON()).toEqual(schema.toJSON()) expect(compiledSchema.properties?.['aa']['x-component-props']).toEqual( '{{123}}' ) const toJSable = { toJS() { return { aa: 123, } }, expression: '{{123}}', } const compiledToJSable = compile(toJSable) expect(compiledToJSable).toEqual(toJSable) expect(compiledToJSable['expression']).toEqual('{{123}}') const toJSONable = { toJSON() { return { aa: 123, } }, expression: '{{123}}', } const compiledToJSONable = compile(toJSONable) expect(compiledToJSONable).toEqual(toJSONable) expect(compiledToJSONable['expression']).toEqual('{{123}}') const circularRef = { expression: '{{123}}', } circularRef['self'] = circularRef const compiledCircularRef = compile(circularRef) expect(compiledCircularRef['expression']).toEqual(123) }) test('shallowCompile', () => { expect(shallowCompile('{{123}}xx')).toEqual('{{123}}xx') expect(shallowCompile('{{123}} ')).toEqual(123) expect(shallowCompile('{{123}}')).toEqual(123) expect( shallowCompile({ hello: '{{123}}', }) ).toEqual({ hello: '{{123}}', }) expect( shallowCompile({ array: ['{{123}}'], }) ).toEqual({ array: ['{{123}}'], }) expect(shallowCompile(['{{123}}'])).toEqual(['{{123}}']) expect(shallowCompile([{ kk: '{{123}}' }])).toEqual([{ kk: '{{123}}' }]) }) test('unsilent', () => { silent(false) expect(() => compile('{{ ( }}')).toThrowError() }) test('silent', () => { silent(true) expect(() => compile('{{ ( }}')).not.toThrowError() silent(false) }) test('patchCompile', () => { const targetState = { title: '', description: '', dataSource: [22], } patchCompile( targetState as any, { title: '132', description: '{{"Hello world"}}', dataSource: [1, 2, 3, '{{333}}'], extend: '333', }, {} ) expect(targetState).toEqual({ title: '132', description: 'Hello world', dataSource: [1, 2, 3, 333], }) }) test('patchSchemaCompile', () => { const targetState = { title: '', description: '', dataSource: [22], } patchSchemaCompile( targetState as any, { title: '132', description: '{{"Hello world"}}', enum: [1, 2, 3, '{{333}}'], 'x-reactions': { fulfill: { schema: { title: 'hello', }, }, }, version: '1.2.3', }, {} ) expect(targetState).toEqual({ title: '132', description: 'Hello world', dataSource: [ { label: 1, value: 1 }, { label: 2, value: 2 }, { label: 3, value: 3 }, { label: 333, value: 333 }, ], }) }) test('patchSchemaCompile demand un initialized', () => { const setValidatorRule = (name: string, value: any) => { targetState[name] = value } const targetState = { title: '', description: '', dataSource: [22], setValidatorRule, } patchSchemaCompile( targetState as any, { title: '132', 'x-display': undefined, 'x-hidden': null, description: '{{"Hello world"}}', enum: [1, 2, 3, '{{333}}'], format: 'phone', 'x-reactions': { fulfill: { schema: { title: 'hello', }, }, }, version: '1.2.3', }, {}, true ) expect(targetState).toEqual({ title: '132', description: 'Hello world', hidden: null, format: 'phone', setValidatorRule, dataSource: [ { label: 1, value: 1 }, { label: 2, value: 2 }, { label: 3, value: 3 }, { label: 333, value: 333 }, ], }) }) test('patchSchemaCompile demand initialized', () => { const targetState = { initialized: true, title: '', description: '', dataSource: [22], } patchSchemaCompile( targetState as any, { title: '132', description: '{{"Hello world"}}', enum: [1, 2, 3, '{{333}}'], 'x-reactions': { fulfill: { schema: { title: 'hello', }, }, }, version: '1.2.3', }, {}, true ) expect(targetState).toEqual({ initialized: true, title: '', description: '', dataSource: [22], }) }) test('patchSchemaCompile x-compile-omitted', () => { const targetState = { title: '', validator: [], } patchSchemaCompile( targetState as any, { title: '132', 'x-validator': [ { remoteCheckUniq: '{{field.value}}', }, ], version: '1.2.3', }, { field: { value: 888, }, } ) expect(targetState).toEqual({ title: '132', validator: [{ remoteCheckUniq: 888 }], }) const targetOmitState = { title: '', validator: [], } patchSchemaCompile( targetOmitState as any, { title: '132', 'x-compile-omitted': ['x-validator'], 'x-validator': [ { remoteCheckUniq: '{{field.value}}', }, ], version: '1.2.3', }, { field: { value: 888, }, } ) expect(targetOmitState).toEqual({ title: '132', validator: [{ remoteCheckUniq: '{{field.value}}' }], }) }) test('registerCompiler', () => { registerCompiler(() => { return 'compiled' }) expect(compile('{{123}}xx')).toEqual('{{123}}xx') expect(compile('{{123}} ')).toEqual('compiled') expect(compile('{{123}}')).toEqual('compiled') expect( compile({ hello: '{{123}}', }) ).toEqual({ hello: 'compiled', }) expect( compile({ array: ['{{123}}'], }) ).toEqual({ array: ['compiled'], }) registerCompiler(null) }) ``` -------------------------------------------------------------------------------- /packages/vue/src/components/SchemaField.ts: -------------------------------------------------------------------------------- ```typescript import { inject, provide, computed, shallowRef, watch } from 'vue-demi' import { ISchema, Schema, SchemaTypes } from '@formily/json-schema' import { RecursionField } from '../components' import { SchemaMarkupSymbol, SchemaExpressionScopeSymbol, SchemaOptionsSymbol, } from '../shared' import { ISchemaFieldVueFactoryOptions, SchemaVueComponents, ISchemaFieldProps, ISchemaMarkupFieldProps, ISchemaTypeFieldProps, } from '../types' import { resolveSchemaProps } from '../utils/resolveSchemaProps' import { h } from '../shared/h' import { Fragment } from '../shared/fragment' import type { DefineComponent } from '../types' import { lazyMerge } from '@formily/shared' type SchemaFieldComponents = { SchemaField: DefineComponent<ISchemaFieldProps> SchemaMarkupField: DefineComponent<ISchemaMarkupFieldProps> SchemaStringField: DefineComponent<ISchemaTypeFieldProps> SchemaObjectField: DefineComponent<ISchemaTypeFieldProps> SchemaArrayField: DefineComponent<ISchemaTypeFieldProps> SchemaBooleanField: DefineComponent<ISchemaTypeFieldProps> SchemaDateField: DefineComponent<ISchemaTypeFieldProps> SchemaDateTimeField: DefineComponent<ISchemaTypeFieldProps> SchemaVoidField: DefineComponent<ISchemaTypeFieldProps> SchemaNumberField: DefineComponent<ISchemaTypeFieldProps> } const env = { nonameId: 0, } const getRandomName = () => { return `NO_NAME_FIELD_$${env.nonameId++}` } const markupProps = { version: String, name: [String, Number], title: {}, description: {}, default: {}, readOnly: { type: Boolean, default: undefined, }, writeOnly: { type: Boolean, default: undefined, }, enum: {}, const: {}, multipleOf: Number, maximum: Number, exclusiveMaximum: Number, minimum: Number, exclusiveMinimum: Number, maxLength: Number, minLength: Number, pattern: {}, maxItems: Number, minItems: Number, uniqueItems: { type: Boolean, default: undefined, }, maxProperties: Number, minProperties: Number, required: { type: [Boolean, Array, String], default: undefined, }, format: String, properties: {}, items: {}, additionalItems: {}, patternProperties: {}, additionalProperties: {}, xIndex: Number, xPattern: {}, xDisplay: {}, xValidator: {}, xDecorator: {}, xDecoratorProps: {}, xComponent: {}, xComponentProps: {}, xReactions: {}, xContent: {}, xVisible: { type: Boolean, default: undefined, }, xHidden: { type: Boolean, default: undefined, }, xDisabled: { type: Boolean, default: undefined, }, xEditable: { type: Boolean, default: undefined, }, xReadOnly: { type: Boolean, default: undefined, }, xReadPretty: { type: Boolean, default: undefined, }, } export function createSchemaField< Components extends SchemaVueComponents = SchemaVueComponents >(options: ISchemaFieldVueFactoryOptions<Components> = {}): SchemaFieldComponents { const SchemaField = { name: 'SchemaField', inheritAttrs: false, props: { schema: {}, scope: {}, components: {}, name: [String, Number], basePath: {}, onlyRenderProperties: { type: Boolean, default: undefined }, onlyRenderSelf: { type: Boolean, default: undefined }, mapProperties: {}, filterProperties: {}, }, setup(props: ISchemaFieldProps, { slots }) { const schemaRef = computed(() => Schema.isSchemaInstance(props.schema) ? props.schema : new Schema({ type: 'object', ...props.schema, }) ) const scopeRef = computed(() => lazyMerge(options.scope, props.scope)) const optionsRef = computed(() => ({ ...options, components: { ...options.components, ...props.components, }, })) provide(SchemaMarkupSymbol, schemaRef) provide(SchemaOptionsSymbol, optionsRef) provide(SchemaExpressionScopeSymbol, scopeRef) return () => { env.nonameId = 0 return h( Fragment, {}, { default: () => { const children = [] if (slots.default) { children.push( h( 'template', {}, { default: () => slots.default(), } ) ) } children.push( h( RecursionField, { attrs: { ...props, schema: schemaRef.value, }, }, {} ) ) return children }, } ) } }, } const MarkupField = { name: 'MarkupField', props: { type: String, ...markupProps, }, setup(props: ISchemaMarkupFieldProps, { slots }) { const parentRef = inject(SchemaMarkupSymbol, null) if (!parentRef || !parentRef.value) return () => h('template', {}, {}) const name = props.name || getRandomName() const appendArraySchema = (schema: ISchema) => { if (parentRef.value.items) { return parentRef.value.addProperty(name, schema) } else { return parentRef.value.setItems(resolveSchemaProps(props)) } } const schemaRef = shallowRef(null) watch( parentRef, () => { if ( parentRef.value.type === 'object' || parentRef.value.type === 'void' ) { schemaRef.value = parentRef.value.addProperty( name, resolveSchemaProps(props) ) } else if (parentRef.value.type === 'array') { const schema = appendArraySchema(resolveSchemaProps(props)) schemaRef.value = Array.isArray(schema) ? schema[0] : schema } }, { immediate: true } ) provide(SchemaMarkupSymbol, schemaRef) return () => { return h('div', { style: 'display: none;' }, slots) } }, } const SchemaFieldFactory = (type: SchemaTypes, name: string) => { return { name: name, props: { ...markupProps }, setup(props: ISchemaTypeFieldProps, { slots }) { return () => h( MarkupField, { attrs: { ...props, type: type, }, }, slots ) }, } } return { SchemaField, SchemaMarkupField: MarkupField, SchemaStringField: SchemaFieldFactory('string', 'SchemaStringField'), SchemaObjectField: SchemaFieldFactory('object', 'SchemaObjectField'), SchemaArrayField: SchemaFieldFactory('array', 'SchemaArrayField'), SchemaBooleanField: SchemaFieldFactory('boolean', 'SchemaBooleanField'), SchemaDateField: SchemaFieldFactory('date', 'SchemaDateField'), SchemaDateTimeField: SchemaFieldFactory('datetime', 'SchemaDatetimeField'), SchemaVoidField: SchemaFieldFactory('void', 'SchemaVoidField'), SchemaNumberField: SchemaFieldFactory('number', 'SchemaNumberField'), } as unknown as SchemaFieldComponents } ``` -------------------------------------------------------------------------------- /docs/guide/form-builder.md: -------------------------------------------------------------------------------- ```markdown # Form designer development guide ## Introduction  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. ## Core Concept 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 ## Install Ant Design users ```bash npm install --save @designable/formily-antd ``` Alibaba Fusion users ```bash npm install --save @designable/formily-next ``` ## Get started quickly [Example Source Code](https://github.com/alibaba/designable/tree/main/formily/antd/playground) ```tsx pure import 'antd/dist/antd.less' import React, { useMemo } from 'react' import ReactDOM from 'react-dom' import { Designer, //Designer root component, mainly used to deliver context DesignerToolsWidget, //Drawing board tool pendant ViewToolsWidget, //View switching tool pendant Workspace, //Workspace components, core components, used to manage drag and drop behavior in the workspace, tree node data, etc... OutlineTreeWidget, //Outline tree component, it will automatically identify the current workspace and display the tree nodes in the workspace ResourceWidget, //Drag and drop the source widget HistoryWidget, //History widget StudioPanel, //Main layout panel CompositePanel, //Left combined layout panel WorkspacePanel, //Workspace layout panel ToolbarPanel, //Toolbar layout panel ViewportPanel, //Viewport layout panel ViewPanel, //View layout panel SettingsPanel, //Configure the form layout panel on the right ComponentTreeWidget, //Component tree renderer } from '@designable/react' import { SettingsForm } from '@designable/react-settings-form' import { createDesigner, GlobalRegistry, Shortcut, KeyCode, } from '@designable/core' import { LogoWidget, ActionsWidget, PreviewWidget, SchemaEditorWidget, MarkupSchemaWidget, } from './widgets' import { saveSchema } from './service' import { Form, Field, Input, Select, TreeSelect, Cascader, Radio, Checkbox, Slider, Rate, NumberPicker, Transfer, Password, DatePicker, TimePicker, Upload, Switch, Text, Card, ArrayCards, ObjectContainer, ArrayTable, Space, FormTab, FormCollapse, FormLayout, FormGrid, } from '../src' GlobalRegistry.registerDesignerLocales({ 'zh-CN': { sources: { Inputs: 'Input controls', Layouts: 'Layout components', Arrays: 'Self-incrementing components', Displays: 'Display components', }, }, 'en-US': { sources: { Inputs: 'Inputs', Layouts: 'Layouts', Arrays: 'Arrays', Displays: 'Displays', }, }, }) const App = () => { const engine = useMemo( () => createDesigner({ shortcuts: [ new Shortcut({ codes: [ [KeyCode.Meta, KeyCode.S], [KeyCode.Control, KeyCode.S], ], handler(ctx) { saveSchema(ctx.engine) }, }), ], rootComponentName: 'Form', }), [] ) return ( <Designer engine={engine}> <StudioPanel logo={<LogoWidget />} actions={<ActionsWidget />}> <CompositePanel> <CompositePanel.Item title="panels.Component" icon="Component"> <ResourceWidget title="sources.Inputs" sources={[ Input, Password, NumberPicker, Rate, Slider, Select, TreeSelect, Cascader, Transfer, Checkbox, Radio, DatePicker, TimePicker, Upload, Switch, ObjectContainer, ]} /> <ResourceWidget title="sources.Layouts" sources={[ Card, FormGrid, FormTab, FormLayout, FormCollapse, Space, ]} /> <ResourceWidget title="sources.Arrays" sources={[ArrayCards, ArrayTable]} /> <ResourceWidget title="sources.Displays" sources={[Text]} /> </CompositePanel.Item> <CompositePanel.Item title="panels.OutlinedTree" icon="Outline"> <OutlineTreeWidget /> </CompositePanel.Item> <CompositePanel.Item title="panels.History" icon="History"> <HistoryWidget /> </CompositePanel.Item> </CompositePanel> <Workspace id="form"> <WorkspacePanel> <ToolbarPanel> <DesignerToolsWidget /> <ViewToolsWidget use={['DESIGNABLE', 'JSONTREE', 'MARKUP', 'PREVIEW']} /> </ToolbarPanel> <ViewportPanel> <ViewPanel type="DESIGNABLE"> {() => ( <ComponentTreeWidget components={{ Form, Field, Input, Select, TreeSelect, Cascader, Radio, Checkbox, Slider, Rate, NumberPicker, Transfer, Password, DatePicker, TimePicker, Upload, Switch, Text, Card, ArrayCards, ArrayTable, Space, FormTab, FormCollapse, FormGrid, FormLayout, ObjectContainer, }} /> )} </ViewPanel> <ViewPanel type="JSONTREE" scrollable={false}> {(tree, onChange) => ( <SchemaEditorWidget tree={tree} onChange={onChange} /> )} </ViewPanel> <ViewPanel type="MARKUP" scrollable={false}> {(tree) => <MarkupSchemaWidget tree={tree} />} </ViewPanel> <ViewPanel type="PREVIEW"> {(tree) => <PreviewWidget tree={tree} />} </ViewPanel> </ViewportPanel> </WorkspacePanel> </Workspace> <SettingsPanel title="panels.PropertySettings"> <SettingsForm uploadAction="https://www.mocky.io/v2/5cc8019d300000980a055e76" /> </SettingsPanel> </StudioPanel> </Designer> ) } ReactDOM.render(<App />, document.getElementById('root')) ``` ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormDrawer.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # FormDrawer > 抽屉表单,主要用在简单的事件打开表单场景 ## Markup Schema 案例 ```tsx import React from 'react' import { FormDrawer, FormItem, FormLayout, Input, Submit, Reset, FormButtonGroup, } from '@formily/antd' import { createSchemaField } from '@formily/react' import { Button } from 'antd' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) export default () => { return ( <Button onClick={() => { FormDrawer('抽屉表单', () => { return ( <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String name="aaa" required title="输入框1" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="bbb" required title="输入框2" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="ccc" required title="输入框3" x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="ddd" required title="输入框4" x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormDrawer.Extra> <FormButtonGroup align="right"> <Submit onSubmit={() => { return new Promise((resolve) => { setTimeout(resolve, 1000) }) }} > 提交 </Submit> <Reset>重置</Reset> </FormButtonGroup> </FormDrawer.Extra> </FormLayout> ) }) .forOpen((props, next) => { setTimeout(() => { next() }, 1000) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > 点我打开表单 </Button> ) } ``` ## JSON Schema 案例 ```tsx import React from 'react' import { FormDrawer, FormItem, FormLayout, Input, Submit, Reset, FormButtonGroup, } from '@formily/antd' import { createSchemaField } from '@formily/react' import { Button } from 'antd' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const schema = { type: 'object', properties: { aaa: { type: 'string', title: '输入框1', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, bbb: { type: 'string', title: '输入框2', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ccc: { type: 'string', title: '输入框3', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ddd: { type: 'string', title: '输入框4', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, } export default () => { return ( <Button onClick={() => { FormDrawer('弹窗表单', () => { return ( <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} /> <FormDrawer.Extra> <FormButtonGroup align="right"> <Submit onSubmit={() => { return new Promise((resolve) => { setTimeout(resolve, 1000) }) }} > 提交 </Submit> <Reset>重置</Reset> </FormButtonGroup> </FormDrawer.Extra> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > 点我打开表单 </Button> ) } ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { FormDrawer, FormItem, FormLayout, Input, Submit, Reset, FormButtonGroup, } from '@formily/antd' import { Field } from '@formily/react' import { Button } from 'antd' export default () => { return ( <Button onClick={() => { FormDrawer('弹窗表单', () => { return ( <FormLayout labelCol={6} wrapperCol={10}> <Field name="aaa" required title="输入框1" decorator={[FormItem]} component={[Input]} /> <Field name="bbb" required title="输入框2" decorator={[FormItem]} component={[Input]} /> <Field name="ccc" required title="输入框3" decorator={[FormItem]} component={[Input]} /> <Field name="ddd" required title="输入框4" decorator={[FormItem]} component={[Input]} /> <FormDrawer.Extra> <FormButtonGroup align="right"> <Submit onSubmit={() => { return new Promise((resolve) => { setTimeout(resolve, 1000) }) }} > 提交 </Submit> <Reset>重置</Reset> </FormButtonGroup> </FormDrawer.Extra> </FormLayout> ) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }} > 点我打开表单 </Button> ) } ``` ## API ### FormDrawer ```ts pure import { IFormProps, Form } from '@formily/core' type FormDrawerRenderer = | React.ReactElement | ((form: Form) => React.ReactElement) interface IFormDrawer { forOpen( middleware: ( props: IFormProps, next: (props?: IFormProps) => Promise<any> ) => any ): any //中间件拦截器,可以拦截Drawer打开 //打开弹窗,接收表单属性,可以传入initialValues/values/effects etc. open(props: IFormProps): Promise<any> //返回表单数据 //关闭弹窗 close(): void } export interface IDrawerProps extends DrawerProps { onClose?: (e: EventType) => void | boolean // return false can prevent onClose loadingText?: React.ReactNode } interface FormDrawer { (title: IDrawerProps, id: string, renderer: FormDrawerRenderer): IFormDrawer (title: IDrawerProps, renderer: FormDrawerRenderer): IFormDrawer (title: ModalTitle, id: string, renderer: FormDrawerRenderer): IFormDrawer (title: ModalTitle, renderer: FormDrawerRenderer): IFormDrawer } ``` `DrawerProps`类型定义参考 ant design [Drawer API](https://ant.design/components/drawer-cn/#API) ### FormDrawer.Extra 无属性,只接收子节点 ### FormDrawer.Footer 无属性,只接收子节点 ### FormDrawer.Portal 接收可选的 id 属性,默认值为`form-drawer`,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id ``` -------------------------------------------------------------------------------- /packages/antd/src/array-collapse/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment, useState, useEffect } from 'react' import { Badge, Card, Collapse, CollapsePanelProps, CollapseProps, Empty, } from 'antd' import { ArrayField } from '@formily/core' import { RecursionField, useField, useFieldSchema, observer, ISchema, } from '@formily/react' import { toArr } from '@formily/shared' import cls from 'classnames' import ArrayBase, { ArrayBaseMixins, IArrayBaseProps } from '../array-base' import { usePrefixCls } from '../__builtins__' export interface IArrayCollapseProps extends CollapseProps { defaultOpenPanelCount?: number } type ComposedArrayCollapse = React.FC< React.PropsWithChildren<IArrayCollapseProps & IArrayBaseProps> > & ArrayBaseMixins & { CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>> } const isAdditionComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Addition') > -1 } const isIndexComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Index') > -1 } const isRemoveComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Remove') > -1 } const isMoveUpComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveUp') > -1 } const isMoveDownComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveDown') > -1 } const isOperationComponent = (schema: ISchema) => { return ( isAdditionComponent(schema) || isRemoveComponent(schema) || isMoveDownComponent(schema) || isMoveUpComponent(schema) ) } const range = (count: number) => Array.from({ length: count }).map((_, i) => i) const takeDefaultActiveKeys = ( dataSourceLength: number, defaultOpenPanelCount: number ) => { if (dataSourceLength < defaultOpenPanelCount) return range(dataSourceLength) return range(defaultOpenPanelCount) } const insertActiveKeys = (activeKeys: number[], index: number) => { if (activeKeys.length <= index) return activeKeys.concat(index) return activeKeys.reduce((buf, key) => { if (key < index) return buf.concat(key) if (key === index) return buf.concat([key, key + 1]) return buf.concat(key + 1) }, []) } export const ArrayCollapse: ComposedArrayCollapse = observer( ({ defaultOpenPanelCount = 5, ...props }) => { const field = useField<ArrayField>() const dataSource = Array.isArray(field.value) ? field.value : [] const [activeKeys, setActiveKeys] = useState<number[]>( takeDefaultActiveKeys(dataSource.length, defaultOpenPanelCount) ) const schema = useFieldSchema() const prefixCls = usePrefixCls('formily-array-collapse', props) useEffect(() => { if (!field.modified && dataSource.length) { setActiveKeys( takeDefaultActiveKeys(dataSource.length, defaultOpenPanelCount) ) } }, [dataSource.length, field]) if (!schema) throw new Error('can not found schema object') const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props const renderAddition = () => { return schema.reduceProperties((addition, schema, key) => { if (isAdditionComponent(schema)) { return <RecursionField schema={schema} name={key} /> } return addition }, null) } const renderEmpty = () => { if (dataSource.length) return return ( <Card className={cls(`${prefixCls}-item`, props.className)}> <Empty /> </Card> ) } const renderItems = () => { return ( <Collapse {...props} activeKey={activeKeys} onChange={(keys: string[]) => setActiveKeys(toArr(keys).map(Number))} className={cls(`${prefixCls}-item`, props.className)} > {dataSource.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items const panelProps = field .query(`${field.address}.${index}`) .get('componentProps') const props: CollapsePanelProps = items['x-component-props'] const header = () => { const header = panelProps?.header || props.header || field.title const path = field.address.concat(index) const errors = field.form.queryFeedbacks({ type: 'error', address: `${path}.**`, }) return ( <ArrayBase.Item index={index} record={() => field.value?.[index]} > <RecursionField schema={items} name={index} filterProperties={(schema) => { if (!isIndexComponent(schema)) return false return true }} onlyRenderProperties /> {errors.length ? ( <Badge size="small" className="errors-badge" count={errors.length} > {header} </Badge> ) : ( header )} </ArrayBase.Item> ) } const extra = ( <ArrayBase.Item index={index} record={item}> <RecursionField schema={items} name={index} filterProperties={(schema) => { if (!isOperationComponent(schema)) return false return true }} onlyRenderProperties /> {panelProps?.extra} </ArrayBase.Item> ) const content = ( <RecursionField schema={items} name={index} filterProperties={(schema) => { if (isIndexComponent(schema)) return false if (isOperationComponent(schema)) return false return true }} /> ) return ( <Collapse.Panel {...props} {...panelProps} forceRender key={index} header={header()} extra={extra} > <ArrayBase.Item index={index} key={index} record={item}> {content} </ArrayBase.Item> </Collapse.Panel> ) })} </Collapse> ) } return ( <ArrayBase onAdd={(index) => { onAdd?.(index) setActiveKeys(insertActiveKeys(activeKeys, index)) }} onCopy={onCopy} onRemove={onRemove} onMoveUp={onMoveUp} onMoveDown={onMoveDown} > {renderEmpty()} {renderItems()} {renderAddition()} </ArrayBase> ) } ) const CollapsePanel: React.FC<React.PropsWithChildren<CollapsePanelProps>> = ({ children, }) => { return <Fragment>{children}</Fragment> } CollapsePanel.displayName = 'CollapsePanel' ArrayCollapse.displayName = 'ArrayCollapse' ArrayCollapse.CollapsePanel = CollapsePanel ArrayBase.mixin(ArrayCollapse) export default ArrayCollapse ``` -------------------------------------------------------------------------------- /packages/next/src/form-dialog/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment, useRef, useLayoutEffect, useState } from 'react' import { createPortal } from 'react-dom' import { createForm, IFormProps, Form } from '@formily/core' import { toJS } from '@formily/reactive' import { FormProvider, Observer, observer, ReactFC } from '@formily/react' import { isNum, isStr, isBool, isFn, applyMiddleware, IMiddleware, } from '@formily/shared' import { Dialog, ConfigProvider, Button } from '@alifd/next' import { DialogProps } from '@alifd/next/lib/dialog' import { usePrefixCls, loading, createPortalProvider, createPortalRoot, } from '../__builtins__' type FormDialogRenderer = | React.ReactElement | ((form: Form) => React.ReactElement) type ModalTitle = string | number | React.ReactElement const getContext: () => any = ConfigProvider['getContext'] const isModalTitle = (props: any): props is ModalTitle => { return ( isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props) ) } const getModelProps = (props: any): IDialogProps => { if (isModalTitle(props)) { return { title: props, } } else { return props } } export interface IDialogProps extends DialogProps { onOk?: (event: React.MouseEvent) => void | boolean onCancel?: (event: React.MouseEvent) => void | boolean loadingText?: React.ReactText } export interface IFormDialog { forOpen(middleware: IMiddleware<IFormProps>): IFormDialog forConfirm(middleware: IMiddleware<Form>): IFormDialog forCancel(middleware: IMiddleware<Form>): IFormDialog open(props?: IFormProps): Promise<any> close(): void } export function FormDialog( title: IDialogProps, id: string, renderer: FormDialogRenderer ): IFormDialog export function FormDialog( title: IDialogProps, renderer: FormDialogRenderer ): IFormDialog export function FormDialog( title: ModalTitle, id: string, renderer: FormDialogRenderer ): IFormDialog export function FormDialog( title: ModalTitle, renderer: FormDialogRenderer ): IFormDialog export function FormDialog(title: any, id: any, renderer?: any): IFormDialog { if (isFn(id) || React.isValidElement(id)) { renderer = id id = 'form-dialog' } const env = { host: document.createElement('div'), form: null, promise: null, openMiddlewares: [], confirmMiddlewares: [], cancelMiddlewares: [], } const root = createPortalRoot(env.host, id) const props = getModelProps(title) const modal = { ...props, style: { width: '40%', ...props.style, }, afterClose: () => { props?.afterClose?.() root.unmount() }, } const DialogContent = observer(() => { return <Fragment>{isFn(renderer) ? renderer(env.form) : renderer}</Fragment> }) const renderDialog = ( visible = true, resolve?: () => any, reject?: () => any ) => { const ctx = getContext() const prefix = modal.prefix || ctx.prefix || 'next' const okProps = { children: ctx?.locale?.Dialog?.ok || '确定', ...(ctx?.defaultPropsConfig?.Dialog?.okProps || {}), ...(modal.okProps || {}), } const cancelProps = { children: ctx?.locale?.Dialog?.cancel || '取消', ...(ctx?.defaultPropsConfig?.Dialog?.cancelProps || {}), ...(modal.cancelProps || {}), } const buttonMap = { ok: ( <Button key="ok" type="primary" className={prefix + '-dialog-btn'} loading={env.form.submitting} onClick={(e) => { if (modal?.onOk?.(e) !== false) { resolve() } }} {...okProps} /> ), cancel: ( <Button key="cancel" className={prefix + '-dialog-btn'} onClick={(e) => { if (modal?.onCancel?.(e) !== false) { reject() } }} {...cancelProps} /> ), } const footerActions = ctx?.defaultPropsConfig?.Dialog?.footerActions || modal.footerActions || ['cancel', 'ok'] return ( <ConfigProvider {...ctx}> <Observer> {() => ( <Dialog {...modal} visible={visible} footer={ <Fragment> {footerActions.map((item) => buttonMap[item])} </Fragment> } onClose={(trigger, e) => { modal?.onClose?.(trigger, e) reject() }} > <FormProvider form={env.form}> <DialogContent /> </FormProvider> </Dialog> )} </Observer> </ConfigProvider> ) } document.body.appendChild(env.host) const formDialog = { forOpen: (middleware: IMiddleware<IFormProps>) => { if (isFn(middleware)) { env.openMiddlewares.push(middleware) } return formDialog }, forConfirm: (middleware: IMiddleware<Form>) => { if (isFn(middleware)) { env.confirmMiddlewares.push(middleware) } return formDialog }, forCancel: (middleware: IMiddleware<Form>) => { if (isFn(middleware)) { env.cancelMiddlewares.push(middleware) } return formDialog }, open: async (props: IFormProps) => { if (env.promise) return env.promise env.promise = new Promise(async (resolve, reject) => { try { props = await loading(modal.loadingText, () => applyMiddleware(props, env.openMiddlewares) ) env.form = env.form || createForm(props) } catch (e) { reject(e) } root.render(() => renderDialog( true, () => { env.form .submit(async () => { await applyMiddleware(env.form, env.confirmMiddlewares) resolve(toJS(env.form.values)) formDialog.close() }) .catch(() => {}) }, async () => { await loading(modal.loadingText, () => applyMiddleware(env.form, env.cancelMiddlewares) ) formDialog.close() } ) ) }) return env.promise }, close: () => { if (!env.host) return root.render(() => renderDialog(false)) }, } return formDialog } const DialogFooter: ReactFC = (props) => { const ref = useRef<HTMLDivElement>() const [footer, setFooter] = useState<HTMLDivElement>() const footerRef = useRef<HTMLDivElement>() const prefixCls = usePrefixCls('dialog') useLayoutEffect(() => { const content = ref.current?.closest(`.${prefixCls}`) if (content) { if (!footerRef.current) { footerRef.current = content.querySelector(`.${prefixCls}-footer`) if (!footerRef.current) { footerRef.current = document.createElement('div') footerRef.current.classList.add(`${prefixCls}-footer`) content.appendChild(footerRef.current) } } setFooter(footerRef.current) } }) footerRef.current = footer return ( <div ref={ref} style={{ display: 'none' }}> {footer && createPortal(props.children, footer)} </div> ) } FormDialog.Footer = DialogFooter FormDialog.Portal = createPortalProvider('form-dialog') export default FormDialog ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/DatePicker.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # DatePicker > 日期选择器 ## Markup Schema 案例 ```tsx import React from 'react' import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { DatePicker, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="date" required title="普通日期" x-decorator="FormItem" x-component="DatePicker" /> <SchemaField.String name="week" title="周选择" x-decorator="FormItem" x-component="DatePicker" x-component-props={{ picker: 'week', }} /> <SchemaField.String name="month" title="月选择" x-decorator="FormItem" x-component="DatePicker" x-component-props={{ picker: 'month', }} /> <SchemaField.String name="quarter" title="财年选择" x-decorator="FormItem" x-component="DatePicker" x-component-props={{ picker: 'quarter', }} /> <SchemaField.String name="year" title="年选择" x-decorator="FormItem" x-component="DatePicker" x-component-props={{ picker: 'year', }} /> <SchemaField.String name="[startDate,endDate]" title="日期范围" x-decorator="FormItem" x-component="DatePicker.RangePicker" x-component-props={{ showTime: true, }} /> <SchemaField.String name="range_week" title="周范围选择" x-decorator="FormItem" x-component="DatePicker.RangePicker" x-component-props={{ picker: 'week', }} /> <SchemaField.String name="range_month" title="月范围选择" x-decorator="FormItem" x-component="DatePicker.RangePicker" x-component-props={{ picker: 'month', }} /> <SchemaField.String name="range_quarter" title="财年范围选择" x-decorator="FormItem" x-component="DatePicker.RangePicker" x-component-props={{ picker: 'quarter', }} /> <SchemaField.String name="range_year" title="年范围选择" x-decorator="FormItem" x-component="DatePicker.RangePicker" x-component-props={{ picker: 'year', }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { DatePicker, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { date: { title: '普通日期', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', type: 'string', }, week: { title: '周选择', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { picker: 'week', }, type: 'string', }, month: { title: '月选择', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { picker: 'month', }, type: 'string', }, quarter: { title: '财年选择', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { picker: 'quarter', }, type: 'string', }, year: { title: '年选择', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { picker: 'year', }, type: 'string', }, '[startDate,endDate]': { title: '日期范围', 'x-decorator': 'FormItem', 'x-component': 'DatePicker.RangePicker', 'x-component-props': { showTime: true, }, type: 'string', }, range_week: { title: '周范围选择', 'x-decorator': 'FormItem', 'x-component': 'DatePicker.RangePicker', 'x-component-props': { picker: 'week', }, type: 'string', }, range_month: { title: '月范围选择', 'x-decorator': 'FormItem', 'x-component': 'DatePicker.RangePicker', 'x-component-props': { picker: 'month', }, type: 'string', }, range_quarter: { title: '财年范围选择', 'x-decorator': 'FormItem', 'x-component': 'DatePicker.RangePicker', 'x-component-props': { picker: 'quarter', }, type: 'string', }, range_year: { name: 'range_year', title: '年范围选择', 'x-decorator': 'FormItem', 'x-component': 'DatePicker.RangePicker', 'x-component-props': { picker: 'year', }, type: 'string', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="date" title="日期选择" decorator={[FormItem]} component={[DatePicker]} /> <Field name="week" title="周选择" decorator={[FormItem]} component={[ DatePicker, { picker: 'week', }, ]} /> <Field name="quarter" title="财年选择" decorator={[FormItem]} component={[ DatePicker, { picker: 'month', }, ]} /> <Field name="year" title="年选择" decorator={[FormItem]} component={[ DatePicker, { picker: 'year', }, ]} /> <Field name="[startDate,endDate]" title="日期范围选择" decorator={[FormItem]} component={[DatePicker.RangePicker]} /> <Field name="range_week" title="周范围选择" decorator={[FormItem]} component={[ DatePicker.RangePicker, { picker: 'week', }, ]} /> <Field name="range_month" title="月范围选择" decorator={[FormItem]} component={[ DatePicker.RangePicker, { picker: 'month', }, ]} /> <Field name="range_quarter" title="财年范围选择" decorator={[FormItem]} component={[ DatePicker.RangePicker, { picker: 'quarter', }, ]} /> <Field name="range_year" title="年范围选择" decorator={[FormItem]} component={[ DatePicker.RangePicker, { picker: 'year', }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://ant.design/components/date-picker-cn/ ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Upload.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Upload > 上传组件 > > 注意:使用上传组件,推荐用户进行二次封装,用户无需关心上传组件与 Formily 的数据通信,只需要处理样式与基本上传配置即可。 ## Markup Schema 案例 ```tsx import React from 'react' import { Upload, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from '@alifd/next' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} withCredentials={false} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button> <UploadOutlined /> 上传文件 </Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload.Card {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" withCredentials={false} headers={{ authorization: 'authorization-text', }} /> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} withCredentials={false} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <div className="next-upload-drag"> <p className="next-upload-drag-icon" style={{ fontSize: 50 }}> <InboxOutlined /> </p> <p className="next-upload-drag-text"> click to <Button text>download template</Button> or drag file here </p> <p className="next-upload-drag-hint">supports docx, xls, PDF </p> </div> </Upload.Dragger> ) } const SchemaField = createSchemaField({ components: { NormalUpload, CardUpload, DraggerUpload, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={14}> <SchemaField> <SchemaField.Array name="upload" title="上传" x-decorator="FormItem" x-component="NormalUpload" required /> <SchemaField.Array name="upload2" title="卡片上传" x-decorator="FormItem" x-component="CardUpload" required /> <SchemaField.Array name="upload3" title="拖拽上传" x-decorator="FormItem" x-component="DraggerUpload" required /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Upload, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from '@alifd/next' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} withCredentials={false} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button> <UploadOutlined /> 上传文件 </Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload.Card {...props} withCredentials={false} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} /> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} withCredentials={false} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <div className="next-upload-drag"> <p className="next-upload-drag-icon" style={{ fontSize: 50 }}> <InboxOutlined /> </p> <p className="next-upload-drag-text"> click to <Button text>download template</Button> or drag file here </p> <p className="next-upload-drag-hint">supports docx, xls, PDF </p> </div> </Upload.Dragger> ) } const SchemaField = createSchemaField({ components: { NormalUpload, CardUpload, DraggerUpload, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { upload: { type: 'array', title: '上传', required: true, 'x-decorator': 'FormItem', 'x-component': 'NormalUpload', }, upload2: { type: 'array', title: '卡片上传', required: true, 'x-decorator': 'FormItem', 'x-component': 'CardUpload', }, upload3: { type: 'array', title: '拖拽上传', required: true, 'x-decorator': 'FormItem', 'x-component': 'DraggerUpload', }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={14}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Upload, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' import { Button } from '@alifd/next' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} withCredentials={false} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button> <UploadOutlined /> 上传文件 </Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload.Card {...props} withCredentials={false} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} /> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} withCredentials={false} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <div className="next-upload-drag"> <p className="next-upload-drag-icon" style={{ fontSize: 50 }}> <InboxOutlined /> </p> <p className="next-upload-drag-text"> click to <Button text>download template</Button> or drag file here </p> <p className="next-upload-drag-hint">supports docx, xls, PDF </p> </div> </Upload.Dragger> ) } const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={14}> <Field name="upload" title="上传" required decorator={[FormItem]} component={[NormalUpload]} /> <Field name="upload2" title="卡片上传" required decorator={[FormItem]} component={[CardUpload]} /> <Field name="upload3" title="拖拽上传" required decorator={[FormItem]} component={[DraggerUpload]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/upload ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Space.md: -------------------------------------------------------------------------------- ```markdown # Space > Super convenient Flex layout component, can help users quickly realize the layout of any element side by side next to each other ## Markup Schema example ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, Space, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <SchemaField> <SchemaField.Void title="name" x-decorator="FormItem" x-decorator-props={{ asterisk: true, feedbackLayout: 'none', }} x-component="Space" > <SchemaField.String name="firstName" x-decorator="FormItem" x-component="Input" required /> <SchemaField.String name="lastName" x-decorator="FormItem" x-component="Input" required /> </SchemaField.Void> <SchemaField.Void title="Text concatenation" x-decorator="FormItem" x-decorator-props={{ asterisk: true, feedbackLayout: 'none', }} x-component="Space" > <SchemaField.String name="aa" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> <SchemaField.String name="bb" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> <SchemaField.String name="cc" x-decorator="FormItem" x-component="Input" x-decorator-props={{ addonAfter: 'Unit', }} required /> </SchemaField.Void> <SchemaField.String name="textarea" title="text box" x-decorator="FormItem" required x-component="Input.TextArea" x-component-props={{ style: { width: 400, }, }} /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, Space, }, }) const form = createForm() const schema = { type: 'object', properties: { name: { type: 'void', title: 'Name', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { firstName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, lastName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, }, }, texts: { type: 'void', title: 'Text concatenation', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { aa: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, bb: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, cc: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: 'Unit', }, 'x-component': 'Input', required: true, }, }, }, textarea: { type: 'string', title: 'Text box', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 400, }, }, required: true, }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Input, FormItem, FormLayout, FormButtonGroup, Submit, Space, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field, VoidField } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={16}> <VoidField name="name" title="name" decorator={[ FormItem, { asterisk: true, feedbackLayout: 'none', }, ]} component={[Space]} > <Field name="firstName" decorator={[FormItem]} component={[Input]} required /> <Field name="lastName" decorator={[FormItem]} component={[Input]} required /> </VoidField> <VoidField name="texts" title="Text concatenation" decorator={[ FormItem, { asterisk: true, feedbackLayout: 'none', }, ]} component={[Space]} > <Field name="aa" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> <Field name="bb" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> <Field name="cc" decorator={[ FormItem, { addonAfter: 'Unit', }, ]} component={[Input]} required /> </VoidField> <Field name="textarea" title="text box" decorator={[FormItem]} component={[ Input.TextArea, { style: { width: 400, }, }, ]} required /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API Reference https://ant.design/components/space-cn/ ``` -------------------------------------------------------------------------------- /packages/element/src/editable/index.ts: -------------------------------------------------------------------------------- ```typescript import { Field, isVoidField } from '@formily/core' import { reaction } from '@formily/reactive' import { observer } from '@formily/reactive-vue' import { h, useField } from '@formily/vue' import { Popover } from 'element-ui' import { defineComponent, onBeforeUnmount, ref } from 'vue-demi' import { stylePrefix } from '../__builtins__/configs' import type { Popover as PopoverProps } from 'element-ui' import { FormBaseItem, FormItemProps } from '../form-item' import { composeExport, useCompatRef } from '../__builtins__/shared' export type EditableProps = FormItemProps export type EditablePopoverProps = PopoverProps const getParentPattern = (fieldRef) => { const field = fieldRef.value return field?.parent?.pattern || field?.form?.pattern } const getFormItemProps = (fieldRef): FormItemProps => { const field = fieldRef.value if (isVoidField(field)) return {} if (!field) return {} const takeMessage = () => { if (field.selfErrors.length) return field.selfErrors[0] if (field.selfWarnings.length) return field.selfWarnings[0] if (field.selfSuccesses.length) return field.selfSuccesses[0] } return { feedbackStatus: field.validateStatus === 'validating' ? 'pending' : field.validateStatus, feedbackText: takeMessage(), extra: field.description, } } const EditableInner = observer( defineComponent<EditableProps>({ name: 'FEditable', setup(props, { attrs, slots, refs }) { const fieldRef = useField<Field>() const { elRef: innerRef, elRefBinder } = useCompatRef(refs) const prefixCls = `${stylePrefix}-editable` const setEditable = (payload: boolean) => { const pattern = getParentPattern(fieldRef) if (pattern !== 'editable') return fieldRef.value.setPattern(payload ? 'editable' : 'readPretty') } const dispose = reaction( () => { const pattern = getParentPattern(fieldRef) return pattern }, (pattern) => { if (pattern === 'editable') { fieldRef.value.setPattern('readPretty') } }, { fireImmediately: true, } ) onBeforeUnmount(dispose) return () => { const field = fieldRef.value const editable = field.pattern === 'editable' const pattern = getParentPattern(fieldRef) const itemProps = getFormItemProps(fieldRef) const recover = () => { if (editable && !fieldRef.value?.errors?.length) { setEditable(false) } } const onClick = (e: MouseEvent) => { const target = e.target as HTMLElement const close = innerRef.value.querySelector(`.${prefixCls}-close-btn`) if (target?.contains(close) || close?.contains(target)) { recover() } else if (!editable) { setTimeout(() => { setEditable(true) setTimeout(() => { innerRef.value.querySelector('input')?.focus() }) }) } } const renderEditHelper = () => { if (editable) return null return h( FormBaseItem, { attrs: { ...attrs, ...itemProps, }, }, { default: () => { return h( 'i', { class: [ `${prefixCls}-edit-btn`, pattern === 'editable' ? 'el-icon-edit' : 'el-icon-chat-dot-round', ], }, {} ) }, } ) } const renderCloseHelper = () => { if (!editable) return null return h( FormBaseItem, { attrs: { ...attrs, }, }, { default: () => { return h( 'i', { class: [`${prefixCls}-close-btn`, 'el-icon-close'], }, {} ) }, } ) } return h( 'div', { class: prefixCls, ref: elRefBinder, on: { click: onClick, }, }, { default: () => h( 'div', { class: `${prefixCls}-content`, }, { default: () => [ h( FormBaseItem, { attrs: { ...attrs, ...itemProps, }, }, slots ), renderEditHelper(), renderCloseHelper(), ], } ), } ) } }, }) ) const EditablePopover = observer( defineComponent<EditablePopoverProps>({ name: 'FEditablePopover', setup(props, { attrs, slots }) { const fieldRef = useField<Field>() const prefixCls = `${stylePrefix}-editable` const visible = ref(false) return () => { const field = fieldRef.value const pattern = getParentPattern(fieldRef) return h( Popover, { class: [prefixCls], attrs: { ...attrs, title: attrs.title || field.title, value: visible.value, trigger: 'click', }, on: { input: (value) => { visible.value = value }, }, }, { default: () => [slots.default()], reference: () => h( FormBaseItem, { class: [`${prefixCls}-trigger`] }, { default: () => h( 'div', { class: [`${prefixCls}-content`], }, { default: () => [ h( 'span', { class: [`${prefixCls}-preview`], }, { default: () => [attrs.title || field.title], } ), h( 'i', { class: [ `${prefixCls}-edit-btn`, pattern === 'editable' ? 'el-icon-edit' : 'el-icon-chat-dot-round', ], }, {} ), ], } ), } ), } ) } }, }) ) export const Editable = composeExport(EditableInner, { Popover: EditablePopover, }) export default Editable ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/schema.spec.ts: -------------------------------------------------------------------------------- ```typescript import { Schema } from '../' import { isFn } from '@formily/shared' test('has methods', () => { const schema = new Schema({ type: 'object', properties: { aa: { type: 'string', }, }, }) expect(isFn(schema.setAdditionalItems)).toBeTruthy() expect(isFn(schema.setAdditionalProperties)).toBeTruthy() expect(isFn(schema.setItems)).toBeTruthy() expect(isFn(schema.setPatternProperties)).toBeTruthy() expect(isFn(schema.setProperties)).toBeTruthy() expect(isFn(schema.addPatternProperty)).toBeTruthy() expect(isFn(schema.addProperty)).toBeTruthy() expect(isFn(schema.fromJSON)).toBeTruthy() expect(isFn(schema.toJSON)).toBeTruthy() expect(isFn(schema.reducePatternProperties)).toBeTruthy() expect(isFn(schema.reduceProperties)).toBeTruthy() expect(isFn(schema.removeProperty)).toBeTruthy() expect(isFn(schema.removePatternProperty)).toBeTruthy() expect(isFn(schema.mapPatternProperties)).toBeTruthy() expect(isFn(schema.mapProperties)).toBeTruthy() expect(isFn(Schema.isSchemaInstance)).toBeTruthy() expect(isFn(Schema.registerCompiler)).toBeTruthy() expect(isFn(Schema.registerPatches)).toBeTruthy() expect(isFn(Schema.shallowCompile)).toBeTruthy() expect(isFn(Schema.compile)).toBeTruthy() expect(isFn(Schema.getOrderProperties)).toBeTruthy() }) test('all props', () => { const schema = new Schema({ type: 'object', title: 'title', description: 'description', patternProperties: { '^[a-zA-Z0-9]*$': { properties: { model: { type: 'string' }, made: { type: 'string' }, year: { type: 'string' }, }, }, }, additionalProperties: { type: 'string', }, properties: { string: { type: 'string', default: 'default', required: true, 'x-component': 'Input', 'x-component-props': { placeholder: 'placeholder', }, 'x-decorator': 'FormItem', 'x-decorator-props': { labelCol: 3, }, 'x-disabled': true, 'x-display': 'visible', 'x-editable': false, 'x-hidden': false, 'x-pattern': 'readPretty', 'x-read-only': true, 'x-validator': ['phone'], 'x-reactions': [ { target: 'xxx', when: '{{aa > bb}}', }, ], }, boolean: { type: 'boolean', default: false, }, number: { type: 'number', default: 100, }, date: { type: 'date', default: '2020-12-23', }, datetime: { type: 'datetime', default: '2020-12-23 23:00:00', }, array: { type: 'array', items: { type: 'string', }, additionalItems: { type: 'number', }, }, array2: { type: 'array', items: [ { type: 'string', }, { type: 'object', }, ], }, void: { type: 'void', }, }, }) expect(schema).toMatchSnapshot() }) test('all methods', () => { const schema = new Schema({ type: 'object', 'x-reactions': null, }) const schema2 = new Schema({ type: 'object', fn: () => {}, } as any) const schema3 = new Schema({ type: 'object', additionalItems: null, additionalProperties: null, properties: null, }) schema3.additionalItems = null schema3.additionalProperties = null schema3.properties = { xxx: null, } schema3.items = [null] const schema4 = new Schema({ type: 'object', additionalItems: {}, additionalProperties: {}, properties: {}, }) schema4.additionalItems = {} as any schema4.additionalProperties = {} as any schema4.properties = { xxx: {} as any, } schema4.items = [{}] as any const schema5 = new Schema({ type: 'array', }) schema5.items = null const schema6 = new Schema({ type: 'array', }) schema6.items = {} as any const schema7 = new Schema({ type: 'array', items: { type: 'string', }, }) const string = schema.addProperty('string', { type: 'string', title: 'string', description: null, 'x-reactions': [ { target: 'xxx', when: true, fulfill: { schema: {}, }, }, ], }) const array = schema.addProperty('array', { type: 'string', title: 'string', items: [{ type: 'integer' }, { type: 'integer' }], }) const pattern = schema.addPatternProperty('^[a-zA-Z0-9]*$', { properties: { model: { type: 'string', 'x-index': 2 }, made: { type: 'string', 'x-index': 1 }, year: { type: 'string', 'x-index': 0 }, }, }) schema.addPatternProperty('xxx', null) schema.setAdditionalProperties({ type: 'string', }) schema.setAdditionalProperties(null) array.setItems(null) array.setAdditionalItems({ type: 'string', }) array.setAdditionalItems(null) schema.setPatternProperties(null) schema.fromJSON(null) expect(schema2['fn']).toBeUndefined() expect(schema.properties.string).not.toBeUndefined() expect(schema.patternProperties['^[a-zA-Z0-9]*$']).not.toBeUndefined() expect(schema).toMatchSnapshot() expect(schema.toJSON()).toMatchSnapshot() expect(pattern.mapProperties((schema, key) => key)).toEqual([ 'year', 'made', 'model', ]) expect( pattern.reduceProperties((buf, schema, key) => buf.concat('_' + key), []) ).toEqual(['_year', '_made', '_model']) expect(schema.mapPatternProperties((schema, key) => key)).toEqual([ '^[a-zA-Z0-9]*$', ]) expect( schema.reducePatternProperties( (buf, schema, key) => buf.concat('_' + key), [] ) ).toEqual(['_^[a-zA-Z0-9]*$']) schema5.toJSON() schema6.toJSON() schema7.toJSON() schema.removeProperty('string') expect(schema.properties.string).toBeUndefined() schema.removePatternProperty('^[a-zA-Z0-9]*$') expect(schema.patternProperties['^[a-zA-Z0-9]*$']).toBeUndefined() expect(schema.compile()).toMatchSnapshot() expect(string.compile()).toMatchSnapshot() expect(schema3.toJSON()).toMatchSnapshot() expect(schema4.toJSON()).toMatchSnapshot() }) describe('all static methods', () => { expect(Schema.compile({ aa: '{{123}}' })).toEqual({ aa: 123 }) expect(Schema.shallowCompile('{{123}}')).toEqual(123) expect(Schema.getOrderProperties()).toEqual([]) Schema.registerPatches(null) }) test('single function x-reactions', () => { const reactions = () => console.info('x-reactions') const schema = new Schema({ type: 'string', 'x-reactions': reactions, }) expect(schema.compile()['x-reactions']).toEqual(reactions) }) test('definitions and $ref', () => { const schema = new Schema({ definitions: { address: { type: 'object', properties: { street_address: { type: 'string', }, city: { type: 'string', }, state: { type: 'string', }, }, required: ['street_address', 'city', 'state'], }, }, type: 'object', properties: { billing_address: { title: 'Billing address', $ref: '#/definitions/address', }, shipping_address: { title: 'Shipping address', $ref: '#/definitions/address', }, }, }) expect(schema.properties.billing_address.required).toEqual([ 'street_address', 'city', 'state', ]) }) ``` -------------------------------------------------------------------------------- /packages/next/src/array-collapse/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment, useState, useEffect } from 'react' import { Badge, Card, Collapse } from '@alifd/next' import { ArrayField } from '@formily/core' import { RecursionField, useField, useFieldSchema, observer, ISchema, } from '@formily/react' import { toArr } from '@formily/shared' import cls from 'classnames' import ArrayBase, { ArrayBaseMixins, IArrayBaseProps } from '../array-base' import { usePrefixCls, Empty } from '../__builtins__' import { CollapseProps, PanelProps } from '@alifd/next/lib/collapse' export interface IArrayCollapseProps extends CollapseProps { defaultOpenPanelCount?: number } type ComposedArrayCollapse = React.FC< React.PropsWithChildren<IArrayCollapseProps & IArrayBaseProps> > & ArrayBaseMixins & { CollapsePanel?: React.FC<React.PropsWithChildren<PanelProps>> } const isAdditionComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Addition') > -1 } const isIndexComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Index') > -1 } const isRemoveComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Remove') > -1 } const isMoveUpComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveUp') > -1 } const isMoveDownComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveDown') > -1 } const isOperationComponent = (schema: ISchema) => { return ( isAdditionComponent(schema) || isRemoveComponent(schema) || isMoveDownComponent(schema) || isMoveUpComponent(schema) ) } const range = (count: number) => Array.from({ length: count }).map((_, i) => i) const takeDefaultExpandedKeys = ( dataSourceLength: number, defaultOpenPanelCount: number ) => { if (dataSourceLength < defaultOpenPanelCount) return range(dataSourceLength) return range(defaultOpenPanelCount) } const insertExpandedKeys = (expandedKeys: number[], index: number) => { if (expandedKeys.length <= index) return expandedKeys.concat(index) return expandedKeys.reduce((buf, key) => { if (key < index) return buf.concat(key) if (key === index) return buf.concat([key, key + 1]) return buf.concat(key + 1) }, []) } export const ArrayCollapse: ComposedArrayCollapse = observer( ({ defaultOpenPanelCount = 5, ...props }) => { const field = useField<ArrayField>() const dataSource = Array.isArray(field.value) ? field.value : [] const [expandKeys, setExpandKeys] = useState<number[]>( takeDefaultExpandedKeys(dataSource.length, defaultOpenPanelCount) ) const schema = useFieldSchema() const prefixCls = usePrefixCls('formily-array-collapse', props) useEffect(() => { if (!field.modified && dataSource.length) { setExpandKeys( takeDefaultExpandedKeys(dataSource.length, defaultOpenPanelCount) ) } }, [dataSource.length, field]) if (!schema) throw new Error('can not found schema object') const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props const renderAddition = () => { return schema.reduceProperties((addition, schema, key) => { if (isAdditionComponent(schema)) { return <RecursionField schema={schema} name={key} /> } return addition }, null) } const renderEmpty = () => { if (dataSource.length) return return ( <Card className={cls(`${prefixCls}-item`, props.className)}> <Empty /> </Card> ) } const renderItems = () => { return ( <Collapse {...props} onChange={() => {}} expandedKeys={expandKeys.map(String)} onExpand={(keys: string[]) => setExpandKeys(toArr(keys).map(Number))} className={cls(`${prefixCls}-item`, props.className)} > {dataSource.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items const panelProps = field .query(`${field.address}.${index}`) .get('componentProps') const props: PanelProps = items['x-component-props'] const title = () => { const title = `${ panelProps?.title || props?.title || field.title }` const path = field.address.concat(index) const errors = field.form.queryFeedbacks({ type: 'error', address: `${path}.**`, }) return ( <ArrayBase.Item index={index} record={item}> <div className={cls(`${prefixCls}-item-title`, props.className)} > <div> <RecursionField schema={items} name={index} filterProperties={(schema) => { if (!isIndexComponent(schema)) return false return true }} onlyRenderProperties /> {errors.length ? ( <Badge className="errors-badge" count={errors.length}> {title} </Badge> ) : ( title )} </div> <div> <RecursionField schema={items} name={index} filterProperties={(schema) => { if (!isOperationComponent(schema)) return false return true }} onlyRenderProperties /> </div> </div> </ArrayBase.Item> ) } const content = ( <RecursionField schema={items} name={index} filterProperties={(schema) => { if (isIndexComponent(schema)) return false if (isOperationComponent(schema)) return false return true }} /> ) return ( <Collapse.Panel {...props} {...panelProps} onChange={() => {}} key={index} title={title()} > <ArrayBase.Item index={index} key={index} record={() => field.value?.[index]} > {content} </ArrayBase.Item> </Collapse.Panel> ) })} </Collapse> ) } return ( <ArrayBase onAdd={(index) => { onAdd?.(index) setExpandKeys(insertExpandedKeys(expandKeys, index)) }} onCopy={onCopy} onRemove={onRemove} onMoveUp={onMoveUp} onMoveDown={onMoveDown} > {renderEmpty()} {renderItems()} {renderAddition()} </ArrayBase> ) } ) const CollapsePanel: React.FC<React.PropsWithChildren<PanelProps>> = ({ children, }) => { return <Fragment>{children}</Fragment> } CollapsePanel.displayName = 'CollapsePanel' ArrayCollapse.displayName = 'ArrayCollapse' ArrayCollapse.CollapsePanel = CollapsePanel ArrayBase.mixin(ArrayCollapse) export default ArrayCollapse ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Upload.md: -------------------------------------------------------------------------------- ```markdown # Upload > Upload components > > Note: Using the upload component, it is recommended that users perform secondary packaging. Users do not need to care about the data communication between the upload component and Formily, only the style and basic upload configuration are required. ## Markup Schema example ```tsx import React from 'react' import { Upload, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from 'antd' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button icon={<UploadOutlined />}>Upload files</Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" listType="picture-card" headers={{ authorization: 'authorization-text', }} > <UploadOutlined style={{ fontSize: 20 }} /> </Upload> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text"> Click or drag file to this area to upload </p> <p className="ant-upload-hint"> Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files </p> </Upload.Dragger> ) } const SchemaField = createSchemaField({ components: { NormalUpload, CardUpload, DraggerUpload, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.Array name="upload" title="Upload" x-decorator="FormItem" x-component="NormalUpload" required /> <SchemaField.Array name="upload2" title="Card upload" x-decorator="FormItem" x-component="CardUpload" required /> <SchemaField.Array name="upload3" title="Drag and drop upload" x-decorator="FormItem" x-component="DraggerUpload" required /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Upload, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from 'antd' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button icon={<UploadOutlined />}>Upload files</Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" listType="picture-card" headers={{ authorization: 'authorization-text', }} > <UploadOutlined style={{ fontSize: 20 }} /> </Upload> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text"> Click or drag file to this area to upload </p> <p className="ant-upload-hint"> Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files </p> </Upload.Dragger> ) } const SchemaField = createSchemaField({ components: { NormalUpload, CardUpload, DraggerUpload, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { upload: { type: 'array', title: 'Upload', required: true, 'x-decorator': 'FormItem', 'x-component': 'NormalUpload', }, upload2: { type: 'array', title: 'Card upload', required: true, 'x-decorator': 'FormItem', 'x-component': 'CardUpload', }, upload3: { type: 'array', title: 'Drag and drop upload', required: true, 'x-decorator': 'FormItem', 'x-component': 'DraggerUpload', }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Upload, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' import { Button } from 'antd' import { UploadOutlined, InboxOutlined } from '@ant-design/icons' const NormalUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" headers={{ authorization: 'authorization-text', }} > <Button icon={<UploadOutlined />}>Upload files</Button> </Upload> ) } const CardUpload = (props) => { return ( <Upload {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" listType="picture-card" headers={{ authorization: 'authorization-text', }} > <UploadOutlined style={{ fontSize: 20 }} /> </Upload> ) } const DraggerUpload = (props) => { return ( <Upload.Dragger {...props} action="https://www.mocky.io/v2/5cc8019d300000980a055e76" > <p className="ant-upload-drag-icon"> <InboxOutlined /> </p> <p className="ant-upload-text"> Click or drag file to this area to upload </p> <p className="ant-upload-hint"> Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files </p> </Upload.Dragger> ) } const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <Field name="upload" title="Upload" required decorator={[FormItem]} component={[NormalUpload]} /> <Field name="upload2" title="Card upload" required decorator={[FormItem]} component={[CardUpload]} /> <Field name="upload3" title="Drag and drop upload" required decorator={[FormItem]} component={[DraggerUpload]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API Reference https://ant.design/components/upload-cn/ ``` -------------------------------------------------------------------------------- /packages/element/src/preview-text/index.ts: -------------------------------------------------------------------------------- ```typescript import { Field } from '@formily/core' import { observer } from '@formily/reactive-vue' import { isArr, isValid } from '@formily/shared' import { h, useField } from '@formily/vue' import { Tag } from 'element-ui' import { formatDate } from 'element-ui/src/utils/date-util' import { computed, defineComponent, Ref, toRef } from 'vue-demi' import type { CascaderProps } from '../cascader' import type { DatePickerProps } from '../date-picker' import { InputProps } from '../input' import type { SelectProps } from '../select' import { Space } from '../space' import type { TimePickerProps } from '../time-picker' import { stylePrefix } from '../__builtins__/configs' import { composeExport, createContext, resolveComponent, useContext, } from '../__builtins__/shared' const prefixCls = `${stylePrefix}-preview-text` const PlaceholderContext = createContext('N/A') export const usePlaceholder = (value?: Ref<any>) => { const placeholderCtx = useContext(PlaceholderContext) const placeholder = computed(() => { return isValid(value?.value) && value.value !== '' ? value.value : resolveComponent(placeholderCtx.value) || 'N/A' }) return placeholder } const Input = defineComponent<InputProps>({ name: 'FPreviewTextInput', props: ['value'], setup(props, { attrs, slots }) { const value = toRef(props, 'value') const placeholder = usePlaceholder(value) return () => { return h( Space, { class: [prefixCls], style: attrs.style, }, { default: () => [ slots?.prepend?.(), slots?.prefix?.(), placeholder.value, slots?.suffix?.(), slots?.append?.(), ], } ) } }, }) const Select = observer( defineComponent<SelectProps>({ name: 'FPreviewTextSelect', props: [], setup(_props, { attrs }) { const fieldRef = useField<Field>() const field = fieldRef.value const props = attrs as unknown as SelectProps const dataSource: any[] = field?.dataSource?.length ? field.dataSource : props?.options?.length ? props.options : [] const placeholder = usePlaceholder() const getSelected = () => { const value = props.value if (props.multiple) { return isArr(value) ? value.map((val) => ({ label: val, value: val })) : [] } else { return isValid(value) ? [{ label: value, value }] : [] } } const getLabels = () => { const selected = getSelected() if (!selected.length) { return h( Tag, {}, { default: () => placeholder.value, } ) } return selected.map(({ value, label }, key) => { const text = dataSource?.find((item) => item.value == value)?.label || label return h( Tag, { key, props: { type: 'info', effect: 'light', }, }, { default: () => text || placeholder.value, } ) }) } return () => { return h( Space, { class: [prefixCls], style: attrs.style, }, { default: () => getLabels(), } ) } }, }) ) const Cascader = observer( defineComponent<CascaderProps>({ name: 'FPreviewTextCascader', props: [], setup(_props, { attrs }) { const fieldRef = useField<Field>() const field = fieldRef.value const props = attrs as unknown as CascaderProps const dataSource: any[] = field?.dataSource?.length ? field.dataSource : props?.options?.length ? props.options : [] const placeholder = usePlaceholder() const valueKey = props.props?.value || 'value' const labelKey = props.props?.label || 'label' const getSelected = () => { return isArr(props.value) ? props.value : [] } const findLabel = (value: any, dataSource: any[]) => { for (let i = 0; i < dataSource?.length; i++) { const item = dataSource[i] if (item?.[valueKey] === value) { return item?.[labelKey] } else { const childLabel = findLabel(value, item?.children) if (childLabel) return childLabel } } } const getLabels = () => { const selected = getSelected() if (!selected?.length) { return h( Tag, {}, { default: () => placeholder.value, } ) } return selected.map((value, key) => { const text = findLabel(value, dataSource) return h( Tag, { key, props: { type: 'info', effect: 'light', }, }, { default: () => text || placeholder.value, } ) }) } return () => { return h( Space, { class: [prefixCls], style: attrs.style, }, { default: () => getLabels(), } ) } }, }) ) const DatePicker = defineComponent<DatePickerProps>({ name: 'FPreviewTextDatePicker', props: [], setup(_props, { attrs }) { const props = attrs as unknown as DatePickerProps const placeholder = usePlaceholder() const getLabels = () => { if (isArr(props.value)) { const labels = (props.value as any[]).map( (value: String | Date) => formatDate(value, props.format) || placeholder.value ) return labels.join('~') } else { return formatDate(props.value, props.format) || placeholder.value } } return () => { return h( 'div', { class: [prefixCls], style: attrs.style, }, { default: () => getLabels(), } ) } }, }) const TimePicker = defineComponent<TimePickerProps>({ name: 'FPreviewTextTimePicker', props: [], setup(_props, { attrs }) { const props = attrs as unknown as TimePickerProps const format = props.pickerOptions?.format || 'HH:mm:ss' const placeholder = usePlaceholder() const getLabels = () => { if (isArr(props.value)) { const labels = props.value.map( (value) => formatDate(value, format) || placeholder.value ) return labels.join('~') } else { return formatDate(props.value, format) || placeholder.value } } return () => { return h( 'div', { class: [prefixCls], style: attrs.style, }, { default: () => getLabels(), } ) } }, }) const Text = defineComponent<any>({ name: 'FPreviewText', setup(_props, { attrs }) { const placeholder = usePlaceholder() return () => { return h( 'div', { class: [prefixCls], style: attrs.style, }, { default: () => placeholder.value, } ) } }, }) export const PreviewText = composeExport(Text, { Input, Select, Cascader, DatePicker, TimePicker, Placeholder: PlaceholderContext.Provider, usePlaceholder, }) export default PreviewText ```